@ricsam/isolate-fs 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/cjs/index.cjs +752 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/node-adapter.cjs +230 -0
- package/dist/cjs/node-adapter.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/mjs/index.mjs +708 -0
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/node-adapter.mjs +186 -0
- package/dist/mjs/node-adapter.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/types/index.d.ts +70 -0
- package/dist/types/isolate.d.ts +308 -0
- package/dist/types/node-adapter.d.ts +24 -0
- package/package.json +41 -15
- package/CHANGELOG.md +0 -9
- package/src/fixtures/test-image.png +0 -0
- package/src/index.test.ts +0 -882
- package/src/index.ts +0 -997
- package/src/integration.test.ts +0 -288
- package/src/node-adapter.test.ts +0 -337
- package/src/node-adapter.ts +0 -300
- package/src/streaming.test.ts +0 -634
- package/tsconfig.json +0 -8
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/fs/src/index.ts
|
|
3
|
+
import ivm from "isolated-vm";
|
|
4
|
+
import { setupCore, clearAllInstanceState } from "@ricsam/isolate-core";
|
|
5
|
+
import { createNodeFileSystemHandler } from "./node-adapter.mjs";
|
|
6
|
+
var instanceStateMap = new WeakMap;
|
|
7
|
+
var nextInstanceId = 1;
|
|
8
|
+
function getInstanceStateMapForContext(context) {
|
|
9
|
+
let map = instanceStateMap.get(context);
|
|
10
|
+
if (!map) {
|
|
11
|
+
map = new Map;
|
|
12
|
+
instanceStateMap.set(context, map);
|
|
13
|
+
}
|
|
14
|
+
return map;
|
|
15
|
+
}
|
|
16
|
+
function setupFileSystemDirectoryHandle(context, stateMap) {
|
|
17
|
+
const global = context.global;
|
|
18
|
+
global.setSync("__FileSystemDirectoryHandle_get_name", new ivm.Callback((instanceId) => {
|
|
19
|
+
const state = stateMap.get(instanceId);
|
|
20
|
+
return state?.name ?? "";
|
|
21
|
+
}));
|
|
22
|
+
global.setSync("__FileSystemDirectoryHandle_get_path", new ivm.Callback((instanceId) => {
|
|
23
|
+
const state = stateMap.get(instanceId);
|
|
24
|
+
return state?.path ?? "/";
|
|
25
|
+
}));
|
|
26
|
+
const getFileHandleRef = new ivm.Reference(async (instanceId, name, optionsJson) => {
|
|
27
|
+
const state = stateMap.get(instanceId);
|
|
28
|
+
if (!state) {
|
|
29
|
+
throw new Error("[NotFoundError]Directory handle not found");
|
|
30
|
+
}
|
|
31
|
+
const options = JSON.parse(optionsJson);
|
|
32
|
+
const childPath = state.path === "/" ? `/${name}` : `${state.path}/${name}`;
|
|
33
|
+
try {
|
|
34
|
+
await state.handler.getFileHandle(childPath, options);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof Error) {
|
|
37
|
+
throw new Error(err.message);
|
|
38
|
+
}
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
const fileInstanceId = nextInstanceId++;
|
|
42
|
+
const fileState = {
|
|
43
|
+
instanceId: fileInstanceId,
|
|
44
|
+
path: childPath,
|
|
45
|
+
name,
|
|
46
|
+
handler: state.handler
|
|
47
|
+
};
|
|
48
|
+
stateMap.set(fileInstanceId, fileState);
|
|
49
|
+
return JSON.stringify({ instanceId: fileInstanceId });
|
|
50
|
+
});
|
|
51
|
+
global.setSync("__FileSystemDirectoryHandle_getFileHandle_ref", getFileHandleRef);
|
|
52
|
+
const getDirectoryHandleRef = new ivm.Reference(async (instanceId, name, optionsJson) => {
|
|
53
|
+
const state = stateMap.get(instanceId);
|
|
54
|
+
if (!state) {
|
|
55
|
+
throw new Error("[NotFoundError]Directory handle not found");
|
|
56
|
+
}
|
|
57
|
+
const options = JSON.parse(optionsJson);
|
|
58
|
+
const childPath = state.path === "/" ? `/${name}` : `${state.path}/${name}`;
|
|
59
|
+
try {
|
|
60
|
+
await state.handler.getDirectoryHandle(childPath, options);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err instanceof Error) {
|
|
63
|
+
throw new Error(err.message);
|
|
64
|
+
}
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
const dirInstanceId = nextInstanceId++;
|
|
68
|
+
const dirState = {
|
|
69
|
+
instanceId: dirInstanceId,
|
|
70
|
+
path: childPath,
|
|
71
|
+
name,
|
|
72
|
+
handler: state.handler
|
|
73
|
+
};
|
|
74
|
+
stateMap.set(dirInstanceId, dirState);
|
|
75
|
+
return JSON.stringify({ instanceId: dirInstanceId });
|
|
76
|
+
});
|
|
77
|
+
global.setSync("__FileSystemDirectoryHandle_getDirectoryHandle_ref", getDirectoryHandleRef);
|
|
78
|
+
const removeEntryRef = new ivm.Reference(async (instanceId, name, optionsJson) => {
|
|
79
|
+
const state = stateMap.get(instanceId);
|
|
80
|
+
if (!state) {
|
|
81
|
+
throw new Error("[NotFoundError]Directory handle not found");
|
|
82
|
+
}
|
|
83
|
+
const options = JSON.parse(optionsJson);
|
|
84
|
+
const childPath = state.path === "/" ? `/${name}` : `${state.path}/${name}`;
|
|
85
|
+
try {
|
|
86
|
+
await state.handler.removeEntry(childPath, options);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err instanceof Error) {
|
|
89
|
+
throw new Error(err.message);
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
global.setSync("__FileSystemDirectoryHandle_removeEntry_ref", removeEntryRef);
|
|
95
|
+
const readDirectoryRef = new ivm.Reference(async (instanceId) => {
|
|
96
|
+
const state = stateMap.get(instanceId);
|
|
97
|
+
if (!state) {
|
|
98
|
+
throw new Error("[NotFoundError]Directory handle not found");
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const entries = await state.handler.readDirectory(state.path);
|
|
102
|
+
const result = entries.map((entry) => {
|
|
103
|
+
const entryId = nextInstanceId++;
|
|
104
|
+
const entryPath = state.path === "/" ? `/${entry.name}` : `${state.path}/${entry.name}`;
|
|
105
|
+
if (entry.kind === "file") {
|
|
106
|
+
const fileState = {
|
|
107
|
+
instanceId: entryId,
|
|
108
|
+
path: entryPath,
|
|
109
|
+
name: entry.name,
|
|
110
|
+
handler: state.handler
|
|
111
|
+
};
|
|
112
|
+
stateMap.set(entryId, fileState);
|
|
113
|
+
} else {
|
|
114
|
+
const dirState = {
|
|
115
|
+
instanceId: entryId,
|
|
116
|
+
path: entryPath,
|
|
117
|
+
name: entry.name,
|
|
118
|
+
handler: state.handler
|
|
119
|
+
};
|
|
120
|
+
stateMap.set(entryId, dirState);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
name: entry.name,
|
|
124
|
+
kind: entry.kind,
|
|
125
|
+
instanceId: entryId
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
return JSON.stringify(result);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (err instanceof Error) {
|
|
131
|
+
throw new Error(err.message);
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
global.setSync("__FileSystemDirectoryHandle_readDirectory_ref", readDirectoryRef);
|
|
137
|
+
global.setSync("__FileSystemDirectoryHandle_isSameEntry", new ivm.Callback((id1, id2) => {
|
|
138
|
+
const state1 = stateMap.get(id1);
|
|
139
|
+
const state2 = stateMap.get(id2);
|
|
140
|
+
if (!state1 || !state2)
|
|
141
|
+
return false;
|
|
142
|
+
return state1.path === state2.path;
|
|
143
|
+
}));
|
|
144
|
+
const resolveRef = new ivm.Reference(async (instanceId, descendantId) => {
|
|
145
|
+
const state = stateMap.get(instanceId);
|
|
146
|
+
const descendantState = stateMap.get(descendantId);
|
|
147
|
+
if (!state || !descendantState) {
|
|
148
|
+
return "null";
|
|
149
|
+
}
|
|
150
|
+
const basePath = state.path === "/" ? "" : state.path;
|
|
151
|
+
if (!descendantState.path.startsWith(basePath + "/") && descendantState.path !== state.path) {
|
|
152
|
+
return "null";
|
|
153
|
+
}
|
|
154
|
+
const relativePath = descendantState.path.slice(basePath.length);
|
|
155
|
+
const components = relativePath.split("/").filter((c) => c.length > 0);
|
|
156
|
+
return JSON.stringify(components);
|
|
157
|
+
});
|
|
158
|
+
global.setSync("__FileSystemDirectoryHandle_resolve_ref", resolveRef);
|
|
159
|
+
const directoryHandleCode = `
|
|
160
|
+
(function() {
|
|
161
|
+
const _directoryHandleInstanceIds = new WeakMap();
|
|
162
|
+
|
|
163
|
+
function __decodeError(err) {
|
|
164
|
+
if (!(err instanceof Error)) return err;
|
|
165
|
+
const match = err.message.match(/^\\[(TypeError|RangeError|NotFoundError|TypeMismatchError|InvalidModificationError|Error)\\](.*)$/);
|
|
166
|
+
if (match) {
|
|
167
|
+
if (['NotFoundError', 'TypeMismatchError', 'InvalidModificationError'].includes(match[1])) {
|
|
168
|
+
return new DOMException(match[2], match[1]);
|
|
169
|
+
}
|
|
170
|
+
const ErrorType = globalThis[match[1]] || Error;
|
|
171
|
+
return new ErrorType(match[2]);
|
|
172
|
+
}
|
|
173
|
+
return err;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
class FileSystemDirectoryHandle {
|
|
177
|
+
constructor(path, name) {
|
|
178
|
+
// Internal construction from instance ID
|
|
179
|
+
if (typeof path === 'number' && name === null) {
|
|
180
|
+
_directoryHandleInstanceIds.set(this, path);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const instanceId = __FileSystemDirectoryHandle_construct(path, name);
|
|
184
|
+
_directoryHandleInstanceIds.set(this, instanceId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static _fromInstanceId(instanceId) {
|
|
188
|
+
return new FileSystemDirectoryHandle(instanceId, null);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_getInstanceId() {
|
|
192
|
+
return _directoryHandleInstanceIds.get(this);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
get kind() {
|
|
196
|
+
return 'directory';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get name() {
|
|
200
|
+
return __FileSystemDirectoryHandle_get_name(this._getInstanceId());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
getFileHandle(name, options = {}) {
|
|
204
|
+
try {
|
|
205
|
+
const resultJson = __FileSystemDirectoryHandle_getFileHandle_ref.applySyncPromise(
|
|
206
|
+
undefined,
|
|
207
|
+
[this._getInstanceId(), name, JSON.stringify(options)]
|
|
208
|
+
);
|
|
209
|
+
const result = JSON.parse(resultJson);
|
|
210
|
+
return FileSystemFileHandle._fromInstanceId(result.instanceId);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
throw __decodeError(err);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
getDirectoryHandle(name, options = {}) {
|
|
217
|
+
try {
|
|
218
|
+
const resultJson = __FileSystemDirectoryHandle_getDirectoryHandle_ref.applySyncPromise(
|
|
219
|
+
undefined,
|
|
220
|
+
[this._getInstanceId(), name, JSON.stringify(options)]
|
|
221
|
+
);
|
|
222
|
+
const result = JSON.parse(resultJson);
|
|
223
|
+
return FileSystemDirectoryHandle._fromInstanceId(result.instanceId);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
throw __decodeError(err);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
removeEntry(name, options = {}) {
|
|
230
|
+
try {
|
|
231
|
+
__FileSystemDirectoryHandle_removeEntry_ref.applySyncPromise(
|
|
232
|
+
undefined,
|
|
233
|
+
[this._getInstanceId(), name, JSON.stringify(options)]
|
|
234
|
+
);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
throw __decodeError(err);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async *entries() {
|
|
241
|
+
let entriesJson;
|
|
242
|
+
try {
|
|
243
|
+
entriesJson = __FileSystemDirectoryHandle_readDirectory_ref.applySyncPromise(
|
|
244
|
+
undefined,
|
|
245
|
+
[this._getInstanceId()]
|
|
246
|
+
);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
throw __decodeError(err);
|
|
249
|
+
}
|
|
250
|
+
const entries = JSON.parse(entriesJson);
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
if (entry.kind === 'file') {
|
|
253
|
+
yield [entry.name, FileSystemFileHandle._fromInstanceId(entry.instanceId)];
|
|
254
|
+
} else {
|
|
255
|
+
yield [entry.name, FileSystemDirectoryHandle._fromInstanceId(entry.instanceId)];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async *keys() {
|
|
261
|
+
for await (const [name] of this.entries()) {
|
|
262
|
+
yield name;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async *values() {
|
|
267
|
+
for await (const [, handle] of this.entries()) {
|
|
268
|
+
yield handle;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
[Symbol.asyncIterator]() {
|
|
273
|
+
return this.entries();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
isSameEntry(other) {
|
|
277
|
+
if (!(other instanceof FileSystemDirectoryHandle)) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
return __FileSystemDirectoryHandle_isSameEntry(
|
|
281
|
+
this._getInstanceId(),
|
|
282
|
+
other._getInstanceId()
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
resolve(possibleDescendant) {
|
|
287
|
+
try {
|
|
288
|
+
const resultJson = __FileSystemDirectoryHandle_resolve_ref.applySyncPromise(
|
|
289
|
+
undefined,
|
|
290
|
+
[this._getInstanceId(), possibleDescendant._getInstanceId()]
|
|
291
|
+
);
|
|
292
|
+
return resultJson === 'null' ? null : JSON.parse(resultJson);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
throw __decodeError(err);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
globalThis.FileSystemDirectoryHandle = FileSystemDirectoryHandle;
|
|
300
|
+
})();
|
|
301
|
+
`;
|
|
302
|
+
context.evalSync(directoryHandleCode);
|
|
303
|
+
}
|
|
304
|
+
function setupFileSystemFileHandle(context, stateMap) {
|
|
305
|
+
const global = context.global;
|
|
306
|
+
global.setSync("__FileSystemFileHandle_get_name", new ivm.Callback((instanceId) => {
|
|
307
|
+
const state = stateMap.get(instanceId);
|
|
308
|
+
return state?.name ?? "";
|
|
309
|
+
}));
|
|
310
|
+
global.setSync("__FileSystemFileHandle_get_path", new ivm.Callback((instanceId) => {
|
|
311
|
+
const state = stateMap.get(instanceId);
|
|
312
|
+
return state?.path ?? "";
|
|
313
|
+
}));
|
|
314
|
+
const getFileRef = new ivm.Reference(async (instanceId) => {
|
|
315
|
+
const state = stateMap.get(instanceId);
|
|
316
|
+
if (!state) {
|
|
317
|
+
throw new Error("[NotFoundError]File handle not found");
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
const fileData = await state.handler.readFile(state.path);
|
|
321
|
+
return JSON.stringify({
|
|
322
|
+
name: state.name,
|
|
323
|
+
data: Array.from(fileData.data),
|
|
324
|
+
size: fileData.size,
|
|
325
|
+
lastModified: fileData.lastModified,
|
|
326
|
+
type: fileData.type
|
|
327
|
+
});
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (err instanceof Error) {
|
|
330
|
+
throw new Error(err.message);
|
|
331
|
+
}
|
|
332
|
+
throw err;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
global.setSync("__FileSystemFileHandle_getFile_ref", getFileRef);
|
|
336
|
+
const createWritableRef = new ivm.Reference(async (instanceId, _optionsJson) => {
|
|
337
|
+
const state = stateMap.get(instanceId);
|
|
338
|
+
if (!state) {
|
|
339
|
+
throw new Error("[NotFoundError]File handle not found");
|
|
340
|
+
}
|
|
341
|
+
const streamInstanceId = nextInstanceId++;
|
|
342
|
+
const streamState = {
|
|
343
|
+
instanceId: streamInstanceId,
|
|
344
|
+
filePath: state.path,
|
|
345
|
+
position: 0,
|
|
346
|
+
buffer: [],
|
|
347
|
+
closed: false,
|
|
348
|
+
handler: state.handler
|
|
349
|
+
};
|
|
350
|
+
stateMap.set(streamInstanceId, streamState);
|
|
351
|
+
return streamInstanceId;
|
|
352
|
+
});
|
|
353
|
+
global.setSync("__FileSystemFileHandle_createWritable_ref", createWritableRef);
|
|
354
|
+
global.setSync("__FileSystemFileHandle_isSameEntry", new ivm.Callback((id1, id2) => {
|
|
355
|
+
const state1 = stateMap.get(id1);
|
|
356
|
+
const state2 = stateMap.get(id2);
|
|
357
|
+
if (!state1 || !state2)
|
|
358
|
+
return false;
|
|
359
|
+
return state1.path === state2.path;
|
|
360
|
+
}));
|
|
361
|
+
const fileHandleCode = `
|
|
362
|
+
(function() {
|
|
363
|
+
const _fileHandleInstanceIds = new WeakMap();
|
|
364
|
+
|
|
365
|
+
function __decodeError(err) {
|
|
366
|
+
if (!(err instanceof Error)) return err;
|
|
367
|
+
const match = err.message.match(/^\\[(TypeError|RangeError|NotFoundError|TypeMismatchError|InvalidModificationError|Error)\\](.*)$/);
|
|
368
|
+
if (match) {
|
|
369
|
+
if (['NotFoundError', 'TypeMismatchError', 'InvalidModificationError'].includes(match[1])) {
|
|
370
|
+
return new DOMException(match[2], match[1]);
|
|
371
|
+
}
|
|
372
|
+
const ErrorType = globalThis[match[1]] || Error;
|
|
373
|
+
return new ErrorType(match[2]);
|
|
374
|
+
}
|
|
375
|
+
return err;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
class FileSystemFileHandle {
|
|
379
|
+
constructor(path, name) {
|
|
380
|
+
// Internal construction from instance ID
|
|
381
|
+
if (typeof path === 'number' && name === null) {
|
|
382
|
+
_fileHandleInstanceIds.set(this, path);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const instanceId = __FileSystemFileHandle_construct(path, name);
|
|
386
|
+
_fileHandleInstanceIds.set(this, instanceId);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
static _fromInstanceId(instanceId) {
|
|
390
|
+
return new FileSystemFileHandle(instanceId, null);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
_getInstanceId() {
|
|
394
|
+
return _fileHandleInstanceIds.get(this);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
get kind() {
|
|
398
|
+
return 'file';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
get name() {
|
|
402
|
+
return __FileSystemFileHandle_get_name(this._getInstanceId());
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
getFile() {
|
|
406
|
+
try {
|
|
407
|
+
const metadataJson = __FileSystemFileHandle_getFile_ref.applySyncPromise(
|
|
408
|
+
undefined,
|
|
409
|
+
[this._getInstanceId()]
|
|
410
|
+
);
|
|
411
|
+
const metadata = JSON.parse(metadataJson);
|
|
412
|
+
// Create File object from metadata and content
|
|
413
|
+
const content = new Uint8Array(metadata.data);
|
|
414
|
+
return new File([content], metadata.name, {
|
|
415
|
+
type: metadata.type,
|
|
416
|
+
lastModified: metadata.lastModified
|
|
417
|
+
});
|
|
418
|
+
} catch (err) {
|
|
419
|
+
throw __decodeError(err);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
createWritable(options = {}) {
|
|
424
|
+
try {
|
|
425
|
+
const streamId = __FileSystemFileHandle_createWritable_ref.applySyncPromise(
|
|
426
|
+
undefined,
|
|
427
|
+
[this._getInstanceId(), JSON.stringify(options)]
|
|
428
|
+
);
|
|
429
|
+
return FileSystemWritableFileStream._fromInstanceId(streamId);
|
|
430
|
+
} catch (err) {
|
|
431
|
+
throw __decodeError(err);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
isSameEntry(other) {
|
|
436
|
+
if (!(other instanceof FileSystemFileHandle)) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
return __FileSystemFileHandle_isSameEntry(
|
|
440
|
+
this._getInstanceId(),
|
|
441
|
+
other._getInstanceId()
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
globalThis.FileSystemFileHandle = FileSystemFileHandle;
|
|
447
|
+
})();
|
|
448
|
+
`;
|
|
449
|
+
context.evalSync(fileHandleCode);
|
|
450
|
+
}
|
|
451
|
+
function setupFileSystemWritableFileStream(context, stateMap) {
|
|
452
|
+
const global = context.global;
|
|
453
|
+
const writeRef = new ivm.Reference(async (instanceId, bytesJson, position) => {
|
|
454
|
+
const state = stateMap.get(instanceId);
|
|
455
|
+
if (!state) {
|
|
456
|
+
throw new Error("[InvalidStateError]Stream not found");
|
|
457
|
+
}
|
|
458
|
+
if (state.closed) {
|
|
459
|
+
throw new Error("[InvalidStateError]Stream is closed");
|
|
460
|
+
}
|
|
461
|
+
const bytes = JSON.parse(bytesJson);
|
|
462
|
+
const data = new Uint8Array(bytes);
|
|
463
|
+
if (position !== null) {
|
|
464
|
+
state.position = position;
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
await state.handler.writeFile(state.filePath, data, state.position);
|
|
468
|
+
state.position += data.length;
|
|
469
|
+
} catch (err) {
|
|
470
|
+
if (err instanceof Error) {
|
|
471
|
+
throw new Error(err.message);
|
|
472
|
+
}
|
|
473
|
+
throw err;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
global.setSync("__FileSystemWritableFileStream_write_ref", writeRef);
|
|
477
|
+
global.setSync("__FileSystemWritableFileStream_seek", new ivm.Callback((instanceId, position) => {
|
|
478
|
+
const state = stateMap.get(instanceId);
|
|
479
|
+
if (!state) {
|
|
480
|
+
throw new Error("[InvalidStateError]Stream not found");
|
|
481
|
+
}
|
|
482
|
+
if (state.closed) {
|
|
483
|
+
throw new Error("[InvalidStateError]Stream is closed");
|
|
484
|
+
}
|
|
485
|
+
state.position = position;
|
|
486
|
+
}));
|
|
487
|
+
const truncateRef = new ivm.Reference(async (instanceId, size) => {
|
|
488
|
+
const state = stateMap.get(instanceId);
|
|
489
|
+
if (!state) {
|
|
490
|
+
throw new Error("[InvalidStateError]Stream not found");
|
|
491
|
+
}
|
|
492
|
+
if (state.closed) {
|
|
493
|
+
throw new Error("[InvalidStateError]Stream is closed");
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
await state.handler.truncateFile(state.filePath, size);
|
|
497
|
+
if (state.position > size) {
|
|
498
|
+
state.position = size;
|
|
499
|
+
}
|
|
500
|
+
} catch (err) {
|
|
501
|
+
if (err instanceof Error) {
|
|
502
|
+
throw new Error(err.message);
|
|
503
|
+
}
|
|
504
|
+
throw err;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
global.setSync("__FileSystemWritableFileStream_truncate_ref", truncateRef);
|
|
508
|
+
const closeRef = new ivm.Reference(async (instanceId) => {
|
|
509
|
+
const state = stateMap.get(instanceId);
|
|
510
|
+
if (!state) {
|
|
511
|
+
throw new Error("[InvalidStateError]Stream not found");
|
|
512
|
+
}
|
|
513
|
+
if (state.closed) {
|
|
514
|
+
throw new Error("[InvalidStateError]Stream is already closed");
|
|
515
|
+
}
|
|
516
|
+
state.closed = true;
|
|
517
|
+
});
|
|
518
|
+
global.setSync("__FileSystemWritableFileStream_close_ref", closeRef);
|
|
519
|
+
const abortRef = new ivm.Reference(async (instanceId, _reason) => {
|
|
520
|
+
const state = stateMap.get(instanceId);
|
|
521
|
+
if (!state) {
|
|
522
|
+
throw new Error("[InvalidStateError]Stream not found");
|
|
523
|
+
}
|
|
524
|
+
state.closed = true;
|
|
525
|
+
state.buffer = [];
|
|
526
|
+
});
|
|
527
|
+
global.setSync("__FileSystemWritableFileStream_abort_ref", abortRef);
|
|
528
|
+
global.setSync("__FileSystemWritableFileStream_get_locked", new ivm.Callback((instanceId) => {
|
|
529
|
+
const state = stateMap.get(instanceId);
|
|
530
|
+
return state ? !state.closed : false;
|
|
531
|
+
}));
|
|
532
|
+
const writableStreamCode = `
|
|
533
|
+
(function() {
|
|
534
|
+
const _writableStreamInstanceIds = new WeakMap();
|
|
535
|
+
|
|
536
|
+
function __decodeError(err) {
|
|
537
|
+
if (!(err instanceof Error)) return err;
|
|
538
|
+
const match = err.message.match(/^\\[(TypeError|RangeError|InvalidStateError|NotFoundError|Error)\\](.*)$/);
|
|
539
|
+
if (match) {
|
|
540
|
+
if (['InvalidStateError', 'NotFoundError'].includes(match[1])) {
|
|
541
|
+
return new DOMException(match[2], match[1]);
|
|
542
|
+
}
|
|
543
|
+
const ErrorType = globalThis[match[1]] || Error;
|
|
544
|
+
return new ErrorType(match[2]);
|
|
545
|
+
}
|
|
546
|
+
return err;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
class FileSystemWritableFileStream {
|
|
550
|
+
constructor(instanceId) {
|
|
551
|
+
_writableStreamInstanceIds.set(this, instanceId);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
static _fromInstanceId(instanceId) {
|
|
555
|
+
return new FileSystemWritableFileStream(instanceId);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
_getInstanceId() {
|
|
559
|
+
return _writableStreamInstanceIds.get(this);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
write(data) {
|
|
563
|
+
try {
|
|
564
|
+
// Handle different data types
|
|
565
|
+
let writeData;
|
|
566
|
+
let position = null;
|
|
567
|
+
let type = 'write';
|
|
568
|
+
|
|
569
|
+
if (data && typeof data === 'object' && !ArrayBuffer.isView(data) &&
|
|
570
|
+
!(data instanceof Blob) && !(data instanceof ArrayBuffer) &&
|
|
571
|
+
!Array.isArray(data) && typeof data.type === 'string') {
|
|
572
|
+
// WriteParams object: { type, data, position, size }
|
|
573
|
+
type = data.type || 'write';
|
|
574
|
+
if (type === 'seek') {
|
|
575
|
+
return this.seek(data.position);
|
|
576
|
+
}
|
|
577
|
+
if (type === 'truncate') {
|
|
578
|
+
return this.truncate(data.size);
|
|
579
|
+
}
|
|
580
|
+
writeData = data.data;
|
|
581
|
+
position = data.position ?? null;
|
|
582
|
+
} else {
|
|
583
|
+
writeData = data;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Convert data to bytes array for transfer
|
|
587
|
+
let bytes;
|
|
588
|
+
if (typeof writeData === 'string') {
|
|
589
|
+
bytes = Array.from(new TextEncoder().encode(writeData));
|
|
590
|
+
} else if (writeData instanceof Blob) {
|
|
591
|
+
// Synchronously get blob bytes - use the internal callback
|
|
592
|
+
const blobText = writeData.text ? writeData.text() : '';
|
|
593
|
+
bytes = Array.from(new TextEncoder().encode(blobText));
|
|
594
|
+
} else if (writeData instanceof ArrayBuffer) {
|
|
595
|
+
bytes = Array.from(new Uint8Array(writeData));
|
|
596
|
+
} else if (ArrayBuffer.isView(writeData)) {
|
|
597
|
+
bytes = Array.from(new Uint8Array(writeData.buffer, writeData.byteOffset, writeData.byteLength));
|
|
598
|
+
} else if (Array.isArray(writeData)) {
|
|
599
|
+
bytes = writeData;
|
|
600
|
+
} else {
|
|
601
|
+
throw new TypeError('Invalid data type for write');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
__FileSystemWritableFileStream_write_ref.applySyncPromise(
|
|
605
|
+
undefined,
|
|
606
|
+
[this._getInstanceId(), JSON.stringify(bytes), position]
|
|
607
|
+
);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
throw __decodeError(err);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
seek(position) {
|
|
614
|
+
try {
|
|
615
|
+
__FileSystemWritableFileStream_seek(this._getInstanceId(), position);
|
|
616
|
+
} catch (err) {
|
|
617
|
+
throw __decodeError(err);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
truncate(size) {
|
|
622
|
+
try {
|
|
623
|
+
__FileSystemWritableFileStream_truncate_ref.applySyncPromise(
|
|
624
|
+
undefined,
|
|
625
|
+
[this._getInstanceId(), size]
|
|
626
|
+
);
|
|
627
|
+
} catch (err) {
|
|
628
|
+
throw __decodeError(err);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
close() {
|
|
633
|
+
try {
|
|
634
|
+
__FileSystemWritableFileStream_close_ref.applySyncPromise(
|
|
635
|
+
undefined,
|
|
636
|
+
[this._getInstanceId()]
|
|
637
|
+
);
|
|
638
|
+
} catch (err) {
|
|
639
|
+
throw __decodeError(err);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
abort(reason) {
|
|
644
|
+
try {
|
|
645
|
+
__FileSystemWritableFileStream_abort_ref.applySyncPromise(
|
|
646
|
+
undefined,
|
|
647
|
+
[this._getInstanceId(), reason ? String(reason) : null]
|
|
648
|
+
);
|
|
649
|
+
} catch (err) {
|
|
650
|
+
throw __decodeError(err);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
get locked() {
|
|
655
|
+
return __FileSystemWritableFileStream_get_locked(this._getInstanceId());
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
globalThis.FileSystemWritableFileStream = FileSystemWritableFileStream;
|
|
660
|
+
})();
|
|
661
|
+
`;
|
|
662
|
+
context.evalSync(writableStreamCode);
|
|
663
|
+
}
|
|
664
|
+
function setupGetDirectoryGlobal(context, stateMap, options) {
|
|
665
|
+
const global = context.global;
|
|
666
|
+
const getDirectoryRef = new ivm.Reference(async (path) => {
|
|
667
|
+
const handler = await options.getDirectory(path);
|
|
668
|
+
const instanceId = nextInstanceId++;
|
|
669
|
+
const state = {
|
|
670
|
+
instanceId,
|
|
671
|
+
path: "/",
|
|
672
|
+
name: path.split("/").filter(Boolean).pop() || "",
|
|
673
|
+
handler
|
|
674
|
+
};
|
|
675
|
+
stateMap.set(instanceId, state);
|
|
676
|
+
return instanceId;
|
|
677
|
+
});
|
|
678
|
+
global.setSync("__getDirectory_ref", getDirectoryRef);
|
|
679
|
+
const getDirectoryCode = `
|
|
680
|
+
(function() {
|
|
681
|
+
globalThis.getDirectory = async function(path) {
|
|
682
|
+
const instanceId = await __getDirectory_ref.applySyncPromise(undefined, [path]);
|
|
683
|
+
return FileSystemDirectoryHandle._fromInstanceId(instanceId);
|
|
684
|
+
};
|
|
685
|
+
})();
|
|
686
|
+
`;
|
|
687
|
+
context.evalSync(getDirectoryCode);
|
|
688
|
+
}
|
|
689
|
+
async function setupFs(context, options) {
|
|
690
|
+
await setupCore(context);
|
|
691
|
+
const stateMap = getInstanceStateMapForContext(context);
|
|
692
|
+
setupFileSystemDirectoryHandle(context, stateMap);
|
|
693
|
+
setupFileSystemFileHandle(context, stateMap);
|
|
694
|
+
setupFileSystemWritableFileStream(context, stateMap);
|
|
695
|
+
setupGetDirectoryGlobal(context, stateMap, options);
|
|
696
|
+
return {
|
|
697
|
+
dispose() {
|
|
698
|
+
stateMap.clear();
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
export {
|
|
703
|
+
setupFs,
|
|
704
|
+
createNodeFileSystemHandler,
|
|
705
|
+
clearAllInstanceState
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
//# debugId=1B534AD8A27DB4E164756E2164756E21
|