space-data-module-sdk 0.5.18 → 0.5.21
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 +79 -7
- package/package.json +10 -1
- package/src/AGENTS.md +33 -0
- package/src/auth/AGENTS.md +25 -0
- package/src/browser.js +8 -0
- package/src/bundle/AGENTS.md +21 -0
- package/src/compiler/AGENTS.md +35 -0
- package/src/compiler/compileModule.js +3 -0
- package/src/compliance/AGENTS.md +18 -0
- package/src/compliance/pluginCompliance.js +0 -1
- package/src/host/AGENTS.md +39 -0
- package/src/host/browserEdgeShims.js +355 -0
- package/src/host/browserHost.js +399 -0
- package/src/host/index.js +4 -0
- package/src/host/isomorphicLoader.js +234 -0
- package/src/host/wasiShim.js +286 -0
- package/src/index.d.ts +325 -0
- package/src/manifest/AGENTS.md +18 -0
- package/src/runtime-host/AGENTS.md +32 -0
- package/src/runtime-host/flatbufferStreamIngestor.js +181 -0
- package/src/runtime-host/flatsqlRuntimeStore.js +120 -49
- package/src/runtime-host/index.js +2 -0
- package/src/testing/AGENTS.md +33 -0
- package/src/testing/browserModuleHarness.js +275 -0
- package/src/testing/index.d.ts +50 -0
- package/src/testing/index.js +5 -0
- package/src/testing/moduleFlatbufferStreamPump.js +255 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
const textEncoder = new TextEncoder();
|
|
2
|
+
|
|
3
|
+
const FILE_ENTRY = "file";
|
|
4
|
+
const DIRECTORY_ENTRY = "directory";
|
|
5
|
+
|
|
6
|
+
export class BrowserFilesystemScopeError extends Error {
|
|
7
|
+
constructor(requestedPath, filesystemRoot) {
|
|
8
|
+
super(`Path "${requestedPath}" escapes the configured filesystem root.`);
|
|
9
|
+
this.name = "BrowserFilesystemScopeError";
|
|
10
|
+
this.code = "filesystem-scope-violation";
|
|
11
|
+
this.requestedPath = requestedPath;
|
|
12
|
+
this.filesystemRoot = filesystemRoot;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizePath(path) {
|
|
17
|
+
const raw = String(path ?? "").trim();
|
|
18
|
+
const absolute = raw.startsWith("/") ? raw : `/${raw}`;
|
|
19
|
+
const segments = [];
|
|
20
|
+
for (const segment of absolute.split("/")) {
|
|
21
|
+
if (!segment || segment === ".") {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (segment === "..") {
|
|
25
|
+
if (segments.length === 0) {
|
|
26
|
+
throw new BrowserFilesystemScopeError(path, "/");
|
|
27
|
+
}
|
|
28
|
+
segments.pop();
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
segments.push(segment);
|
|
32
|
+
}
|
|
33
|
+
return segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function joinPaths(basePath, requestedPath) {
|
|
37
|
+
const base = normalizePath(basePath ?? "/");
|
|
38
|
+
const requested = String(requestedPath ?? "").trim();
|
|
39
|
+
if (!requested || requested === ".") {
|
|
40
|
+
return base;
|
|
41
|
+
}
|
|
42
|
+
if (requested.startsWith("/")) {
|
|
43
|
+
return normalizePath(requested);
|
|
44
|
+
}
|
|
45
|
+
return normalizePath(`${base}/${requested}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assertWithinRoot(resolvedPath, filesystemRoot, requestedPath) {
|
|
49
|
+
const root = normalizePath(filesystemRoot ?? "/");
|
|
50
|
+
if (
|
|
51
|
+
resolvedPath !== root &&
|
|
52
|
+
root !== "/" &&
|
|
53
|
+
!resolvedPath.startsWith(`${root}/`)
|
|
54
|
+
) {
|
|
55
|
+
throw new BrowserFilesystemScopeError(requestedPath, root);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getParentPath(path) {
|
|
60
|
+
if (path === "/") {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const segments = path.split("/").filter(Boolean);
|
|
64
|
+
if (segments.length <= 1) {
|
|
65
|
+
return "/";
|
|
66
|
+
}
|
|
67
|
+
return `/${segments.slice(0, -1).join("/")}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getBaseName(path) {
|
|
71
|
+
if (path === "/") {
|
|
72
|
+
return "/";
|
|
73
|
+
}
|
|
74
|
+
const segments = path.split("/").filter(Boolean);
|
|
75
|
+
return segments[segments.length - 1];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function cloneEntry(entry) {
|
|
79
|
+
if (!entry) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (entry.kind === FILE_ENTRY) {
|
|
83
|
+
return {
|
|
84
|
+
...entry,
|
|
85
|
+
bytes: new Uint8Array(entry.bytes),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { ...entry };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function toUint8Array(value, encoding = null) {
|
|
92
|
+
if (typeof value === "string") {
|
|
93
|
+
return new TextEncoder().encode(value);
|
|
94
|
+
}
|
|
95
|
+
if (value instanceof Uint8Array) {
|
|
96
|
+
return new Uint8Array(value);
|
|
97
|
+
}
|
|
98
|
+
if (ArrayBuffer.isView(value)) {
|
|
99
|
+
return new Uint8Array(
|
|
100
|
+
value.buffer,
|
|
101
|
+
value.byteOffset,
|
|
102
|
+
value.byteLength,
|
|
103
|
+
).slice();
|
|
104
|
+
}
|
|
105
|
+
if (value instanceof ArrayBuffer) {
|
|
106
|
+
return new Uint8Array(value.slice(0));
|
|
107
|
+
}
|
|
108
|
+
if (value == null) {
|
|
109
|
+
return new Uint8Array();
|
|
110
|
+
}
|
|
111
|
+
if (encoding) {
|
|
112
|
+
return textEncoder.encode(String(value));
|
|
113
|
+
}
|
|
114
|
+
return textEncoder.encode(String(value));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function createMemoryFilesystemEdgeShim(options = {}) {
|
|
118
|
+
const filesystemRoot = normalizePath(options.filesystemRoot ?? "/");
|
|
119
|
+
const entries = new Map();
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
entries.set(filesystemRoot, {
|
|
122
|
+
kind: DIRECTORY_ENTRY,
|
|
123
|
+
ctimeMs: now,
|
|
124
|
+
mtimeMs: now,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
function resolvePath(requestedPath = ".") {
|
|
128
|
+
const resolvedPath = joinPaths(filesystemRoot, requestedPath);
|
|
129
|
+
assertWithinRoot(resolvedPath, filesystemRoot, requestedPath);
|
|
130
|
+
return resolvedPath;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getEntry(path) {
|
|
134
|
+
return entries.get(path) ?? null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function requireEntry(path) {
|
|
138
|
+
const entry = getEntry(path);
|
|
139
|
+
if (!entry) {
|
|
140
|
+
throw new Error(`Path "${path}" does not exist.`);
|
|
141
|
+
}
|
|
142
|
+
return entry;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function requireDirectory(path) {
|
|
146
|
+
const entry = requireEntry(path);
|
|
147
|
+
if (entry.kind !== DIRECTORY_ENTRY) {
|
|
148
|
+
throw new Error(`Path "${path}" is not a directory.`);
|
|
149
|
+
}
|
|
150
|
+
return entry;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function requireParentDirectory(path) {
|
|
154
|
+
const parentPath = getParentPath(path);
|
|
155
|
+
if (!parentPath) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return requireDirectory(parentPath);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function touch(path) {
|
|
162
|
+
const entry = requireEntry(path);
|
|
163
|
+
entry.mtimeMs = Date.now();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function ensureDirectory(path, recursive = false) {
|
|
167
|
+
const resolvedPath = resolvePath(path);
|
|
168
|
+
const existing = getEntry(resolvedPath);
|
|
169
|
+
if (existing) {
|
|
170
|
+
if (existing.kind !== DIRECTORY_ENTRY) {
|
|
171
|
+
throw new Error(`Path "${resolvedPath}" already exists and is not a directory.`);
|
|
172
|
+
}
|
|
173
|
+
return resolvedPath;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const parentPath = getParentPath(resolvedPath);
|
|
177
|
+
if (parentPath && !getEntry(parentPath)) {
|
|
178
|
+
if (!recursive) {
|
|
179
|
+
throw new Error(`Parent directory "${parentPath}" does not exist.`);
|
|
180
|
+
}
|
|
181
|
+
ensureDirectory(parentPath, true);
|
|
182
|
+
}
|
|
183
|
+
requireParentDirectory(resolvedPath);
|
|
184
|
+
const nowMs = Date.now();
|
|
185
|
+
entries.set(resolvedPath, {
|
|
186
|
+
kind: DIRECTORY_ENTRY,
|
|
187
|
+
ctimeMs: nowMs,
|
|
188
|
+
mtimeMs: nowMs,
|
|
189
|
+
});
|
|
190
|
+
if (parentPath) {
|
|
191
|
+
touch(parentPath);
|
|
192
|
+
}
|
|
193
|
+
return resolvedPath;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function setFileBytes(resolvedPath, bytes) {
|
|
197
|
+
const parentPath = getParentPath(resolvedPath);
|
|
198
|
+
if (parentPath) {
|
|
199
|
+
requireDirectory(parentPath);
|
|
200
|
+
}
|
|
201
|
+
const existing = getEntry(resolvedPath);
|
|
202
|
+
const nowMs = Date.now();
|
|
203
|
+
entries.set(resolvedPath, {
|
|
204
|
+
kind: FILE_ENTRY,
|
|
205
|
+
bytes,
|
|
206
|
+
ctimeMs: existing?.ctimeMs ?? nowMs,
|
|
207
|
+
mtimeMs: nowMs,
|
|
208
|
+
});
|
|
209
|
+
if (parentPath) {
|
|
210
|
+
touch(parentPath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return Object.freeze({
|
|
215
|
+
filesystemRoot,
|
|
216
|
+
resolvePath,
|
|
217
|
+
async readFile(path, options = {}) {
|
|
218
|
+
const resolvedPath = resolvePath(path);
|
|
219
|
+
const entry = requireEntry(resolvedPath);
|
|
220
|
+
if (entry.kind !== FILE_ENTRY) {
|
|
221
|
+
throw new Error(`Path "${resolvedPath}" is not a file.`);
|
|
222
|
+
}
|
|
223
|
+
if (options.encoding) {
|
|
224
|
+
return new TextDecoder(options.encoding).decode(entry.bytes);
|
|
225
|
+
}
|
|
226
|
+
return new Uint8Array(entry.bytes);
|
|
227
|
+
},
|
|
228
|
+
async writeFile(path, value, options = {}) {
|
|
229
|
+
const resolvedPath = resolvePath(path);
|
|
230
|
+
setFileBytes(resolvedPath, toUint8Array(value, options.encoding ?? null));
|
|
231
|
+
return { path: resolvedPath };
|
|
232
|
+
},
|
|
233
|
+
async appendFile(path, value, options = {}) {
|
|
234
|
+
const resolvedPath = resolvePath(path);
|
|
235
|
+
const existing = getEntry(resolvedPath);
|
|
236
|
+
const nextBytes = toUint8Array(value, options.encoding ?? null);
|
|
237
|
+
if (!existing) {
|
|
238
|
+
setFileBytes(resolvedPath, nextBytes);
|
|
239
|
+
return { path: resolvedPath };
|
|
240
|
+
}
|
|
241
|
+
if (existing.kind !== FILE_ENTRY) {
|
|
242
|
+
throw new Error(`Path "${resolvedPath}" is not a file.`);
|
|
243
|
+
}
|
|
244
|
+
const combined = new Uint8Array(existing.bytes.length + nextBytes.length);
|
|
245
|
+
combined.set(existing.bytes, 0);
|
|
246
|
+
combined.set(nextBytes, existing.bytes.length);
|
|
247
|
+
setFileBytes(resolvedPath, combined);
|
|
248
|
+
return { path: resolvedPath };
|
|
249
|
+
},
|
|
250
|
+
async deleteFile(path) {
|
|
251
|
+
const resolvedPath = resolvePath(path);
|
|
252
|
+
const entry = requireEntry(resolvedPath);
|
|
253
|
+
if (entry.kind !== FILE_ENTRY) {
|
|
254
|
+
throw new Error(`Path "${resolvedPath}" is not a file.`);
|
|
255
|
+
}
|
|
256
|
+
entries.delete(resolvedPath);
|
|
257
|
+
const parentPath = getParentPath(resolvedPath);
|
|
258
|
+
if (parentPath) {
|
|
259
|
+
touch(parentPath);
|
|
260
|
+
}
|
|
261
|
+
return { path: resolvedPath };
|
|
262
|
+
},
|
|
263
|
+
async mkdir(path, options = {}) {
|
|
264
|
+
const resolvedPath = ensureDirectory(path, options.recursive === true);
|
|
265
|
+
return { path: resolvedPath };
|
|
266
|
+
},
|
|
267
|
+
async readdir(path = ".") {
|
|
268
|
+
const resolvedPath = resolvePath(path);
|
|
269
|
+
requireDirectory(resolvedPath);
|
|
270
|
+
const children = [];
|
|
271
|
+
for (const [entryPath, entry] of entries.entries()) {
|
|
272
|
+
if (entryPath === resolvedPath) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const parentPath = getParentPath(entryPath);
|
|
276
|
+
if (parentPath !== resolvedPath) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
children.push({
|
|
280
|
+
name: getBaseName(entryPath),
|
|
281
|
+
isFile: entry.kind === FILE_ENTRY,
|
|
282
|
+
isDirectory: entry.kind === DIRECTORY_ENTRY,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
286
|
+
return children;
|
|
287
|
+
},
|
|
288
|
+
async stat(path) {
|
|
289
|
+
const resolvedPath = resolvePath(path);
|
|
290
|
+
const entry = requireEntry(resolvedPath);
|
|
291
|
+
return {
|
|
292
|
+
path: resolvedPath,
|
|
293
|
+
size: entry.kind === FILE_ENTRY ? entry.bytes.length : 0,
|
|
294
|
+
isFile: entry.kind === FILE_ENTRY,
|
|
295
|
+
isDirectory: entry.kind === DIRECTORY_ENTRY,
|
|
296
|
+
ctimeMs: entry.ctimeMs,
|
|
297
|
+
mtimeMs: entry.mtimeMs,
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
async rename(fromPath, toPath) {
|
|
301
|
+
const resolvedFromPath = resolvePath(fromPath);
|
|
302
|
+
const resolvedToPath = resolvePath(toPath);
|
|
303
|
+
requireEntry(resolvedFromPath);
|
|
304
|
+
if (getEntry(resolvedToPath)) {
|
|
305
|
+
throw new Error(`Path "${resolvedToPath}" already exists.`);
|
|
306
|
+
}
|
|
307
|
+
const targetParentPath = getParentPath(resolvedToPath);
|
|
308
|
+
if (targetParentPath) {
|
|
309
|
+
requireDirectory(targetParentPath);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const moves = Array.from(entries.entries())
|
|
313
|
+
.filter(([entryPath]) =>
|
|
314
|
+
entryPath === resolvedFromPath ||
|
|
315
|
+
entryPath.startsWith(`${resolvedFromPath}/`),
|
|
316
|
+
)
|
|
317
|
+
.sort((left, right) => left[0].length - right[0].length);
|
|
318
|
+
|
|
319
|
+
for (const [entryPath, entry] of moves) {
|
|
320
|
+
const suffix = entryPath.slice(resolvedFromPath.length);
|
|
321
|
+
entries.set(`${resolvedToPath}${suffix}`, cloneEntry(entry));
|
|
322
|
+
}
|
|
323
|
+
for (const [entryPath] of moves) {
|
|
324
|
+
entries.delete(entryPath);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (targetParentPath) {
|
|
328
|
+
touch(targetParentPath);
|
|
329
|
+
}
|
|
330
|
+
const fromParentPath = getParentPath(resolvedFromPath);
|
|
331
|
+
if (fromParentPath && fromParentPath !== targetParentPath) {
|
|
332
|
+
touch(fromParentPath);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
from: resolvedFromPath,
|
|
337
|
+
to: resolvedToPath,
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function createBrowserEdgeShims(options = {}) {
|
|
344
|
+
return Object.freeze({
|
|
345
|
+
fetch: options.fetch ?? globalThis.fetch?.bind(globalThis),
|
|
346
|
+
WebSocket: options.WebSocket ?? globalThis.WebSocket,
|
|
347
|
+
crypto: options.crypto ?? globalThis.crypto,
|
|
348
|
+
performance: options.performance ?? globalThis.performance,
|
|
349
|
+
filesystem:
|
|
350
|
+
options.filesystem ??
|
|
351
|
+
createMemoryFilesystemEdgeShim({
|
|
352
|
+
filesystemRoot: options.filesystemRoot ?? "/",
|
|
353
|
+
}),
|
|
354
|
+
});
|
|
355
|
+
}
|