space-data-module-sdk 0.1.0 → 0.2.5

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.
Files changed (136) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +236 -73
  3. package/bin/space-data-module.js +24 -0
  4. package/package.json +16 -4
  5. package/schemas/ModuleBundle.fbs +108 -0
  6. package/schemas/PluginInvokeRequest.fbs +18 -0
  7. package/schemas/PluginInvokeResponse.fbs +30 -0
  8. package/schemas/PluginManifest.fbs +33 -1
  9. package/schemas/TypedArenaBuffer.fbs +23 -2
  10. package/src/bundle/codec.js +268 -0
  11. package/src/bundle/constants.js +8 -0
  12. package/src/bundle/index.js +3 -0
  13. package/src/bundle/wasm.js +447 -0
  14. package/src/compiler/compileModule.js +353 -37
  15. package/src/compiler/emceptionNode.js +217 -0
  16. package/src/compiler/flatcSupport.js +66 -0
  17. package/src/compiler/invokeGlue.js +884 -0
  18. package/src/compliance/pluginCompliance.js +575 -1
  19. package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts +51 -0
  20. package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts.map +1 -0
  21. package/src/generated/orbpro/invoke/plugin-invoke-request.js +131 -0
  22. package/src/generated/orbpro/invoke/plugin-invoke-request.js.map +1 -0
  23. package/src/generated/orbpro/invoke/plugin-invoke-request.ts +173 -0
  24. package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts +76 -0
  25. package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts.map +1 -0
  26. package/src/generated/orbpro/invoke/plugin-invoke-response.js +184 -0
  27. package/src/generated/orbpro/invoke/plugin-invoke-response.js.map +1 -0
  28. package/src/generated/orbpro/invoke/plugin-invoke-response.ts +243 -0
  29. package/src/generated/orbpro/invoke.d.ts +3 -0
  30. package/src/generated/orbpro/invoke.d.ts.map +1 -0
  31. package/src/generated/orbpro/invoke.js +5 -0
  32. package/src/generated/orbpro/invoke.js.map +1 -0
  33. package/src/generated/orbpro/invoke.ts +6 -0
  34. package/src/generated/orbpro/manifest/accepted-type-set.d.ts +4 -4
  35. package/src/generated/orbpro/manifest/accepted-type-set.d.ts.map +1 -1
  36. package/src/generated/orbpro/manifest/accepted-type-set.js +18 -11
  37. package/src/generated/orbpro/manifest/accepted-type-set.js.map +1 -1
  38. package/src/generated/orbpro/manifest/build-artifact.d.ts +1 -1
  39. package/src/generated/orbpro/manifest/build-artifact.d.ts.map +1 -1
  40. package/src/generated/orbpro/manifest/build-artifact.js +28 -15
  41. package/src/generated/orbpro/manifest/build-artifact.js.map +1 -1
  42. package/src/generated/orbpro/manifest/capability-kind.d.ts +26 -1
  43. package/src/generated/orbpro/manifest/capability-kind.d.ts.map +1 -1
  44. package/src/generated/orbpro/manifest/capability-kind.js +25 -0
  45. package/src/generated/orbpro/manifest/capability-kind.js.map +1 -1
  46. package/src/generated/orbpro/manifest/capability-kind.ts +25 -0
  47. package/src/generated/orbpro/manifest/drain-policy.d.ts.map +1 -1
  48. package/src/generated/orbpro/manifest/drain-policy.js.map +1 -1
  49. package/src/generated/orbpro/manifest/host-capability.d.ts +2 -2
  50. package/src/generated/orbpro/manifest/host-capability.d.ts.map +1 -1
  51. package/src/generated/orbpro/manifest/host-capability.js +19 -11
  52. package/src/generated/orbpro/manifest/host-capability.js.map +1 -1
  53. package/src/generated/orbpro/manifest/invoke-surface.d.ts +8 -0
  54. package/src/generated/orbpro/manifest/invoke-surface.d.ts.map +1 -0
  55. package/src/generated/orbpro/manifest/invoke-surface.js +11 -0
  56. package/src/generated/orbpro/manifest/invoke-surface.js.map +1 -0
  57. package/src/generated/orbpro/manifest/invoke-surface.ts +11 -0
  58. package/src/generated/orbpro/manifest/method-manifest.d.ts +6 -6
  59. package/src/generated/orbpro/manifest/method-manifest.d.ts.map +1 -1
  60. package/src/generated/orbpro/manifest/method-manifest.js +33 -16
  61. package/src/generated/orbpro/manifest/method-manifest.js.map +1 -1
  62. package/src/generated/orbpro/manifest/plugin-family.d.ts.map +1 -1
  63. package/src/generated/orbpro/manifest/plugin-family.js.map +1 -1
  64. package/src/generated/orbpro/manifest/plugin-manifest.d.ts +10 -2
  65. package/src/generated/orbpro/manifest/plugin-manifest.d.ts.map +1 -1
  66. package/src/generated/orbpro/manifest/plugin-manifest.js +48 -9
  67. package/src/generated/orbpro/manifest/plugin-manifest.js.map +1 -1
  68. package/src/generated/orbpro/manifest/plugin-manifest.ts +322 -491
  69. package/src/generated/orbpro/manifest/port-manifest.d.ts +4 -4
  70. package/src/generated/orbpro/manifest/port-manifest.d.ts.map +1 -1
  71. package/src/generated/orbpro/manifest/port-manifest.js +26 -13
  72. package/src/generated/orbpro/manifest/port-manifest.js.map +1 -1
  73. package/src/generated/orbpro/manifest/protocol-spec.d.ts +1 -1
  74. package/src/generated/orbpro/manifest/protocol-spec.d.ts.map +1 -1
  75. package/src/generated/orbpro/manifest/protocol-spec.js +28 -15
  76. package/src/generated/orbpro/manifest/protocol-spec.js.map +1 -1
  77. package/src/generated/orbpro/manifest/timer-spec.d.ts +1 -1
  78. package/src/generated/orbpro/manifest/timer-spec.d.ts.map +1 -1
  79. package/src/generated/orbpro/manifest/timer-spec.js +27 -16
  80. package/src/generated/orbpro/manifest/timer-spec.js.map +1 -1
  81. package/src/generated/orbpro/manifest.d.ts +13 -0
  82. package/src/generated/orbpro/manifest.d.ts.map +1 -0
  83. package/src/generated/orbpro/manifest.js +1 -0
  84. package/src/generated/orbpro/manifest.js.map +1 -0
  85. package/src/generated/orbpro/manifest.ts +16 -0
  86. package/src/generated/orbpro/module/canonicalization-rule.d.ts +48 -0
  87. package/src/generated/orbpro/module/canonicalization-rule.js +95 -0
  88. package/src/generated/orbpro/module/canonicalization-rule.ts +142 -0
  89. package/src/generated/orbpro/module/module-bundle-entry-role.d.ts +11 -0
  90. package/src/generated/orbpro/module/module-bundle-entry-role.js +14 -0
  91. package/src/generated/orbpro/module/module-bundle-entry-role.ts +15 -0
  92. package/src/generated/orbpro/module/module-bundle-entry.d.ts +97 -0
  93. package/src/generated/orbpro/module/module-bundle-entry.js +219 -0
  94. package/src/generated/orbpro/module/module-bundle-entry.ts +287 -0
  95. package/src/generated/orbpro/module/module-bundle.d.ts +86 -0
  96. package/src/generated/orbpro/module/module-bundle.js +213 -0
  97. package/src/generated/orbpro/module/module-bundle.ts +277 -0
  98. package/src/generated/orbpro/module/module-payload-encoding.d.ts +9 -0
  99. package/src/generated/orbpro/module/module-payload-encoding.js +12 -0
  100. package/src/generated/orbpro/module/module-payload-encoding.ts +13 -0
  101. package/src/generated/orbpro/module.d.ts +5 -0
  102. package/src/generated/orbpro/module.js +7 -0
  103. package/src/generated/orbpro/module.ts +9 -0
  104. package/src/generated/orbpro/stream/buffer-mutability.d.ts.map +1 -1
  105. package/src/generated/orbpro/stream/buffer-mutability.js.map +1 -1
  106. package/src/generated/orbpro/stream/buffer-ownership.d.ts.map +1 -1
  107. package/src/generated/orbpro/stream/buffer-ownership.js.map +1 -1
  108. package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts +22 -5
  109. package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts.map +1 -1
  110. package/src/generated/orbpro/stream/flat-buffer-type-ref.js +107 -17
  111. package/src/generated/orbpro/stream/flat-buffer-type-ref.js.map +1 -1
  112. package/src/generated/orbpro/stream/flat-buffer-type-ref.ts +126 -2
  113. package/src/generated/orbpro/stream/payload-wire-format.d.ts +8 -0
  114. package/src/generated/orbpro/stream/payload-wire-format.d.ts.map +1 -0
  115. package/src/generated/orbpro/stream/payload-wire-format.js +11 -0
  116. package/src/generated/orbpro/stream/payload-wire-format.js.map +1 -0
  117. package/src/generated/orbpro/stream/payload-wire-format.ts +11 -0
  118. package/src/generated/orbpro/stream/typed-arena-buffer.d.ts +4 -4
  119. package/src/generated/orbpro/stream/typed-arena-buffer.d.ts.map +1 -1
  120. package/src/generated/orbpro/stream/typed-arena-buffer.js +42 -24
  121. package/src/generated/orbpro/stream/typed-arena-buffer.js.map +1 -1
  122. package/src/host/abi.js +282 -0
  123. package/src/host/cron.js +247 -0
  124. package/src/host/index.js +3 -0
  125. package/src/host/nodeHost.js +2165 -0
  126. package/src/index.d.ts +958 -0
  127. package/src/index.js +12 -2
  128. package/src/invoke/codec.js +278 -0
  129. package/src/invoke/index.js +9 -0
  130. package/src/manifest/codec.js +10 -2
  131. package/src/manifest/index.js +5 -2
  132. package/src/manifest/normalize.js +90 -1
  133. package/src/runtime/constants.js +29 -0
  134. package/src/transport/pki.js +0 -5
  135. package/src/utils/encoding.js +9 -1
  136. package/src/utils/wasmCrypto.js +49 -1
