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.
- package/LICENSE +190 -0
- package/README.md +236 -73
- package/bin/space-data-module.js +24 -0
- package/package.json +16 -4
- package/schemas/ModuleBundle.fbs +108 -0
- package/schemas/PluginInvokeRequest.fbs +18 -0
- package/schemas/PluginInvokeResponse.fbs +30 -0
- package/schemas/PluginManifest.fbs +33 -1
- package/schemas/TypedArenaBuffer.fbs +23 -2
- package/src/bundle/codec.js +268 -0
- package/src/bundle/constants.js +8 -0
- package/src/bundle/index.js +3 -0
- package/src/bundle/wasm.js +447 -0
- package/src/compiler/compileModule.js +353 -37
- package/src/compiler/emceptionNode.js +217 -0
- package/src/compiler/flatcSupport.js +66 -0
- package/src/compiler/invokeGlue.js +884 -0
- package/src/compliance/pluginCompliance.js +575 -1
- package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts +51 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.js +131 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.js.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-request.ts +173 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts +76 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.js +184 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.js.map +1 -0
- package/src/generated/orbpro/invoke/plugin-invoke-response.ts +243 -0
- package/src/generated/orbpro/invoke.d.ts +3 -0
- package/src/generated/orbpro/invoke.d.ts.map +1 -0
- package/src/generated/orbpro/invoke.js +5 -0
- package/src/generated/orbpro/invoke.js.map +1 -0
- package/src/generated/orbpro/invoke.ts +6 -0
- package/src/generated/orbpro/manifest/accepted-type-set.d.ts +4 -4
- package/src/generated/orbpro/manifest/accepted-type-set.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/accepted-type-set.js +18 -11
- package/src/generated/orbpro/manifest/accepted-type-set.js.map +1 -1
- package/src/generated/orbpro/manifest/build-artifact.d.ts +1 -1
- package/src/generated/orbpro/manifest/build-artifact.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/build-artifact.js +28 -15
- package/src/generated/orbpro/manifest/build-artifact.js.map +1 -1
- package/src/generated/orbpro/manifest/capability-kind.d.ts +26 -1
- package/src/generated/orbpro/manifest/capability-kind.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/capability-kind.js +25 -0
- package/src/generated/orbpro/manifest/capability-kind.js.map +1 -1
- package/src/generated/orbpro/manifest/capability-kind.ts +25 -0
- package/src/generated/orbpro/manifest/drain-policy.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/drain-policy.js.map +1 -1
- package/src/generated/orbpro/manifest/host-capability.d.ts +2 -2
- package/src/generated/orbpro/manifest/host-capability.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/host-capability.js +19 -11
- package/src/generated/orbpro/manifest/host-capability.js.map +1 -1
- package/src/generated/orbpro/manifest/invoke-surface.d.ts +8 -0
- package/src/generated/orbpro/manifest/invoke-surface.d.ts.map +1 -0
- package/src/generated/orbpro/manifest/invoke-surface.js +11 -0
- package/src/generated/orbpro/manifest/invoke-surface.js.map +1 -0
- package/src/generated/orbpro/manifest/invoke-surface.ts +11 -0
- package/src/generated/orbpro/manifest/method-manifest.d.ts +6 -6
- package/src/generated/orbpro/manifest/method-manifest.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/method-manifest.js +33 -16
- package/src/generated/orbpro/manifest/method-manifest.js.map +1 -1
- package/src/generated/orbpro/manifest/plugin-family.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/plugin-family.js.map +1 -1
- package/src/generated/orbpro/manifest/plugin-manifest.d.ts +10 -2
- package/src/generated/orbpro/manifest/plugin-manifest.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/plugin-manifest.js +48 -9
- package/src/generated/orbpro/manifest/plugin-manifest.js.map +1 -1
- package/src/generated/orbpro/manifest/plugin-manifest.ts +322 -491
- package/src/generated/orbpro/manifest/port-manifest.d.ts +4 -4
- package/src/generated/orbpro/manifest/port-manifest.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/port-manifest.js +26 -13
- package/src/generated/orbpro/manifest/port-manifest.js.map +1 -1
- package/src/generated/orbpro/manifest/protocol-spec.d.ts +1 -1
- package/src/generated/orbpro/manifest/protocol-spec.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/protocol-spec.js +28 -15
- package/src/generated/orbpro/manifest/protocol-spec.js.map +1 -1
- package/src/generated/orbpro/manifest/timer-spec.d.ts +1 -1
- package/src/generated/orbpro/manifest/timer-spec.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/timer-spec.js +27 -16
- package/src/generated/orbpro/manifest/timer-spec.js.map +1 -1
- package/src/generated/orbpro/manifest.d.ts +13 -0
- package/src/generated/orbpro/manifest.d.ts.map +1 -0
- package/src/generated/orbpro/manifest.js +1 -0
- package/src/generated/orbpro/manifest.js.map +1 -0
- package/src/generated/orbpro/manifest.ts +16 -0
- package/src/generated/orbpro/module/canonicalization-rule.d.ts +48 -0
- package/src/generated/orbpro/module/canonicalization-rule.js +95 -0
- package/src/generated/orbpro/module/canonicalization-rule.ts +142 -0
- package/src/generated/orbpro/module/module-bundle-entry-role.d.ts +11 -0
- package/src/generated/orbpro/module/module-bundle-entry-role.js +14 -0
- package/src/generated/orbpro/module/module-bundle-entry-role.ts +15 -0
- package/src/generated/orbpro/module/module-bundle-entry.d.ts +97 -0
- package/src/generated/orbpro/module/module-bundle-entry.js +219 -0
- package/src/generated/orbpro/module/module-bundle-entry.ts +287 -0
- package/src/generated/orbpro/module/module-bundle.d.ts +86 -0
- package/src/generated/orbpro/module/module-bundle.js +213 -0
- package/src/generated/orbpro/module/module-bundle.ts +277 -0
- package/src/generated/orbpro/module/module-payload-encoding.d.ts +9 -0
- package/src/generated/orbpro/module/module-payload-encoding.js +12 -0
- package/src/generated/orbpro/module/module-payload-encoding.ts +13 -0
- package/src/generated/orbpro/module.d.ts +5 -0
- package/src/generated/orbpro/module.js +7 -0
- package/src/generated/orbpro/module.ts +9 -0
- package/src/generated/orbpro/stream/buffer-mutability.d.ts.map +1 -1
- package/src/generated/orbpro/stream/buffer-mutability.js.map +1 -1
- package/src/generated/orbpro/stream/buffer-ownership.d.ts.map +1 -1
- package/src/generated/orbpro/stream/buffer-ownership.js.map +1 -1
- package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts +22 -5
- package/src/generated/orbpro/stream/flat-buffer-type-ref.d.ts.map +1 -1
- package/src/generated/orbpro/stream/flat-buffer-type-ref.js +107 -17
- package/src/generated/orbpro/stream/flat-buffer-type-ref.js.map +1 -1
- package/src/generated/orbpro/stream/flat-buffer-type-ref.ts +126 -2
- package/src/generated/orbpro/stream/payload-wire-format.d.ts +8 -0
- package/src/generated/orbpro/stream/payload-wire-format.d.ts.map +1 -0
- package/src/generated/orbpro/stream/payload-wire-format.js +11 -0
- package/src/generated/orbpro/stream/payload-wire-format.js.map +1 -0
- package/src/generated/orbpro/stream/payload-wire-format.ts +11 -0
- package/src/generated/orbpro/stream/typed-arena-buffer.d.ts +4 -4
- package/src/generated/orbpro/stream/typed-arena-buffer.d.ts.map +1 -1
- package/src/generated/orbpro/stream/typed-arena-buffer.js +42 -24
- package/src/generated/orbpro/stream/typed-arena-buffer.js.map +1 -1
- package/src/host/abi.js +282 -0
- package/src/host/cron.js +247 -0
- package/src/host/index.js +3 -0
- package/src/host/nodeHost.js +2165 -0
- package/src/index.d.ts +958 -0
- package/src/index.js +12 -2
- package/src/invoke/codec.js +278 -0
- package/src/invoke/index.js +9 -0
- package/src/manifest/codec.js +10 -2
- package/src/manifest/index.js +5 -2
- package/src/manifest/normalize.js +90 -1
- package/src/runtime/constants.js +29 -0
- package/src/transport/pki.js +0 -5
- package/src/utils/encoding.js +9 -1
- 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 {
|
|
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
|
|
72
|
-
|
|
73
|
-
);
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
421
|
+
// Validate the compiled artifact
|
|
118
422
|
const report = await validateArtifactWithStandards({
|
|
119
423
|
manifest,
|
|
120
|
-
wasmPath:
|
|
424
|
+
wasmPath: resolvedOutputPath,
|
|
121
425
|
});
|
|
122
426
|
|
|
123
427
|
return {
|
|
124
|
-
compiler:
|
|
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
|
+
}
|