@@ -16,6 +16,16 @@ export type * from "./virtual_server.ts";
1616import WebSocket , { WebSocketServer as HttpWebSocketServer } from "ws" ;
1717import stoppable from "stoppable" ;
1818import { promises as fs } from "node:fs" ;
19+ import {
20+ openSync ,
21+ readSync ,
22+ writeSync ,
23+ closeSync ,
24+ mkdirSync ,
25+ unlinkSync ,
26+ rmSync ,
27+ } from "node:fs" ;
28+ import os from "node:os" ;
1929import http from "node:http" ;
2030import path from "node:path" ;
2131import { webcrypto } from "node:crypto" ;
@@ -56,12 +66,123 @@ const uncompressed_client_wasm = await fs
5666 . then ( ( buffer ) => load_wasm_stage_0 ( buffer . buffer as ArrayBuffer ) ) ;
5767
5868await perspective_client . default ( { module_or_path : uncompressed_client_wasm } ) ;
69+
70+ function make_node_disk_bridge ( {
71+ heap,
72+ toAddr,
73+ readCString,
74+ } : {
75+ heap : ( ) => Uint8Array ;
76+ toAddr : ( p : number | bigint ) => number ;
77+ readCString : ( p : number | bigint ) => string ;
78+ } ) {
79+ const root = path . join ( os . tmpdir ( ) , `perspective-${ process . pid } ` ) ;
80+ const resolve_path = ( name : string ) => {
81+ const p = path . join ( root , name ) ;
82+ if ( ! p . startsWith ( root ) ) {
83+ throw new Error ( `refusing disk path outside root: ${ name } ` ) ;
84+ }
85+ return p ;
86+ } ;
87+
88+ let cleaned = false ;
89+ const cleanup = ( ) => {
90+ if ( cleaned ) return ;
91+ cleaned = true ;
92+ try {
93+ rmSync ( root , { recursive : true , force : true } ) ;
94+ } catch ( e ) {
95+ /* best effort */
96+ }
97+ } ;
98+ process . on ( "exit" , cleanup ) ;
99+ process . on ( "SIGINT" , ( ) => {
100+ cleanup ( ) ;
101+ process . exit ( 130 ) ;
102+ } ) ;
103+
104+ return {
105+ // node:fs is synchronous — nothing to pre-open between safepoint phases.
106+ async ensureOpen ( _name : string ) { } ,
107+ store (
108+ namePtr : number | bigint ,
109+ dataPtr : number | bigint ,
110+ len : number ,
111+ ) : number {
112+ try {
113+ const p = resolve_path ( readCString ( namePtr ) ) ;
114+ mkdirSync ( path . dirname ( p ) , { recursive : true } ) ;
115+ const fd = openSync ( p , "w" ) ;
116+ try {
117+ if ( len > 0 ) {
118+ const addr = toAddr ( dataPtr ) ;
119+ const view = heap ( ) . subarray ( addr , addr + len ) ;
120+ let off = 0 ;
121+ while ( off < len ) {
122+ off += writeSync ( fd , view , off , len - off , off ) ;
123+ }
124+ }
125+ } finally {
126+ closeSync ( fd ) ;
127+ }
128+ return len ;
129+ } catch ( e ) {
130+ console . error ( "node disk store failed" , e ) ;
131+ return - 1 ;
132+ }
133+ } ,
134+ load (
135+ namePtr : number | bigint ,
136+ dataPtr : number | bigint ,
137+ len : number ,
138+ ) : number {
139+ try {
140+ if ( len <= 0 ) return 0 ;
141+ let fd : number ;
142+ try {
143+ fd = openSync ( resolve_path ( readCString ( namePtr ) ) , "r" ) ;
144+ } catch ( e ) {
145+ return 0 ; // never-flushed file reads as zeros
146+ }
147+ try {
148+ const addr = toAddr ( dataPtr ) ;
149+ const view = heap ( ) . subarray ( addr , addr + len ) ;
150+ let off = 0 ;
151+ let n : number ;
152+ while (
153+ off < len &&
154+ ( n = readSync ( fd , view , off , len - off , off ) ) > 0
155+ ) {
156+ off += n ;
157+ }
158+ return off ;
159+ } finally {
160+ closeSync ( fd ) ;
161+ }
162+ } catch ( e ) {
163+ return 0 ;
164+ }
165+ } ,
166+ remove ( namePtr : number | bigint ) {
167+ try {
168+ unlinkSync ( resolve_path ( readCString ( namePtr ) ) ) ;
169+ } catch ( e ) {
170+ /* already gone */
171+ }
172+ } ,
173+ } ;
174+ }
175+
59176const SYNC_MODULE = await fs
60177 . readFile (
61178 resolve ( "@perspective-dev/server/dist/wasm/perspective-server.wasm" ) ,
62179 )
63180 . then ( ( buffer ) => load_wasm_stage_0 ( buffer . buffer as ArrayBuffer ) )
64- . then ( ( buffer ) => compile_perspective ( buffer . buffer as ArrayBuffer ) ) ;
181+ . then ( ( buffer ) =>
182+ compile_perspective ( buffer . buffer as ArrayBuffer , {
183+ make_disk_bridge : make_node_disk_bridge ,
184+ } ) ,
185+ ) ;
65186
66187let SYNC_CLIENT : perspective_client . Client ;
67188
0 commit comments