@@ -1,6 +1,12 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import {
4
+ mkdir,
5
+ mkdtemp,
6
+ readFile,
7
+ rm,
8
+ writeFile,
9
+ } from "node:fs/promises";
4
10
  import { execFile as execFileCallback } from "node:child_process";
5
11
  import { promisify } from "node:util";
6
12
 
@@ -11,11 +17,23 @@ import {
11
17
  } from "../auth/index.js";
12
18
  import { validateArtifactWithStandards } from "../compliance/index.js";
13
19
  import { generateEmbeddedManifestSource } from "../embeddedManifest.js";
20
+ import {
21
+ generateInvokeSupportHeader,
22
+ generateInvokeSupportSource,
23
+ resolveInvokeSurfaces,
24
+ } from "./invokeGlue.js";
25
+ import {
26
+ getFlatbuffersCppRuntimeHeaders,
27
+ getInvokeCppSchemaHeaders,
28
+ } from "./flatcSupport.js";
29
+ import { runWithEmceptionLock } from "./emceptionNode.js";
14
30
  import { encodePluginManifest, toEmbeddedPluginManifest } from "../manifest/index.js";
31
+ import { DefaultInvokeExports, InvokeSurface } from "../runtime/constants.js";
15
32
  import {
16
33
  encryptJsonForRecipient,
17
34
  generateX25519Keypair,
18
35
  } from "../transport/index.js";
