@ricsam/quickjs-fs 0.2.17 → 0.2.19
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 +20 -9
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/setup.cjs +2 -5
- package/dist/cjs/setup.cjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/setup.mjs +2 -5
- package/dist/mjs/setup.mjs.map +3 -3
- package/dist/types/quickjs.d.ts +17 -21
- package/dist/types/setup.d.ts +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# @ricsam/quickjs-fs
|
|
2
2
|
|
|
3
|
-
File System Access API (OPFS-compatible).
|
|
3
|
+
File System Access API (OPFS-compatible) for QuickJS runtime.
|
|
4
|
+
|
|
5
|
+
> **Note**: This is a low-level package. For most use cases, use [`@ricsam/quickjs-runtime`](../runtime) with `createRuntime()` instead.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @ricsam/quickjs-fs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
4
14
|
|
|
5
15
|
```typescript
|
|
6
16
|
import { setupFs, createNodeDirectoryHandle } from "@ricsam/quickjs-fs";
|
|
@@ -16,16 +26,17 @@ const handle = setupFs(context, {
|
|
|
16
26
|
});
|
|
17
27
|
```
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
## Injected Globals
|
|
30
|
+
|
|
31
|
+
- `getDirectory(path)` - Entry point for file system access
|
|
21
32
|
- `FileSystemDirectoryHandle`, `FileSystemFileHandle`
|
|
22
33
|
- `FileSystemWritableFileStream`
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
## Usage in QuickJS
|
|
25
36
|
|
|
26
37
|
```javascript
|
|
27
38
|
// Get directory handle
|
|
28
|
-
const root = await
|
|
39
|
+
const root = await getDirectory("/data");
|
|
29
40
|
|
|
30
41
|
// Read a file
|
|
31
42
|
const fileHandle = await root.getFileHandle("config.json");
|
|
@@ -50,9 +61,9 @@ for await (const [name, handle] of root.entries()) {
|
|
|
50
61
|
}
|
|
51
62
|
```
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
## Host Adapters
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
### Node.js/Bun adapter
|
|
56
67
|
|
|
57
68
|
```typescript
|
|
58
69
|
import { createNodeDirectoryHandle } from "@ricsam/quickjs-fs";
|
|
@@ -60,7 +71,7 @@ import { createNodeDirectoryHandle } from "@ricsam/quickjs-fs";
|
|
|
60
71
|
const dirHandle = createNodeDirectoryHandle("/path/to/directory");
|
|
61
72
|
```
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
### In-memory adapter (for testing)
|
|
64
75
|
|
|
65
76
|
```typescript
|
|
66
77
|
import { createMemoryDirectoryHandle } from "@ricsam/quickjs-fs";
|
|
@@ -69,4 +80,4 @@ const memFs = createMemoryDirectoryHandle({
|
|
|
69
80
|
"config.json": JSON.stringify({ debug: true }),
|
|
70
81
|
"data/users.json": JSON.stringify([]),
|
|
71
82
|
});
|
|
72
|
-
```
|
|
83
|
+
```
|
package/dist/cjs/package.json
CHANGED
package/dist/cjs/setup.cjs
CHANGED
|
@@ -101,7 +101,6 @@ function setupFs(context, options) {
|
|
|
101
101
|
throw new Error(`Failed to set up FS wrapper methods: ${JSON.stringify(error)}`);
|
|
102
102
|
}
|
|
103
103
|
wrapperResult.value.dispose();
|
|
104
|
-
const fsNamespace = context.newObject();
|
|
105
104
|
const getDirectoryFn = context.newFunction("getDirectory", (pathHandle) => {
|
|
106
105
|
const path = context.getString(pathHandle);
|
|
107
106
|
const deferred = context.newPromise();
|
|
@@ -128,9 +127,7 @@ function setupFs(context, options) {
|
|
|
128
127
|
return deferred.handle;
|
|
129
128
|
});
|
|
130
129
|
handles.push(getDirectoryFn);
|
|
131
|
-
context.setProp(
|
|
132
|
-
context.setProp(context.global, "fs", fsNamespace);
|
|
133
|
-
fsNamespace.dispose();
|
|
130
|
+
context.setProp(context.global, "getDirectory", getDirectoryFn);
|
|
134
131
|
return {
|
|
135
132
|
stateMap,
|
|
136
133
|
dispose() {
|
|
@@ -144,4 +141,4 @@ function setupFs(context, options) {
|
|
|
144
141
|
}
|
|
145
142
|
})
|
|
146
143
|
|
|
147
|
-
//# debugId=
|
|
144
|
+
//# debugId=84DEDC530B56E65C64756E2164756E21
|
package/dist/cjs/setup.cjs.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/setup.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport {\n setupCore,\n createStateMap,\n marshal,\n} from \"@ricsam/quickjs-core\";\nimport type { SetupFsOptions, FsHandle } from \"./types.cjs\";\nimport { createFileSystemWritableFileStreamClass } from \"./handles/writable-stream.cjs\";\nimport { createFileSystemFileHandleClass } from \"./handles/file-handle.cjs\";\nimport { createFileSystemDirectoryHandleClass, registerHostDirectoryHandle } from \"./handles/directory-handle.cjs\";\n\n/**\n * Setup File System API in a QuickJS context\n *\n * Injects the following globals:\n * -
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport {\n setupCore,\n createStateMap,\n marshal,\n} from \"@ricsam/quickjs-core\";\nimport type { SetupFsOptions, FsHandle } from \"./types.cjs\";\nimport { createFileSystemWritableFileStreamClass } from \"./handles/writable-stream.cjs\";\nimport { createFileSystemFileHandleClass } from \"./handles/file-handle.cjs\";\nimport { createFileSystemDirectoryHandleClass, registerHostDirectoryHandle } from \"./handles/directory-handle.cjs\";\n\n/**\n * Setup File System API in a QuickJS context\n *\n * Injects the following globals:\n * - getDirectory(path) - Server-compatible entry point\n * - FileSystemFileHandle\n * - FileSystemDirectoryHandle\n * - FileSystemWritableFileStream\n *\n * **Private globals (internal use):**\n * - `__FileSystemFileHandle__` - For creating file handle instances from wrapper code\n * - `__FileSystemDirectoryHandle__` - For creating directory handle instances\n * - `__FileSystemWritableFileStream__` - For creating writable stream instances\n *\n * These private globals follow the `__Name__` convention and are required for the\n * wrapper pattern: public methods call internal methods that return IDs, then use\n * evalCode with private constructors to create properly-typed class instances.\n * See PATTERNS.md sections 2 and 5 for details.\n *\n * **Wrapper pattern example:**\n * ```typescript\n * // Public method (added via evalCode)\n * FileSystemFileHandle.prototype.createWritable = async function(options) {\n * const result = await this._createWritableInternal(options);\n * return new __FileSystemWritableFileStream__(result.__writableStreamId);\n * };\n * ```\n *\n * @example\n * import { setupFs, createNodeDirectoryHandle } from \"@ricsam/quickjs-fs\";\n *\n * const handle = setupFs(context, {\n * getDirectory: async (path) => {\n * // Validate and resolve path\n * const resolvedPath = resolveSandboxPath(path);\n * return createNodeDirectoryHandle(resolvedPath);\n * }\n * });\n *\n * context.evalCode(`\n * const root = await getDirectory(\"/data\");\n * const configHandle = await root.getFileHandle(\"config.json\");\n * const file = await configHandle.getFile();\n * const text = await file.text();\n * console.log(JSON.parse(text));\n *\n * // Write a new file\n * const outputHandle = await root.getFileHandle(\"output.txt\", { create: true });\n * const writable = await outputHandle.createWritable();\n * await writable.write(\"Hello, World!\");\n * await writable.close();\n * `);\n */\nexport function setupFs(\n context: QuickJSContext,\n options: SetupFsOptions\n): FsHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n const handles: QuickJSHandle[] = [];\n\n // Create FileSystemWritableFileStream class\n const WritableStreamClass = createFileSystemWritableFileStreamClass(\n context,\n stateMap\n );\n context.setProp(\n context.global,\n \"FileSystemWritableFileStream\",\n WritableStreamClass\n );\n context.setProp(context.global, \"__FileSystemWritableFileStream__\", WritableStreamClass);\n WritableStreamClass.dispose();\n\n // Create FileSystemFileHandle class\n const FileHandleClass = createFileSystemFileHandleClass(\n context,\n stateMap\n );\n context.setProp(context.global, \"FileSystemFileHandle\", FileHandleClass);\n context.setProp(context.global, \"__FileSystemFileHandle__\", FileHandleClass);\n FileHandleClass.dispose();\n\n // Create FileSystemDirectoryHandle class\n const DirectoryHandleClass = createFileSystemDirectoryHandleClass(\n context,\n stateMap\n );\n context.setProp(\n context.global,\n \"FileSystemDirectoryHandle\",\n DirectoryHandleClass\n );\n context.setProp(context.global, \"__FileSystemDirectoryHandle__\", DirectoryHandleClass);\n DirectoryHandleClass.dispose();\n\n // Add Symbol.asyncIterator support for FileSystemDirectoryHandle (for await...of)\n const asyncIteratorResult = context.evalCode(`\n FileSystemDirectoryHandle.prototype[Symbol.asyncIterator] = async function*() {\n const entries = await this.entries();\n for (const entry of entries) {\n yield entry;\n }\n };\n `);\n if (asyncIteratorResult.error) {\n asyncIteratorResult.error.dispose();\n } else {\n asyncIteratorResult.value.dispose();\n }\n\n // Add wrapper methods that convert IDs to class instances\n const wrapperCode = `\n // Wrapper for getFileHandle\n FileSystemDirectoryHandle.prototype.getFileHandle = async function(name, options) {\n const result = await this._getFileHandleInternal(name, options);\n return new __FileSystemFileHandle__(result.__fileHandleId);\n };\n\n // Wrapper for getDirectoryHandle\n FileSystemDirectoryHandle.prototype.getDirectoryHandle = async function(name, options) {\n const result = await this._getDirectoryHandleInternal(name, options);\n return new __FileSystemDirectoryHandle__(result.__directoryHandleId);\n };\n\n // Wrapper for createWritable\n FileSystemFileHandle.prototype.createWritable = async function(options) {\n const result = await this._createWritableInternal(options);\n return new __FileSystemWritableFileStream__(result.__writableStreamId);\n };\n\n // Wrapper for getFile - creates proper File instance\n FileSystemFileHandle.prototype.getFile = async function() {\n const result = await this._getFileInternal();\n const { buffer, type, name, lastModified } = result.__fileData;\n return new File([buffer], name, { type, lastModified });\n };\n `;\n const wrapperResult = context.evalCode(wrapperCode);\n if (wrapperResult.error) {\n const error = context.dump(wrapperResult.error);\n wrapperResult.error.dispose();\n throw new Error(`Failed to set up FS wrapper methods: ${JSON.stringify(error)}`);\n }\n wrapperResult.value.dispose();\n\n // Create getDirectory function that returns proper class instances\n const getDirectoryFn = context.newFunction(\"getDirectory\", (pathHandle) => {\n const path = context.getString(pathHandle);\n\n const deferred = context.newPromise();\n\n options\n .getDirectory(path)\n .then((hostHandle) => {\n // Register the host handle and get an ID\n const hostHandleId = registerHostDirectoryHandle(hostHandle);\n\n // Create instance using evalCode with the ID\n const instanceResult = context.evalCode(\n `new __FileSystemDirectoryHandle__(${hostHandleId})`\n );\n\n if (instanceResult.error) {\n deferred.reject(instanceResult.error);\n instanceResult.error.dispose();\n } else {\n deferred.resolve(instanceResult.value);\n instanceResult.value.dispose();\n }\n context.runtime.executePendingJobs();\n })\n .catch((error) => {\n const errorHandle = marshal(context, {\n name: error instanceof Error ? error.name : \"Error\",\n message: error instanceof Error ? error.message : String(error),\n });\n deferred.reject(errorHandle);\n errorHandle.dispose();\n context.runtime.executePendingJobs();\n });\n\n return deferred.handle;\n });\n handles.push(getDirectoryFn);\n context.setProp(context.global, \"getDirectory\", getDirectoryFn);\n\n return {\n stateMap,\n dispose() {\n for (const handle of handles) {\n if (handle.alive) {\n handle.dispose();\n }\n }\n },\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKO,IAJP;AAMwD,IAAxD;AACgD,IAAhD;AACkF,IAAlF;AAuDO,SAAS,OAAO,CACrB,SACA,SACU;AAAA,EAEV,MAAM,aACJ,QAAQ,cACR,8BAAU,SAAS;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAAA,EAEH,MAAM,WAAW,QAAQ,YAAY,WAAW;AAAA,EAChD,MAAM,UAA2B,CAAC;AAAA,EAGlC,MAAM,sBAAsB,+DAC1B,SACA,QACF;AAAA,EACA,QAAQ,QACN,QAAQ,QACR,gCACA,mBACF;AAAA,EACA,QAAQ,QAAQ,QAAQ,QAAQ,oCAAoC,mBAAmB;AAAA,EACvF,oBAAoB,QAAQ;AAAA,EAG5B,MAAM,kBAAkB,mDACtB,SACA,QACF;AAAA,EACA,QAAQ,QAAQ,QAAQ,QAAQ,wBAAwB,eAAe;AAAA,EACvE,QAAQ,QAAQ,QAAQ,QAAQ,4BAA4B,eAAe;AAAA,EAC3E,gBAAgB,QAAQ;AAAA,EAGxB,MAAM,uBAAuB,6DAC3B,SACA,QACF;AAAA,EACA,QAAQ,QACN,QAAQ,QACR,6BACA,oBACF;AAAA,EACA,QAAQ,QAAQ,QAAQ,QAAQ,iCAAiC,oBAAoB;AAAA,EACrF,qBAAqB,QAAQ;AAAA,EAG7B,MAAM,sBAAsB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAO5C;AAAA,EACD,IAAI,oBAAoB,OAAO;AAAA,IAC7B,oBAAoB,MAAM,QAAQ;AAAA,EACpC,EAAO;AAAA,IACL,oBAAoB,MAAM,QAAQ;AAAA;AAAA,EAIpC,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BpB,MAAM,gBAAgB,QAAQ,SAAS,WAAW;AAAA,EAClD,IAAI,cAAc,OAAO;AAAA,IACvB,MAAM,QAAQ,QAAQ,KAAK,cAAc,KAAK;AAAA,IAC9C,cAAc,MAAM,QAAQ;AAAA,IAC5B,MAAM,IAAI,MAAM,wCAAwC,KAAK,UAAU,KAAK,GAAG;AAAA,EACjF;AAAA,EACA,cAAc,MAAM,QAAQ;AAAA,EAG5B,MAAM,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "84DEDC530B56E65C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
package/dist/mjs/setup.mjs
CHANGED
|
@@ -71,7 +71,6 @@ function setupFs(context, options) {
|
|
|
71
71
|
throw new Error(`Failed to set up FS wrapper methods: ${JSON.stringify(error)}`);
|
|
72
72
|
}
|
|
73
73
|
wrapperResult.value.dispose();
|
|
74
|
-
const fsNamespace = context.newObject();
|
|
75
74
|
const getDirectoryFn = context.newFunction("getDirectory", (pathHandle) => {
|
|
76
75
|
const path = context.getString(pathHandle);
|
|
77
76
|
const deferred = context.newPromise();
|
|
@@ -98,9 +97,7 @@ function setupFs(context, options) {
|
|
|
98
97
|
return deferred.handle;
|
|
99
98
|
});
|
|
100
99
|
handles.push(getDirectoryFn);
|
|
101
|
-
context.setProp(
|
|
102
|
-
context.setProp(context.global, "fs", fsNamespace);
|
|
103
|
-
fsNamespace.dispose();
|
|
100
|
+
context.setProp(context.global, "getDirectory", getDirectoryFn);
|
|
104
101
|
return {
|
|
105
102
|
stateMap,
|
|
106
103
|
dispose() {
|
|
@@ -116,4 +113,4 @@ export {
|
|
|
116
113
|
setupFs
|
|
117
114
|
};
|
|
118
115
|
|
|
119
|
-
//# debugId=
|
|
116
|
+
//# debugId=1305651E74773D2064756E2164756E21
|
package/dist/mjs/setup.mjs.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/setup.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport {\n setupCore,\n createStateMap,\n marshal,\n} from \"@ricsam/quickjs-core\";\nimport type { SetupFsOptions, FsHandle } from \"./types.mjs\";\nimport { createFileSystemWritableFileStreamClass } from \"./handles/writable-stream.mjs\";\nimport { createFileSystemFileHandleClass } from \"./handles/file-handle.mjs\";\nimport { createFileSystemDirectoryHandleClass, registerHostDirectoryHandle } from \"./handles/directory-handle.mjs\";\n\n/**\n * Setup File System API in a QuickJS context\n *\n * Injects the following globals:\n * -
|
|
5
|
+
"import type { QuickJSContext, QuickJSHandle } from \"quickjs-emscripten\";\nimport {\n setupCore,\n createStateMap,\n marshal,\n} from \"@ricsam/quickjs-core\";\nimport type { SetupFsOptions, FsHandle } from \"./types.mjs\";\nimport { createFileSystemWritableFileStreamClass } from \"./handles/writable-stream.mjs\";\nimport { createFileSystemFileHandleClass } from \"./handles/file-handle.mjs\";\nimport { createFileSystemDirectoryHandleClass, registerHostDirectoryHandle } from \"./handles/directory-handle.mjs\";\n\n/**\n * Setup File System API in a QuickJS context\n *\n * Injects the following globals:\n * - getDirectory(path) - Server-compatible entry point\n * - FileSystemFileHandle\n * - FileSystemDirectoryHandle\n * - FileSystemWritableFileStream\n *\n * **Private globals (internal use):**\n * - `__FileSystemFileHandle__` - For creating file handle instances from wrapper code\n * - `__FileSystemDirectoryHandle__` - For creating directory handle instances\n * - `__FileSystemWritableFileStream__` - For creating writable stream instances\n *\n * These private globals follow the `__Name__` convention and are required for the\n * wrapper pattern: public methods call internal methods that return IDs, then use\n * evalCode with private constructors to create properly-typed class instances.\n * See PATTERNS.md sections 2 and 5 for details.\n *\n * **Wrapper pattern example:**\n * ```typescript\n * // Public method (added via evalCode)\n * FileSystemFileHandle.prototype.createWritable = async function(options) {\n * const result = await this._createWritableInternal(options);\n * return new __FileSystemWritableFileStream__(result.__writableStreamId);\n * };\n * ```\n *\n * @example\n * import { setupFs, createNodeDirectoryHandle } from \"@ricsam/quickjs-fs\";\n *\n * const handle = setupFs(context, {\n * getDirectory: async (path) => {\n * // Validate and resolve path\n * const resolvedPath = resolveSandboxPath(path);\n * return createNodeDirectoryHandle(resolvedPath);\n * }\n * });\n *\n * context.evalCode(`\n * const root = await getDirectory(\"/data\");\n * const configHandle = await root.getFileHandle(\"config.json\");\n * const file = await configHandle.getFile();\n * const text = await file.text();\n * console.log(JSON.parse(text));\n *\n * // Write a new file\n * const outputHandle = await root.getFileHandle(\"output.txt\", { create: true });\n * const writable = await outputHandle.createWritable();\n * await writable.write(\"Hello, World!\");\n * await writable.close();\n * `);\n */\nexport function setupFs(\n context: QuickJSContext,\n options: SetupFsOptions\n): FsHandle {\n // Setup core if not already done\n const coreHandle =\n options.coreHandle ??\n setupCore(context, {\n stateMap: options.stateMap,\n });\n\n const stateMap = options.stateMap ?? coreHandle.stateMap;\n const handles: QuickJSHandle[] = [];\n\n // Create FileSystemWritableFileStream class\n const WritableStreamClass = createFileSystemWritableFileStreamClass(\n context,\n stateMap\n );\n context.setProp(\n context.global,\n \"FileSystemWritableFileStream\",\n WritableStreamClass\n );\n context.setProp(context.global, \"__FileSystemWritableFileStream__\", WritableStreamClass);\n WritableStreamClass.dispose();\n\n // Create FileSystemFileHandle class\n const FileHandleClass = createFileSystemFileHandleClass(\n context,\n stateMap\n );\n context.setProp(context.global, \"FileSystemFileHandle\", FileHandleClass);\n context.setProp(context.global, \"__FileSystemFileHandle__\", FileHandleClass);\n FileHandleClass.dispose();\n\n // Create FileSystemDirectoryHandle class\n const DirectoryHandleClass = createFileSystemDirectoryHandleClass(\n context,\n stateMap\n );\n context.setProp(\n context.global,\n \"FileSystemDirectoryHandle\",\n DirectoryHandleClass\n );\n context.setProp(context.global, \"__FileSystemDirectoryHandle__\", DirectoryHandleClass);\n DirectoryHandleClass.dispose();\n\n // Add Symbol.asyncIterator support for FileSystemDirectoryHandle (for await...of)\n const asyncIteratorResult = context.evalCode(`\n FileSystemDirectoryHandle.prototype[Symbol.asyncIterator] = async function*() {\n const entries = await this.entries();\n for (const entry of entries) {\n yield entry;\n }\n };\n `);\n if (asyncIteratorResult.error) {\n asyncIteratorResult.error.dispose();\n } else {\n asyncIteratorResult.value.dispose();\n }\n\n // Add wrapper methods that convert IDs to class instances\n const wrapperCode = `\n // Wrapper for getFileHandle\n FileSystemDirectoryHandle.prototype.getFileHandle = async function(name, options) {\n const result = await this._getFileHandleInternal(name, options);\n return new __FileSystemFileHandle__(result.__fileHandleId);\n };\n\n // Wrapper for getDirectoryHandle\n FileSystemDirectoryHandle.prototype.getDirectoryHandle = async function(name, options) {\n const result = await this._getDirectoryHandleInternal(name, options);\n return new __FileSystemDirectoryHandle__(result.__directoryHandleId);\n };\n\n // Wrapper for createWritable\n FileSystemFileHandle.prototype.createWritable = async function(options) {\n const result = await this._createWritableInternal(options);\n return new __FileSystemWritableFileStream__(result.__writableStreamId);\n };\n\n // Wrapper for getFile - creates proper File instance\n FileSystemFileHandle.prototype.getFile = async function() {\n const result = await this._getFileInternal();\n const { buffer, type, name, lastModified } = result.__fileData;\n return new File([buffer], name, { type, lastModified });\n };\n `;\n const wrapperResult = context.evalCode(wrapperCode);\n if (wrapperResult.error) {\n const error = context.dump(wrapperResult.error);\n wrapperResult.error.dispose();\n throw new Error(`Failed to set up FS wrapper methods: ${JSON.stringify(error)}`);\n }\n wrapperResult.value.dispose();\n\n // Create getDirectory function that returns proper class instances\n const getDirectoryFn = context.newFunction(\"getDirectory\", (pathHandle) => {\n const path = context.getString(pathHandle);\n\n const deferred = context.newPromise();\n\n options\n .getDirectory(path)\n .then((hostHandle) => {\n // Register the host handle and get an ID\n const hostHandleId = registerHostDirectoryHandle(hostHandle);\n\n // Create instance using evalCode with the ID\n const instanceResult = context.evalCode(\n `new __FileSystemDirectoryHandle__(${hostHandleId})`\n );\n\n if (instanceResult.error) {\n deferred.reject(instanceResult.error);\n instanceResult.error.dispose();\n } else {\n deferred.resolve(instanceResult.value);\n instanceResult.value.dispose();\n }\n context.runtime.executePendingJobs();\n })\n .catch((error) => {\n const errorHandle = marshal(context, {\n name: error instanceof Error ? error.name : \"Error\",\n message: error instanceof Error ? error.message : String(error),\n });\n deferred.reject(errorHandle);\n errorHandle.dispose();\n context.runtime.executePendingJobs();\n });\n\n return deferred.handle;\n });\n handles.push(getDirectoryFn);\n context.setProp(context.global, \"getDirectory\", getDirectoryFn);\n\n return {\n stateMap,\n dispose() {\n for (const handle of handles) {\n if (handle.alive) {\n handle.dispose();\n }\n }\n },\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;AACA;AAAA;AAAA;AAAA;AAMA;AACA;AACA;AAuDO,SAAS,OAAO,CACrB,SACA,SACU;AAAA,EAEV,MAAM,aACJ,QAAQ,cACR,UAAU,SAAS;AAAA,IACjB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAAA,EAEH,MAAM,WAAW,QAAQ,YAAY,WAAW;AAAA,EAChD,MAAM,UAA2B,CAAC;AAAA,EAGlC,MAAM,sBAAsB,wCAC1B,SACA,QACF;AAAA,EACA,QAAQ,QACN,QAAQ,QACR,gCACA,mBACF;AAAA,EACA,QAAQ,QAAQ,QAAQ,QAAQ,oCAAoC,mBAAmB;AAAA,EACvF,oBAAoB,QAAQ;AAAA,EAG5B,MAAM,kBAAkB,gCACtB,SACA,QACF;AAAA,EACA,QAAQ,QAAQ,QAAQ,QAAQ,wBAAwB,eAAe;AAAA,EACvE,QAAQ,QAAQ,QAAQ,QAAQ,4BAA4B,eAAe;AAAA,EAC3E,gBAAgB,QAAQ;AAAA,EAGxB,MAAM,uBAAuB,qCAC3B,SACA,QACF;AAAA,EACA,QAAQ,QACN,QAAQ,QACR,6BACA,oBACF;AAAA,EACA,QAAQ,QAAQ,QAAQ,QAAQ,iCAAiC,oBAAoB;AAAA,EACrF,qBAAqB,QAAQ;AAAA,EAG7B,MAAM,sBAAsB,QAAQ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAO5C;AAAA,EACD,IAAI,oBAAoB,OAAO;AAAA,IAC7B,oBAAoB,MAAM,QAAQ;AAAA,EACpC,EAAO;AAAA,IACL,oBAAoB,MAAM,QAAQ;AAAA;AAAA,EAIpC,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BpB,MAAM,gBAAgB,QAAQ,SAAS,WAAW;AAAA,EAClD,IAAI,cAAc,OAAO;AAAA,IACvB,MAAM,QAAQ,QAAQ,KAAK,cAAc,KAAK;AAAA,IAC9C,cAAc,MAAM,QAAQ;AAAA,IAC5B,MAAM,IAAI,MAAM,wCAAwC,KAAK,UAAU,KAAK,GAAG;AAAA,EACjF;AAAA,EACA,cAAc,MAAM,QAAQ;AAAA,EAG5B,MAAM,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "1305651E74773D2064756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/types/quickjs.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* // Typecheck QuickJS code with file system access
|
|
9
|
-
* const root = await
|
|
9
|
+
* const root = await getDirectory("/data");
|
|
10
10
|
* const fileHandle = await root.getFileHandle("config.json");
|
|
11
11
|
* const file = await fileHandle.getFile();
|
|
12
12
|
* const content = await file.text();
|
|
@@ -16,30 +16,26 @@ export {};
|
|
|
16
16
|
|
|
17
17
|
declare global {
|
|
18
18
|
// ============================================
|
|
19
|
-
//
|
|
19
|
+
// getDirectory - QuickJS-specific entry point
|
|
20
20
|
// ============================================
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Get a directory handle for the given path.
|
|
24
|
+
*
|
|
25
|
+
* This is the QuickJS-specific entry point for file system access
|
|
26
|
+
* (differs from browser's navigator.storage.getDirectory()).
|
|
27
|
+
* The host controls which paths are accessible. Invalid or unauthorized
|
|
28
|
+
* paths will throw an error.
|
|
29
|
+
*
|
|
30
|
+
* @param path - The path to request from the host
|
|
31
|
+
* @returns A promise resolving to a directory handle
|
|
32
|
+
* @throws If the path is not allowed or doesn't exist
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const root = await getDirectory("/");
|
|
36
|
+
* const dataDir = await getDirectory("/data");
|
|
25
37
|
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Get a directory handle for the given path.
|
|
29
|
-
*
|
|
30
|
-
* The host controls which paths are accessible. Invalid or unauthorized
|
|
31
|
-
* paths will throw an error.
|
|
32
|
-
*
|
|
33
|
-
* @param path - The path to request from the host
|
|
34
|
-
* @returns A promise resolving to a directory handle
|
|
35
|
-
* @throws If the path is not allowed or doesn't exist
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* const root = await fs.getDirectory("/");
|
|
39
|
-
* const dataDir = await fs.getDirectory("/data");
|
|
40
|
-
*/
|
|
41
|
-
function getDirectory(path: string): Promise<FileSystemDirectoryHandle>;
|
|
42
|
-
}
|
|
38
|
+
function getDirectory(path: string): Promise<FileSystemDirectoryHandle>;
|
|
43
39
|
|
|
44
40
|
// ============================================
|
|
45
41
|
// File System Access API
|
package/dist/types/setup.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SetupFsOptions, FsHandle } from "./types.ts";
|
|
|
4
4
|
* Setup File System API in a QuickJS context
|
|
5
5
|
*
|
|
6
6
|
* Injects the following globals:
|
|
7
|
-
* -
|
|
7
|
+
* - getDirectory(path) - Server-compatible entry point
|
|
8
8
|
* - FileSystemFileHandle
|
|
9
9
|
* - FileSystemDirectoryHandle
|
|
10
10
|
* - FileSystemWritableFileStream
|
|
@@ -40,7 +40,7 @@ import type { SetupFsOptions, FsHandle } from "./types.ts";
|
|
|
40
40
|
* });
|
|
41
41
|
*
|
|
42
42
|
* context.evalCode(`
|
|
43
|
-
* const root = await
|
|
43
|
+
* const root = await getDirectory("/data");
|
|
44
44
|
* const configHandle = await root.getFileHandle("config.json");
|
|
45
45
|
* const file = await configHandle.getFile();
|
|
46
46
|
* const text = await file.text();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/quickjs-fs",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"main": "./dist/cjs/index.cjs",
|
|
5
5
|
"types": "./dist/types/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"typecheck": "tsc --noEmit"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@ricsam/quickjs-core": "^0.2.
|
|
22
|
+
"@ricsam/quickjs-core": "^0.2.17",
|
|
23
23
|
"quickjs-emscripten": "^0.31.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|