space-data-module-sdk 0.2.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/README.md +74 -2
- package/package.json +10 -3
- package/schemas/PluginInvokeRequest.fbs +18 -0
- package/schemas/PluginInvokeResponse.fbs +30 -0
- package/schemas/PluginManifest.fbs +7 -0
- package/schemas/TypedArenaBuffer.fbs +23 -2
- package/src/bundle/codec.js +24 -0
- package/src/compiler/compileModule.js +274 -106
- 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 +241 -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 +1 -1
- package/src/generated/orbpro/manifest/capability-kind.d.ts.map +1 -1
- package/src/generated/orbpro/manifest/capability-kind.js +1 -1
- package/src/generated/orbpro/manifest/capability-kind.js.map +1 -1
- 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/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/index.d.ts +83 -5
- package/src/index.js +3 -0
- 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 +58 -0
- package/src/runtime/constants.js +12 -0
|
@@ -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,7 +17,18 @@ 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,
|
|
@@ -49,7 +66,7 @@ function ensureExportableMethodIds(manifest) {
|
|
|
49
66
|
}
|
|
50
67
|
}
|
|
51
68
|
|
|
52
|
-
function buildCompilerArgs(
|
|
69
|
+
function buildCompilerArgs(exportedSymbols, options = {}) {
|
|
53
70
|
const linkerExports = exportedSymbols.map(
|
|
54
71
|
(symbol) => "-Wl,--export=" + symbol,
|
|
55
72
|
);
|
|
@@ -57,113 +74,250 @@ function buildCompilerArgs(compiler, exportedSymbols, options = {}) {
|
|
|
57
74
|
if (options.allowUndefinedImports === true) {
|
|
58
75
|
extraArgs.push("-s", "ERROR_ON_UNDEFINED_SYMBOLS=0", "-Wl,--allow-undefined");
|
|
59
76
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"--no-entry"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
...extraArgs,
|
|
66
|
-
...linkerExports,
|
|
67
|
-
];
|
|
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;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
async function getInvokeCppSupportFiles() {
|
|
85
|
+
const [runtimeHeaders, schemaHeaders] = await Promise.all([
|
|
86
|
+
getFlatbuffersCppRuntimeHeaders(),
|
|
87
|
+
getInvokeCppSchemaHeaders(),
|
|
88
|
+
]);
|
|
89
|
+
return { runtimeHeaders, schemaHeaders };
|
|
90
|
+
}
|
|
73
91
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
}
|
|
76
99
|
|
|
77
|
-
async function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"sdn-emception"
|
|
83
|
-
);
|
|
84
|
-
emceptionInstance = new Emception();
|
|
85
|
-
await emceptionInstance.init();
|
|
86
|
-
return emceptionInstance;
|
|
87
|
-
} catch {
|
|
88
|
-
// emception not available or init failed — fall back to system emcc
|
|
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);
|
|
89
105
|
}
|
|
90
|
-
return null;
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
emception
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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,
|
|
116
137
|
outputPath,
|
|
117
|
-
|
|
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
|
+
);
|
|
118
146
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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;
|
|
124
235
|
}
|
|
125
|
-
|
|
126
|
-
const wasmBytes = emception.readFile(outputPath);
|
|
127
|
-
return new Uint8Array(wasmBytes);
|
|
128
236
|
}
|
|
129
237
|
|
|
130
238
|
// ---------------------------------------------------------------------------
|
|
131
239
|
// System Emscripten — fallback to emcc/em++ on PATH
|
|
132
240
|
// ---------------------------------------------------------------------------
|
|
133
241
|
|
|
134
|
-
async function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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;
|
|
142
255
|
const tempDir = await mkdtemp(
|
|
143
256
|
path.join(os.tmpdir(), "space-data-module-sdk-compile-"),
|
|
144
257
|
);
|
|
145
|
-
const sourcePath = path.join(tempDir, `module.${
|
|
146
|
-
const manifestSourcePath = path.join(tempDir, "plugin-manifest-exports.
|
|
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");
|
|
147
265
|
const resolvedOutputPath = path.resolve(
|
|
148
266
|
outputPath ?? path.join(tempDir, "module.wasm"),
|
|
149
267
|
);
|
|
268
|
+
const runtimeIncludeDir = path.join(tempDir, "flatbuffers-runtime");
|
|
269
|
+
|
|
270
|
+
const { runtimeHeaders, schemaHeaders } = await getInvokeCppSupportFiles();
|
|
150
271
|
|
|
151
272
|
await writeFile(sourcePath, sourceCode, "utf8");
|
|
152
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);
|
|
153
278
|
|
|
154
|
-
const args = buildCompilerArgs(
|
|
279
|
+
const args = buildCompilerArgs(exportedSymbols, compileOptions);
|
|
155
280
|
|
|
156
281
|
try {
|
|
157
|
-
await execFile(
|
|
282
|
+
await execFile(sourceCompilerCommand, [
|
|
283
|
+
"-c",
|
|
158
284
|
sourcePath,
|
|
285
|
+
`-I${tempDir}`,
|
|
286
|
+
"-o",
|
|
287
|
+
sourceObjectPath,
|
|
288
|
+
], { timeout: 120_000 });
|
|
289
|
+
|
|
290
|
+
await execFile(compilerCommand, [
|
|
291
|
+
"-c",
|
|
159
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,
|
|
160
314
|
...args,
|
|
161
315
|
"-o",
|
|
162
316
|
resolvedOutputPath,
|
|
163
317
|
], { timeout: 120_000 });
|
|
164
318
|
} catch (error) {
|
|
165
319
|
error.message =
|
|
166
|
-
`Compilation failed with ${
|
|
320
|
+
`Compilation failed with ${compilerCommand}: ` +
|
|
167
321
|
(error.stderr || error.message);
|
|
168
322
|
throw error;
|
|
169
323
|
}
|
|
@@ -193,16 +347,27 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
193
347
|
}
|
|
194
348
|
|
|
195
349
|
const compiler = selectCompiler(options.language);
|
|
350
|
+
const invokeSurfaces = resolveInvokeSurfaces(manifest);
|
|
351
|
+
const includeCommandMain = invokeSurfaces.includes(InvokeSurface.COMMAND);
|
|
196
352
|
const { manifest: embeddedManifest, warnings } = toEmbeddedPluginManifest(
|
|
197
353
|
manifest,
|
|
198
354
|
);
|
|
199
355
|
const manifestSource = generateEmbeddedManifestSource({
|
|
200
356
|
manifest: embeddedManifest,
|
|
201
357
|
});
|
|
358
|
+
const invokeHeaderSource = generateInvokeSupportHeader();
|
|
359
|
+
const invokeSource = generateInvokeSupportSource({
|
|
360
|
+
manifest,
|
|
361
|
+
includeCommandMain,
|
|
362
|
+
});
|
|
202
363
|
|
|
203
364
|
const exportedSymbols = [
|
|
204
365
|
"plugin_get_manifest_flatbuffer",
|
|
205
366
|
"plugin_get_manifest_flatbuffer_size",
|
|
367
|
+
DefaultInvokeExports.invokeSymbol,
|
|
368
|
+
DefaultInvokeExports.allocSymbol,
|
|
369
|
+
DefaultInvokeExports.freeSymbol,
|
|
370
|
+
...(includeCommandMain ? [DefaultInvokeExports.commandSymbol] : []),
|
|
206
371
|
...new Set(
|
|
207
372
|
(Array.isArray(manifest.methods) ? manifest.methods : [])
|
|
208
373
|
.map((method) => String(method?.methodId ?? "").trim())
|
|
@@ -210,51 +375,54 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
210
375
|
),
|
|
211
376
|
];
|
|
212
377
|
|
|
213
|
-
// Try emception first, fall back to system emcc/em++
|
|
214
|
-
const emception = await loadEmception();
|
|
215
|
-
|
|
216
378
|
let wasmBytes;
|
|
217
379
|
let resolvedOutputPath = null;
|
|
218
380
|
let tempDir = null;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
381
|
+
const compileOptions = {
|
|
382
|
+
...options,
|
|
383
|
+
noEntry: includeCommandMain !== true,
|
|
384
|
+
};
|
|
385
|
+
let compilerBackend = "em++ (emception)";
|
|
386
|
+
let result;
|
|
387
|
+
try {
|
|
388
|
+
result = await compileWithEmception({
|
|
389
|
+
sourceCompilerCommand: compiler.command,
|
|
390
|
+
sourceExtension: compiler.extension,
|
|
225
391
|
sourceCode,
|
|
226
392
|
manifestSource,
|
|
393
|
+
invokeHeaderSource,
|
|
394
|
+
invokeSource,
|
|
227
395
|
exportedSymbols,
|
|
228
|
-
options,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
396
|
+
outputPath: options.outputPath,
|
|
397
|
+
compileOptions,
|
|
398
|
+
});
|
|
399
|
+
} catch (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,
|
|
234
407
|
sourceCode,
|
|
235
408
|
manifestSource,
|
|
409
|
+
invokeHeaderSource,
|
|
410
|
+
invokeSource,
|
|
236
411
|
exportedSymbols,
|
|
237
|
-
options.outputPath,
|
|
238
|
-
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
resolvedOutputPath = result.outputPath;
|
|
242
|
-
tempDir = result.tempDir;
|
|
243
|
-
compilerBackend = `${compiler.command} (system)`;
|
|
412
|
+
outputPath: options.outputPath,
|
|
413
|
+
compileOptions,
|
|
414
|
+
});
|
|
415
|
+
compilerBackend = "em++ (system)";
|
|
244
416
|
}
|
|
417
|
+
wasmBytes = result.wasmBytes;
|
|
418
|
+
resolvedOutputPath = result.outputPath;
|
|
419
|
+
tempDir = result.tempDir;
|
|
245
420
|
|
|
246
421
|
// Validate the compiled artifact
|
|
247
|
-
const report =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
new WebAssembly.Module(wasmBytes),
|
|
252
|
-
).map((e) => e.name),
|
|
253
|
-
})
|
|
254
|
-
: await validateArtifactWithStandards({
|
|
255
|
-
manifest,
|
|
256
|
-
wasmPath: resolvedOutputPath,
|
|
257
|
-
});
|
|
422
|
+
const report = await validateArtifactWithStandards({
|
|
423
|
+
manifest,
|
|
424
|
+
wasmPath: resolvedOutputPath,
|
|
425
|
+
});
|
|
258
426
|
|
|
259
427
|
return {
|
|
260
428
|
compiler: compilerBackend,
|
|
@@ -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
|
+
}
|