36
+ import { createSingleFileBundle } from "../bundle/index.js";
19
37
  import {
20
38
  base64ToBytes,
21
39
  bytesToBase64,
@@ -48,6 +66,270 @@ function ensureExportableMethodIds(manifest) {
48
66
  }
49
67
  }
50
68
 
69
+ function buildCompilerArgs(exportedSymbols, options = {}) {
70
+ const linkerExports = exportedSymbols.map(
71
+ (symbol) => "-Wl,--export=" + symbol,
72
+ );
73
+ const extraArgs = [];
74
+ if (options.allowUndefinedImports === true) {
75
+ extraArgs.push("-s", "ERROR_ON_UNDEFINED_SYMBOLS=0", "-Wl,--allow-undefined");
76
+ }
77
+ const args = ["-O2", "-s", "STANDALONE_WASM=1", ...extraArgs, ...linkerExports];
78
+ if (options.noEntry === true) {
79
+ args.splice(1, 0, "--no-entry");
80
+ }
81
+ return args;
82
+ }
83
+
84
+ async function getInvokeCppSupportFiles() {
85
+ const [runtimeHeaders, schemaHeaders] = await Promise.all([
86
+ getFlatbuffersCppRuntimeHeaders(),
87
+ getInvokeCppSchemaHeaders(),
88
+ ]);
89
+ return { runtimeHeaders, schemaHeaders };
90
+ }
91
+
92
+ async function writeFilesToDirectory(rootDir, files) {
93
+ for (const [relativePath, content] of Object.entries(files)) {
94
+ const filePath = path.join(rootDir, relativePath);
95
+ await mkdir(path.dirname(filePath), { recursive: true });
96
+ await writeFile(filePath, content, "utf8");
97
+ }
98
+ }
99
+
100
+ async function writeFilesToEmception(emception, rootDir, files) {
101
+ for (const [relativePath, content] of Object.entries(files)) {
102
+ const filePath = path.posix.join(rootDir, relativePath);
103
+ emception.FS.mkdirTree(path.posix.dirname(filePath));
104
+ emception.writeFile(filePath, content);
105
+ }
106
+ }
107
+
108
+ function removeEmceptionDirectory(emception, directoryPath) {
109
+ if (!emception.FS.analyzePath(directoryPath).exists) {
110
+ return;
111
+ }
112
+ const entries = emception.FS.readdir(directoryPath).filter(
113
+ (entry) => entry !== "." && entry !== "..",
114
+ );
115
+ for (const entry of entries) {
116
+ const entryPath = path.posix.join(directoryPath, entry);
117
+ const stat = emception.FS.stat(entryPath);
118
+ if (emception.FS.isDir(stat.mode)) {
119
+ removeEmceptionDirectory(emception, entryPath);
120
+ emception.FS.rmdir(entryPath);
121
+ } else {
122
+ emception.FS.unlink(entryPath);
123
+ }
124
+ }
125
+ emception.FS.rmdir(directoryPath);
126
+ }
127
+
128
+ async function compileWithEmception(options = {}) {
129
+ const {
130
+ sourceCompilerCommand,
131
+ sourceExtension,
132
+ sourceCode,
133
+ manifestSource,
134
+ invokeHeaderSource,
135
+ invokeSource,
136
+ exportedSymbols,
137
+ outputPath,
138
+ compileOptions,
139
+ } = options;
140
+ const tempDir = await mkdtemp(
141
+ path.join(os.tmpdir(), "space-data-module-sdk-compile-"),
142
+ );
143
+ const resolvedOutputPath = path.resolve(
144
+ outputPath ?? path.join(tempDir, "module.wasm"),
145
+ );
146
+
147
+ try {
148
+ return await runWithEmceptionLock(async (emception) => {
149
+ const workDir = "/working/space-data-module-sdk-compile";
150
+ const runtimeIncludeDir = path.posix.join(workDir, "flatbuffers-runtime");
151
+ const sourcePath = path.posix.join(workDir, `module.${sourceExtension}`);
152
+ const manifestSourcePath = path.posix.join(workDir, "plugin-manifest-exports.cpp");
153
+ const invokeHeaderPath = path.posix.join(workDir, "space_data_module_invoke.h");
154
+ const invokeSourcePath = path.posix.join(workDir, "plugin-invoke-bridge.cpp");
155
+ const sourceObjectPath = path.posix.join(workDir, "module.o");
156
+ const manifestObjectPath = path.posix.join(workDir, "plugin-manifest-exports.o");
157
+ const invokeObjectPath = path.posix.join(workDir, "plugin-invoke-bridge.o");
158
+ const wasmOutputPath = path.posix.join(workDir, "module.wasm");
159
+
160
+ const { runtimeHeaders, schemaHeaders } = await getInvokeCppSupportFiles();
161
+ const args = buildCompilerArgs(exportedSymbols, compileOptions);
162
+
163
+ try {
164
+ emception.FS.mkdirTree(workDir);
165
+ await writeFilesToEmception(emception, runtimeIncludeDir, runtimeHeaders);
166
+ await writeFilesToEmception(emception, workDir, schemaHeaders);
167
+ emception.writeFile(sourcePath, sourceCode);
168
+ emception.writeFile(manifestSourcePath, manifestSource);
169
+ emception.writeFile(invokeHeaderPath, invokeHeaderSource);
170
+ emception.writeFile(invokeSourcePath, invokeSource);
171
+
172
+ const commands = [
173
+ [
174
+ sourceCompilerCommand,
175
+ "-c",
176
+ sourcePath,
177
+ `-I${workDir}`,
178
+ "-o",
179
+ sourceObjectPath,
180
+ ],
181
+ [
182
+ "em++",
183
+ "-c",
184
+ manifestSourcePath,
185
+ "-std=c++17",
186
+ `-I${workDir}`,
187
+ `-I${runtimeIncludeDir}`,
188
+ "-o",
189
+ manifestObjectPath,
190
+ ],
191
+ [
192
+ "em++",
193
+ "-c",
194
+ invokeSourcePath,
195
+ "-std=c++17",
196
+ `-I${workDir}`,
197
+ `-I${runtimeIncludeDir}`,
198
+ "-o",
199
+ invokeObjectPath,
200
+ ],
201
+ [
202
+ "em++",
203
+ sourceObjectPath,
204
+ manifestObjectPath,
205
+ invokeObjectPath,
206
+ ...args,
207
+ "-o",
208
+ wasmOutputPath,
209
+ ],
210
+ ];
211
+
212
+ for (const command of commands) {
213
+ const result = emception.run(command.join(" "));
214
+ if (result.returncode !== 0) {
215
+ throw new Error(
216
+ `Compilation failed with ${command[0]} (emception): ${result.stderr || result.stdout}`,
217
+ );
218
+ }
219
+ }
220
+
221
+ const wasmBytes = new Uint8Array(emception.readFile(wasmOutputPath));
222
+ await writeFile(resolvedOutputPath, wasmBytes);
223
+ return { wasmBytes, outputPath: resolvedOutputPath, tempDir };
224
+ } finally {
225
+ try {
226
+ removeEmceptionDirectory(emception, workDir);
227
+ } catch {
228
+ // Best-effort cleanup only; the shared emception instance remains usable.
229
+ }
230
+ }
231
+ });
232
+ } catch (error) {
233
+ await rm(tempDir, { recursive: true, force: true });
234
+ throw error;
235
+ }
236
+ }
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // System Emscripten — fallback to emcc/em++ on PATH
240
+ // ---------------------------------------------------------------------------
241
+
242
+ async function compileWithSystemToolchain(options = {}) {
243
+ const {
244
+ compilerCommand,
245
+ sourceCompilerCommand,
246
+ sourceExtension,
247
+ sourceCode,
248
+ manifestSource,
249
+ invokeHeaderSource,
250
+ invokeSource,
251
+ exportedSymbols,
252
+ outputPath,
253
+ compileOptions,
254
+ } = options;
255
+ const tempDir = await mkdtemp(
256
+ path.join(os.tmpdir(), "space-data-module-sdk-compile-"),
257
+ );
258
+ const sourcePath = path.join(tempDir, `module.${sourceExtension}`);
259
+ const manifestSourcePath = path.join(tempDir, "plugin-manifest-exports.cpp");
260
+ const invokeHeaderPath = path.join(tempDir, "space_data_module_invoke.h");
261
+ const invokeSourcePath = path.join(tempDir, "plugin-invoke-bridge.cpp");
262
+ const sourceObjectPath = path.join(tempDir, "module.o");
263
+ const manifestObjectPath = path.join(tempDir, "plugin-manifest-exports.o");
264
+ const invokeObjectPath = path.join(tempDir, "plugin-invoke-bridge.o");
265
+ const resolvedOutputPath = path.resolve(
266
+ outputPath ?? path.join(tempDir, "module.wasm"),
267
+ );
268
+ const runtimeIncludeDir = path.join(tempDir, "flatbuffers-runtime");
269
+
270
+ const { runtimeHeaders, schemaHeaders } = await getInvokeCppSupportFiles();
271
+
272
+ await writeFile(sourcePath, sourceCode, "utf8");
273
+ await writeFile(manifestSourcePath, manifestSource, "utf8");
274
+ await writeFile(invokeHeaderPath, invokeHeaderSource, "utf8");
275
+ await writeFile(invokeSourcePath, invokeSource, "utf8");
276
+ await writeFilesToDirectory(tempDir, schemaHeaders);
277
+ await writeFilesToDirectory(runtimeIncludeDir, runtimeHeaders);
278
+
279
+ const args = buildCompilerArgs(exportedSymbols, compileOptions);
280
+
281
+ try {
282
+ await execFile(sourceCompilerCommand, [
283
+ "-c",
284
+ sourcePath,
285
+ `-I${tempDir}`,
286
+ "-o",
287
+ sourceObjectPath,
288
+ ], { timeout: 120_000 });
289
+
290
+ await execFile(compilerCommand, [
291
+ "-c",
292
+ manifestSourcePath,
293
+ "-std=c++17",
294
+ `-I${tempDir}`,
295
+ `-I${runtimeIncludeDir}`,
296
+ "-o",
297
+ manifestObjectPath,
298
+ ], { timeout: 120_000 });
299
+
300
+ await execFile(compilerCommand, [
301
+ "-c",
302
+ invokeSourcePath,
303
+ "-std=c++17",
304
+ `-I${tempDir}`,
305
+ `-I${runtimeIncludeDir}`,
306
+ "-o",
307
+ invokeObjectPath,
308
+ ], { timeout: 120_000 });
309
+
310
+ await execFile(compilerCommand, [
311
+ sourceObjectPath,
312
+ manifestObjectPath,
313
+ invokeObjectPath,
314
+ ...args,
315
+ "-o",
316
+ resolvedOutputPath,
317
+ ], { timeout: 120_000 });
318
+ } catch (error) {
319
+ error.message =
320
+ `Compilation failed with ${compilerCommand}: ` +
321
+ (error.stderr || error.message);
322
+ throw error;
323
+ }
324
+
325
+ const wasmBytes = await readFile(resolvedOutputPath);
326
+ return { wasmBytes, outputPath: resolvedOutputPath, tempDir };
327
+ }
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // Public API
331
+ // ---------------------------------------------------------------------------
332
+
51
333
  export async function compileModuleFromSource(options = {}) {
52
334
  const manifest = options.manifest ?? {};
53
335
  const sourceCode = String(options.sourceCode ?? "");
@@ -65,65 +347,87 @@ export async function compileModuleFromSource(options = {}) {
65
347
  }
66
348
 
67
349
  const compiler = selectCompiler(options.language);
350
+ const invokeSurfaces = resolveInvokeSurfaces(manifest);
351
+ const includeCommandMain = invokeSurfaces.includes(InvokeSurface.COMMAND);
68
352
  const { manifest: embeddedManifest, warnings } = toEmbeddedPluginManifest(
69
353
  manifest,
70
354
  );
71
- const tempDir = await mkdtemp(
72
- path.join(os.tmpdir(), "space-data-module-sdk-compile-"),
73
- );
74
- const sourcePath = path.join(tempDir, `module.${compiler.extension}`);
75
- const manifestSourcePath = path.join(tempDir, "plugin-manifest-exports.c");
76
- const outputPath = path.resolve(options.outputPath ?? path.join(tempDir, "module.wasm"));
77
-
78
- await writeFile(sourcePath, sourceCode, "utf8");
79
- await writeFile(
80
- manifestSourcePath,
81
- generateEmbeddedManifestSource({ manifest: embeddedManifest }),
82
- "utf8",
83
- );
355
+ const manifestSource = generateEmbeddedManifestSource({
356
+ manifest: embeddedManifest,
357
+ });
358
+ const invokeHeaderSource = generateInvokeSupportHeader();
359
+ const invokeSource = generateInvokeSupportSource({
360
+ manifest,
361
+ includeCommandMain,
362
+ });
84
363
 
85
364
  const exportedSymbols = [
86
365
  "plugin_get_manifest_flatbuffer",
87
366
  "plugin_get_manifest_flatbuffer_size",
367
+ DefaultInvokeExports.invokeSymbol,
368
+ DefaultInvokeExports.allocSymbol,
369
+ DefaultInvokeExports.freeSymbol,
370
+ ...(includeCommandMain ? [DefaultInvokeExports.commandSymbol] : []),
88
371
  ...new Set(
89
372
  (Array.isArray(manifest.methods) ? manifest.methods : [])
90
373
  .map((method) => String(method?.methodId ?? "").trim())
91
374
  .filter(Boolean),
92
375
  ),
93
376
  ];
94
- const linkerExports = exportedSymbols.flatMap((symbol) => [
95
- "-Wl,--export=" + symbol,
96
- ]);
97
377
 
378
+ let wasmBytes;
379
+ let resolvedOutputPath = null;
380
+ let tempDir = null;
381
+ const compileOptions = {
382
+ ...options,
383
+ noEntry: includeCommandMain !== true,
384
+ };
385
+ let compilerBackend = "em++ (emception)";
386
+ let result;
98
387
  try {
99
- await execFile(compiler.command, [
100
- sourcePath,
101
- manifestSourcePath,
102
- "-O2",
103
- "--no-entry",
104
- "-s",
105
- "STANDALONE_WASM=1",
106
- ...linkerExports,
107
- "-o",
108
- outputPath,
109
- ]);
388
+ result = await compileWithEmception({
389
+ sourceCompilerCommand: compiler.command,
390
+ sourceExtension: compiler.extension,
391
+ sourceCode,
392
+ manifestSource,
393
+ invokeHeaderSource,
394
+ invokeSource,
395
+ exportedSymbols,
396
+ outputPath: options.outputPath,
397
+ compileOptions,
398
+ });
110
399
  } catch (error) {
111
- error.message =
112
- `Compilation failed with ${compiler.command}: ` +
113
- (error.stderr || error.message);
114
- throw error;
400
+ if (error?.code !== "EMCEPTION_LOAD_FAILED") {
401
+ throw error;
402
+ }
403
+ result = await compileWithSystemToolchain({
404
+ compilerCommand: "em++",
405
+ sourceCompilerCommand: compiler.command,
406
+ sourceExtension: compiler.extension,
407
+ sourceCode,
408
+ manifestSource,
409
+ invokeHeaderSource,
410
+ invokeSource,
411
+ exportedSymbols,
412
+ outputPath: options.outputPath,
413
+ compileOptions,
414
+ });
415
+ compilerBackend = "em++ (system)";
115
416
  }
417
+ wasmBytes = result.wasmBytes;
418
+ resolvedOutputPath = result.outputPath;
419
+ tempDir = result.tempDir;
116
420
 
117
- const wasmBytes = await readFile(outputPath);
421
+ // Validate the compiled artifact
118
422
  const report = await validateArtifactWithStandards({
119
423
  manifest,
120
- wasmPath: outputPath,
424
+ wasmPath: resolvedOutputPath,
121
425
  });
122
426
 
123
427
  return {
124
- compiler: compiler.command,
428
+ compiler: compilerBackend,
125
429
  language: compiler.language,
126
- outputPath,
430
+ outputPath: resolvedOutputPath,
127
431
  tempDir,
128
432
  wasmBytes,
129
433
  manifestWarnings: warnings,
@@ -217,6 +521,17 @@ export async function protectModuleArtifact(options = {}) {
217
521
  });
218
522
  }
