space-data-module-sdk 0.5.18 → 0.5.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 +27 -0
- package/package.json +6 -1
- package/src/browser.js +6 -0
- package/src/compiler/compileModule.js +3 -0
- package/src/compliance/pluginCompliance.js +0 -1
- 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 +101 -0
- package/src/host/wasiShim.js +275 -0
- package/src/index.d.ts +225 -0
- package/src/testing/browserModuleHarness.js +275 -0
- package/src/testing/index.d.ts +50 -0
- package/src/testing/index.js +4 -0
package/README.md
CHANGED
|
@@ -189,6 +189,11 @@ the preferred server-side target when the guest needs network-oriented runtime
|
|
|
189
189
|
features such as sockets or TLS. Plain `wasi` remains the strict portability
|
|
190
190
|
baseline; `wasmedge` is the practical higher-capability target.
|
|
191
191
|
|
|
192
|
+
If a manifest declares `runtimeTargets: ["browser", "wasmedge"]`, this SDK
|
|
193
|
+
treats that as the explicit "one binary for both" profile. That pair now
|
|
194
|
+
defaults to a shared `single-thread` artifact so the compiled wasm can be loaded
|
|
195
|
+
unchanged by the browser harness and the WasmEdge harness.
|
|
196
|
+
|
|
192
197
|
## WasmEdge Pthreads
|
|
193
198
|
|
|
194
199
|
`space-data-module-sdk` is also the source of truth for module thread-model
|
|
@@ -197,6 +202,7 @@ selection.
|
|
|
197
202
|
- `compileModuleFromSource({ threadModel })` accepts an explicit thread model.
|
|
198
203
|
- If `threadModel` is omitted, the SDK resolves it from `manifest.runtimeTargets`.
|
|
199
204
|
- `runtimeTargets: ["wasmedge"]` defaults to `emscripten-pthreads`.
|
|
205
|
+
- `runtimeTargets: ["browser", "wasmedge"]` defaults to `single-thread`.
|
|
200
206
|
- Other targets currently default to `single-thread`.
|
|
201
207
|
|
|
202
208
|
WasmEdge-targeted pthread builds do not use the embedded `sdn-emception`
|
|
@@ -214,11 +220,32 @@ If a runtime cannot interoperate with the guest pthread contract directly,
|
|
|
214
220
|
document that as a wrapper requirement instead of changing the guest artifact
|
|
215
221
|
semantics.
|
|
216
222
|
|
|
223
|
+
## Browser + WasmEdge Isomorphism
|
|
224
|
+
|
|
225
|
+
The supported isomorphic profile and browser edge shims are documented in
|
|
226
|
+
[`docs/browser-wasmedge-isomorphic.md`](./docs/browser-wasmedge-isomorphic.md).
|
|
227
|
+
|
|
228
|
+
The checked-in same-artifact demo lives in
|
|
229
|
+
[`examples/isomorphic-loader`](./examples/isomorphic-loader):
|
|
230
|
+
|
|
231
|
+
- [`build-demo.mjs`](./examples/isomorphic-loader/build-demo.mjs) compiles the
|
|
232
|
+
shared artifact
|
|
233
|
+
- [`browser-demo.html`](./examples/isomorphic-loader/browser-demo.html) and
|
|
234
|
+
[`browser-demo.mjs`](./examples/isomorphic-loader/browser-demo.mjs) load that
|
|
235
|
+
artifact in the browser harness with browser edge shims
|
|
236
|
+
- [`wasmedge-demo.mjs`](./examples/isomorphic-loader/wasmedge-demo.mjs) loads
|
|
237
|
+
that same artifact in WasmEdge
|
|
238
|
+
|
|
217
239
|
## Testing
|
|
218
240
|
|
|
219
241
|
This repo now exposes a manifest-driven harness generator from
|
|
220
242
|
`space-data-module-sdk/testing` and two complementary integration suites:
|
|
221
243
|
|
|
244
|
+
- browser/isomorphic helpers:
|
|
245
|
+
- `createBrowserModuleHarness(...)`
|
|
246
|
+
- `detectArtifactProfile(...)`
|
|
247
|
+
- `loadModule(...)`
|
|
248
|
+
|
|
222
249
|
- shared process-level helpers for command-surface runtimes:
|
|
223
250
|
- `createPluginInvokeProcessClient(...)`
|
|
224
251
|
- `resolveWasmEdgePluginLaunchPlan(...)`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
"types": "./src/index.d.ts",
|
|
36
36
|
"default": "./src/runtime-host/index.js"
|
|
37
37
|
},
|
|
38
|
+
"./host/browser": "./src/host/browserHost.js",
|
|
39
|
+
"./host/browser-edge-shims": "./src/host/browserEdgeShims.js",
|
|
40
|
+
"./host/wasi-shim": "./src/host/wasiShim.js",
|
|
41
|
+
"./host/isomorphic": "./src/host/isomorphicLoader.js",
|
|
42
|
+
"./testing/browser": "./src/testing/browserModuleHarness.js",
|
|
38
43
|
"./testing": "./src/testing/index.js",
|
|
39
44
|
"./standards": "./src/standards/index.js",
|
|
40
45
|
"./schemas/*": "./schemas/*"
|
package/src/browser.js
CHANGED
|
@@ -6,3 +6,9 @@ export * from "./capabilities.js";
|
|
|
6
6
|
export * from "./deployment/index.js";
|
|
7
7
|
export * from "./invoke/index.js";
|
|
8
8
|
export * from "./runtime/index.js";
|
|
9
|
+
export * from "./host/browserHost.js";
|
|
10
|
+
export * from "./host/browserEdgeShims.js";
|
|
11
|
+
export * from "./host/wasiShim.js";
|
|
12
|
+
export * from "./host/abi.js";
|
|
13
|
+
export * from "./host/isomorphicLoader.js";
|
|
14
|
+
export { createBrowserModuleHarness, detectArtifactProfile } from "./testing/browserModuleHarness.js";
|
|
@@ -142,6 +142,9 @@ function resolveThreadModel({ manifest, threadModel } = {}) {
|
|
|
142
142
|
.map((target) => String(target ?? "").trim().toLowerCase())
|
|
143
143
|
.filter(Boolean)
|
|
144
144
|
: [];
|
|
145
|
+
if (runtimeTargets.includes(RuntimeTarget.BROWSER)) {
|
|
146
|
+
return ModuleThreadModel.SINGLE_THREAD;
|
|
147
|
+
}
|
|
145
148
|
if (runtimeTargets.includes(RuntimeTarget.WASMEDGE)) {
|
|
146
149
|
return ModuleThreadModel.EMSCRIPTEN_PTHREADS;
|
|
147
150
|
}
|
|
@@ -30,7 +30,6 @@ const ExternalInterfaceKindSet = new Set(Object.values(ExternalInterfaceKind));
|
|
|
30
30
|
const ProtocolRoleSet = new Set(Object.values(ProtocolRole));
|
|
31
31
|
const ProtocolTransportKindSet = new Set(Object.values(ProtocolTransportKind));
|
|
32
32
|
const BrowserIncompatibleCapabilitySet = new Set([
|
|
33
|
-
"filesystem",
|
|
34
33
|
"pipe",
|
|
35
34
|
"network",
|
|
36
35
|
"tcp",
|
|
@@ -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
|
+
}
|