219
523
 
524
+ let singleFileBundle = null;
525
+ if (options.singleFileBundle === true) {
526
+ singleFileBundle = await createSingleFileBundle({
527
+ wasmBytes,
528
+ manifest,
529
+ authorization: signedAuthorization,
530
+ transportEnvelope: encryptedEnvelope,
531
+ entries: options.bundleEntries,
532
+ });
533
+ }
534
+
220
535
  return {
221
536
  mnemonic: identity.mnemonic,
222
537
  signingPublicKeyHex: bytesToHex(identity.signingKey.publicKey),
@@ -224,6 +539,8 @@ export async function protectModuleArtifact(options = {}) {
224
539
  payload,
225
540
  encrypted: Boolean(encryptedEnvelope),
226
541
  encryptedEnvelope,
542
+ singleFileBundle,
543
+ bundledWasmBytes: singleFileBundle?.wasmBytes ?? null,
227
544
  };
228
545
  }
229
546
 
@@ -234,4 +551,3 @@ export async function createRecipientKeypairHex() {
234
551
  privateKeyHex: bytesToHex(keypair.privateKey),
235
552
  };
236
553
  }
237
-
@@ -0,0 +1,217 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { cp, readFile, readdir, rm, writeFile } from "node:fs/promises";
4
+ import { readFileSync } from "node:fs";
5
+ import { createRequire } from "node:module";
6
+ import { pathToFileURL } from "node:url";
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const PATCH_VERSION = "space-data-module-sdk-emception-node-v1";
10
+ const PATCH_MARKER_FILENAME = ".space-data-module-sdk-emception-patch";
11
+ const EMCEPTION_PATCH_ROOT = path.join(
12
+ os.tmpdir(),
13
+ `space-data-module-sdk-emception-node-${process.pid}`,
14
+ );
15
+ const FILE_URL_FETCH_PATCH_FLAG =
16
+ "__spaceDataModuleSdkFileUrlFetchPatched";
17
+
18
+ let patchedEmceptionRootPromise = null;
19
+ let emceptionInstancePromise = null;
20
+ let emceptionExecutionQueue = Promise.resolve();
21
+
22
+ function installNodeRuntimeShims() {
23
+ if (typeof globalThis.require !== "function") {
24
+ globalThis.require = require;
25
+ }
26
+
27
+ if (!globalThis.XMLHttpRequest) {
28
+ class FileUrlXMLHttpRequest {
29
+ open(method, url, async = true) {
30
+ this.method = method;
31
+ this.url = url;
32
+ this.async = async;
33
+ this.status = 0;
34
+ this.response = null;
35
+ }
36
+
37
+ overrideMimeType() {}
38
+
39
+ send() {
40
+ if (this.async) {
41
+ throw new Error("Async file XMLHttpRequest is not supported.");
42
+ }
43
+ const target =
44
+ typeof this.url === "string" && this.url.startsWith("file:")
45
+ ? new URL(this.url)
46
+ : this.url;
47
+ const data = readFileSync(target);
48
+ this.status = 200;
49
+ this.response = data.buffer.slice(
50
+ data.byteOffset,
51
+ data.byteOffset + data.byteLength,
52
+ );
53
+ }
54
+ }
55
+
56
+ globalThis.XMLHttpRequest = FileUrlXMLHttpRequest;
57
+ }
58
+
59
+ if (!globalThis[FILE_URL_FETCH_PATCH_FLAG]) {
60
+ const originalFetch = globalThis.fetch;
61
+ globalThis.fetch = async (input, init) => {
62
+ const url =
63
+ typeof input === "string"
64
+ ? input
65
+ : input?.url ?? String(input);
66
+ if (!url.startsWith("file:")) {
67
+ if (typeof originalFetch !== "function") {
68
+ throw new Error("fetch is not available for non-file URLs.");
69
+ }
70
+ return originalFetch(input, init);
71
+ }
72
+ const bytes = await readFile(new URL(url));
73
+ return {
74
+ ok: true,
75
+ status: 200,
76
+ url,
77
+ async arrayBuffer() {
78
+ return bytes.buffer.slice(
79
+ bytes.byteOffset,
80
+ bytes.byteOffset + bytes.byteLength,
81
+ );
82
+ },
83
+ };
84
+ };
85
+ globalThis[FILE_URL_FETCH_PATCH_FLAG] = true;
86
+ }
87
+ }
88
+
89
+ async function patchEmceptionModuleTree(rootDir) {
90
+ const entries = await readdir(rootDir, { withFileTypes: true });
91
+ for (const entry of entries) {
92
+ const fullPath = path.join(rootDir, entry.name);
93
+ if (entry.isDirectory()) {
94
+ await patchEmceptionModuleTree(fullPath);
95
+ continue;
96
+ }
97
+ if (!entry.name.endsWith(".mjs")) {
98
+ continue;
99
+ }
100
+
101
+ let source = await readFile(fullPath, "utf8");
102
+ source = source.replaceAll(
103
+ 'scriptDirectory=__dirname+"/"',
104
+ 'scriptDirectory=(new URL(".", import.meta.url)).pathname',
105
+ );
106
+
107
+ if (entry.name === "emception.mjs") {
108
+ source = source.replace(
109
+ "this.#fs = await new FileSystem();",
110
+ [
111
+ "this.#fs = await new FileSystem({",
112
+ " locateFile: (file, scriptDirectory) => scriptDirectory + file,",
113
+ ' cache: "/tmp/emception-cache",',
114
+ " });",
115
+ ].join("\n"),
116
+ );
117
+ source = source.replace(
118
+ "const config = {",
119
+ [
120
+ "const config = {",
121
+ " locateFile: (file, scriptDirectory) => scriptDirectory + file,",
122
+ ].join("\n"),
123
+ );
124
+ }
125
+
126
+ if (entry.name === "FileSystem.mjs") {
127
+ source = source.replace(
128
+ [
129
+ " if (!this.exists(cache)) {",
130
+ " this.persist(cache);",
131
+ " }",
132
+ " await this.pull();",
133
+ ].join("\n"),
134
+ [
135
+ ' if (typeof indexedDB !== "undefined") {',
136
+ " if (!this.exists(cache)) {",
137
+ " this.persist(cache);",
138
+ " }",
139
+ " await this.pull();",
140
+ " }",
141
+ ].join("\n"),
142
+ );
143
+ }
144
+
145
+ await writeFile(fullPath, source, "utf8");
146
+ }
147
+ }
148
+
149
+ async function preparePatchedEmceptionRoot() {
150
+ if (!patchedEmceptionRootPromise) {
151
+ patchedEmceptionRootPromise = (async () => {
152
+ const sourceRoot = path.dirname(require.resolve("sdn-emception"));
153
+ const markerPath = path.join(EMCEPTION_PATCH_ROOT, PATCH_MARKER_FILENAME);
154
+ let marker = null;
155
+ try {
156
+ marker = await readFile(markerPath, "utf8");
157
+ } catch {
158
+ marker = null;
159
+ }
160
+
161
+ if (marker?.trim() !== PATCH_VERSION) {
162
+ await rm(EMCEPTION_PATCH_ROOT, { recursive: true, force: true });
163
+ await cp(sourceRoot, EMCEPTION_PATCH_ROOT, { recursive: true });
164
+ await patchEmceptionModuleTree(EMCEPTION_PATCH_ROOT);
165
+ await writeFile(markerPath, `${PATCH_VERSION}\n`, "utf8");
166
+ }
167
+
168
+ return EMCEPTION_PATCH_ROOT;
169
+ })().catch((error) => {
170
+ patchedEmceptionRootPromise = null;
171
+ throw error;
172
+ });
173
+ }
174
+
175
+ return patchedEmceptionRootPromise;
176
+ }
177
+
178
+ export async function loadEmception() {
179
+ if (!emceptionInstancePromise) {
180
+ emceptionInstancePromise = (async () => {
181
+ installNodeRuntimeShims();
182
+ const patchedRoot = await preparePatchedEmceptionRoot();
183
+ const moduleUrl = pathToFileURL(path.join(patchedRoot, "emception.mjs")).href;
184
+ const { default: Emception } = await import(moduleUrl);
185
+ const emception = new Emception({
186
+ baseUrl: pathToFileURL(`${patchedRoot}${path.sep}`).href,
187
+ });
188
+ await emception.init();
189
+ return emception;
190
+ })().catch((error) => {
191
+ emceptionInstancePromise = null;
192
+ throw error;
193
+ });
194
+ }
195
+
196
+ return emceptionInstancePromise;
197
+ }
198
+
199
+ export async function runWithEmceptionLock(task) {
200
+ const previous = emceptionExecutionQueue;
201
+ let release = () => {};
202
+ emceptionExecutionQueue = new Promise((resolve) => {
203
+ release = resolve;
204
+ });
205
+ await previous.catch(() => {});
206
+ try {
207
+ const emception = await loadEmception().catch((error) => {
208
+ if (!error.code) {
209
+ error.code = "EMCEPTION_LOAD_FAILED";
210
+ }
211
+ throw error;
212
+ });
213
+ return await task(emception);
214
+ } finally {
215
+ release();
216
+ }
217
+ }