space-data-module-sdk 0.2.0 → 0.2.6
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 +77 -2
- package/package.json +13 -4
- 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 +182 -143
- package/src/compiler/emception.d.ts +60 -0
- package/src/compiler/emception.js +191 -0
- package/src/compiler/emceptionNode.js +234 -0
- package/src/compiler/flatcSupport.js +66 -0
- package/src/compiler/index.d.ts +24 -0
- package/src/compiler/index.js +5 -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 +96 -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
- package/src/runtime/index.d.ts +13 -0
- package/src/runtime/index.js +2 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
mkdtemp,
|
|
5
|
+
rm,
|
|
6
|
+
writeFile,
|
|
7
|
+
} from "node:fs/promises";
|
|
6
8
|
|
|
7
9
|
import {
|
|
8
10
|
createDeploymentAuthorization,
|
|
@@ -11,7 +13,18 @@ import {
|
|
|
11
13
|
} from "../auth/index.js";
|
|
12
14
|
import { validateArtifactWithStandards } from "../compliance/index.js";
|
|
13
15
|
import { generateEmbeddedManifestSource } from "../embeddedManifest.js";
|
|
16
|
+
import {
|
|
17
|
+
generateInvokeSupportHeader,
|
|
18
|
+
generateInvokeSupportSource,
|
|
19
|
+
resolveInvokeSurfaces,
|
|
20
|
+
} from "./invokeGlue.js";
|
|
21
|
+
import {
|
|
22
|
+
getFlatbuffersCppRuntimeHeaders,
|
|
23
|
+
getInvokeCppSchemaHeaders,
|
|
24
|
+
} from "./flatcSupport.js";
|
|
25
|
+
import { runWithEmceptionLock } from "./emceptionNode.js";
|
|
14
26
|
import { encodePluginManifest, toEmbeddedPluginManifest } from "../manifest/index.js";
|
|
27
|
+
import { DefaultInvokeExports, InvokeSurface } from "../runtime/constants.js";
|
|
15
28
|
import {
|
|
16
29
|
encryptJsonForRecipient,
|
|
17
30
|
generateX25519Keypair,
|
|
@@ -26,7 +39,6 @@ import {
|
|
|
26
39
|
import { sha256Bytes } from "../utils/crypto.js";
|
|
27
40
|
import { getWasmWallet } from "../utils/wasmCrypto.js";
|
|
28
41
|
|
|
29
|
-
const execFile = promisify(execFileCallback);
|
|
30
42
|
const C_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
31
43
|
|
|
32
44
|
function selectCompiler(language) {
|
|
@@ -49,7 +61,7 @@ function ensureExportableMethodIds(manifest) {
|
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
function buildCompilerArgs(
|
|
64
|
+
function buildCompilerArgs(exportedSymbols, options = {}) {
|
|
53
65
|
const linkerExports = exportedSymbols.map(
|
|
54
66
|
(symbol) => "-Wl,--export=" + symbol,
|
|
55
67
|
);
|
|
@@ -57,125 +69,159 @@ function buildCompilerArgs(compiler, exportedSymbols, options = {}) {
|
|
|
57
69
|
if (options.allowUndefinedImports === true) {
|
|
58
70
|
extraArgs.push("-s", "ERROR_ON_UNDEFINED_SYMBOLS=0", "-Wl,--allow-undefined");
|
|
59
71
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"--no-entry"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
...extraArgs,
|
|
66
|
-
...linkerExports,
|
|
67
|
-
];
|
|
72
|
+
const args = ["-O2", "-s", "STANDALONE_WASM=1", ...extraArgs, ...linkerExports];
|
|
73
|
+
if (options.noEntry === true) {
|
|
74
|
+
args.splice(1, 0, "--no-entry");
|
|
75
|
+
}
|
|
76
|
+
return args;
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
async function getInvokeCppSupportFiles() {
|
|
80
|
+
const [runtimeHeaders, schemaHeaders] = await Promise.all([
|
|
81
|
+
getFlatbuffersCppRuntimeHeaders(),
|
|
82
|
+
getInvokeCppSchemaHeaders(),
|
|
83
|
+
]);
|
|
84
|
+
return { runtimeHeaders, schemaHeaders };
|
|
85
|
+
}
|
|
76
86
|
|
|
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
|
|
87
|
+
async function writeFilesToEmception(emception, rootDir, files) {
|
|
88
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
89
|
+
const filePath = path.posix.join(rootDir, relativePath);
|
|
90
|
+
emception.FS.mkdirTree(path.posix.dirname(filePath));
|
|
91
|
+
emception.writeFile(filePath, content);
|
|
89
92
|
}
|
|
90
|
-
return null;
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
emception
|
|
95
|
-
|
|
96
|
-
sourceCode,
|
|
97
|
-
manifestSource,
|
|
98
|
-
exportedSymbols,
|
|
99
|
-
compileOptions,
|
|
100
|
-
) {
|
|
101
|
-
const ext = compiler.extension;
|
|
102
|
-
const inputPath = `/working/module.${ext}`;
|
|
103
|
-
const manifestPath = "/working/plugin-manifest-exports.c";
|
|
104
|
-
const outputPath = "/working/module.wasm";
|
|
105
|
-
|
|
106
|
-
emception.writeFile(inputPath, sourceCode);
|
|
107
|
-
emception.writeFile(manifestPath, manifestSource);
|
|
108
|
-
|
|
109
|
-
const args = buildCompilerArgs(compiler, exportedSymbols, compileOptions);
|
|
110
|
-
const cmd = [
|
|
111
|
-
compiler.command,
|
|
112
|
-
inputPath,
|
|
113
|
-
manifestPath,
|
|
114
|
-
...args,
|
|
115
|
-
"-o",
|
|
116
|
-
outputPath,
|
|
117
|
-
].join(" ");
|
|
118
|
-
|
|
119
|
-
const result = emception.run(cmd);
|
|
120
|
-
if (result.returncode !== 0) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
`Compilation failed with ${compiler.command} (emception): ${result.stderr || result.stdout}`,
|
|
123
|
-
);
|
|
95
|
+
function removeEmceptionDirectory(emception, directoryPath) {
|
|
96
|
+
if (!emception.FS.analyzePath(directoryPath).exists) {
|
|
97
|
+
return;
|
|
124
98
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
99
|
+
const entries = emception.FS.readdir(directoryPath).filter(
|
|
100
|
+
(entry) => entry !== "." && entry !== "..",
|
|
101
|
+
);
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const entryPath = path.posix.join(directoryPath, entry);
|
|
104
|
+
const stat = emception.FS.stat(entryPath);
|
|
105
|
+
if (emception.FS.isDir(stat.mode)) {
|
|
106
|
+
removeEmceptionDirectory(emception, entryPath);
|
|
107
|
+
emception.FS.rmdir(entryPath);
|
|
108
|
+
} else {
|
|
109
|
+
emception.FS.unlink(entryPath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
emception.FS.rmdir(directoryPath);
|
|
128
113
|
}
|
|
129
114
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
115
|
+
async function compileWithEmception(options = {}) {
|
|
116
|
+
const {
|
|
117
|
+
sourceCompilerCommand,
|
|
118
|
+
sourceExtension,
|
|
119
|
+
sourceCode,
|
|
120
|
+
manifestSource,
|
|
121
|
+
invokeHeaderSource,
|
|
122
|
+
invokeSource,
|
|
123
|
+
exportedSymbols,
|
|
124
|
+
outputPath,
|
|
125
|
+
compileOptions,
|
|
126
|
+
} = options;
|
|
142
127
|
const tempDir = await mkdtemp(
|
|
143
128
|
path.join(os.tmpdir(), "space-data-module-sdk-compile-"),
|
|
144
129
|
);
|
|
145
|
-
const sourcePath = path.join(tempDir, `module.${compiler.extension}`);
|
|
146
|
-
const manifestSourcePath = path.join(tempDir, "plugin-manifest-exports.c");
|
|
147
130
|
const resolvedOutputPath = path.resolve(
|
|
148
131
|
outputPath ?? path.join(tempDir, "module.wasm"),
|
|
149
132
|
);
|
|
150
133
|
|
|
151
|
-
await writeFile(sourcePath, sourceCode, "utf8");
|
|
152
|
-
await writeFile(manifestSourcePath, manifestSource, "utf8");
|
|
153
|
-
|
|
154
|
-
const args = buildCompilerArgs(compiler, exportedSymbols, compileOptions);
|
|
155
|
-
|
|
156
134
|
try {
|
|
157
|
-
await
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"-
|
|
162
|
-
|
|
163
|
-
|
|
135
|
+
return await runWithEmceptionLock(async (emception) => {
|
|
136
|
+
const workDir = "/working/space-data-module-sdk-compile";
|
|
137
|
+
const runtimeIncludeDir = path.posix.join(workDir, "flatbuffers-runtime");
|
|
138
|
+
const sourcePath = path.posix.join(workDir, `module.${sourceExtension}`);
|
|
139
|
+
const manifestSourcePath = path.posix.join(workDir, "plugin-manifest-exports.cpp");
|
|
140
|
+
const invokeHeaderPath = path.posix.join(workDir, "space_data_module_invoke.h");
|
|
141
|
+
const invokeSourcePath = path.posix.join(workDir, "plugin-invoke-bridge.cpp");
|
|
142
|
+
const sourceObjectPath = path.posix.join(workDir, "module.o");
|
|
143
|
+
const manifestObjectPath = path.posix.join(workDir, "plugin-manifest-exports.o");
|
|
144
|
+
const invokeObjectPath = path.posix.join(workDir, "plugin-invoke-bridge.o");
|
|
145
|
+
const wasmOutputPath = path.posix.join(workDir, "module.wasm");
|
|
146
|
+
|
|
147
|
+
const { runtimeHeaders, schemaHeaders } = await getInvokeCppSupportFiles();
|
|
148
|
+
const args = buildCompilerArgs(exportedSymbols, compileOptions);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
emception.FS.mkdirTree(workDir);
|
|
152
|
+
await writeFilesToEmception(emception, runtimeIncludeDir, runtimeHeaders);
|
|
153
|
+
await writeFilesToEmception(emception, workDir, schemaHeaders);
|
|
154
|
+
emception.writeFile(sourcePath, sourceCode);
|
|
155
|
+
emception.writeFile(manifestSourcePath, manifestSource);
|
|
156
|
+
emception.writeFile(invokeHeaderPath, invokeHeaderSource);
|
|
157
|
+
emception.writeFile(invokeSourcePath, invokeSource);
|
|
158
|
+
|
|
159
|
+
const commands = [
|
|
160
|
+
[
|
|
161
|
+
sourceCompilerCommand,
|
|
162
|
+
"-c",
|
|
163
|
+
sourcePath,
|
|
164
|
+
`-I${workDir}`,
|
|
165
|
+
"-o",
|
|
166
|
+
sourceObjectPath,
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
"em++",
|
|
170
|
+
"-c",
|
|
171
|
+
manifestSourcePath,
|
|
172
|
+
"-std=c++17",
|
|
173
|
+
`-I${workDir}`,
|
|
174
|
+
`-I${runtimeIncludeDir}`,
|
|
175
|
+
"-o",
|
|
176
|
+
manifestObjectPath,
|
|
177
|
+
],
|
|
178
|
+
[
|
|
179
|
+
"em++",
|
|
180
|
+
"-c",
|
|
181
|
+
invokeSourcePath,
|
|
182
|
+
"-std=c++17",
|
|
183
|
+
`-I${workDir}`,
|
|
184
|
+
`-I${runtimeIncludeDir}`,
|
|
185
|
+
"-o",
|
|
186
|
+
invokeObjectPath,
|
|
187
|
+
],
|
|
188
|
+
[
|
|
189
|
+
"em++",
|
|
190
|
+
sourceObjectPath,
|
|
191
|
+
manifestObjectPath,
|
|
192
|
+
invokeObjectPath,
|
|
193
|
+
...args,
|
|
194
|
+
"-o",
|
|
195
|
+
wasmOutputPath,
|
|
196
|
+
],
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
for (const command of commands) {
|
|
200
|
+
const result = emception.run(command.join(" "));
|
|
201
|
+
if (result.returncode !== 0) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Compilation failed with ${command[0]} (emception): ${result.stderr || result.stdout}`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const wasmBytes = new Uint8Array(emception.readFile(wasmOutputPath));
|
|
209
|
+
await writeFile(resolvedOutputPath, wasmBytes);
|
|
210
|
+
return { wasmBytes, outputPath: resolvedOutputPath, tempDir };
|
|
211
|
+
} finally {
|
|
212
|
+
try {
|
|
213
|
+
removeEmceptionDirectory(emception, workDir);
|
|
214
|
+
} catch {
|
|
215
|
+
// Best-effort cleanup only; the shared emception instance remains usable.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
164
219
|
} catch (error) {
|
|
165
|
-
|
|
166
|
-
`Compilation failed with ${compiler.command}: ` +
|
|
167
|
-
(error.stderr || error.message);
|
|
220
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
168
221
|
throw error;
|
|
169
222
|
}
|
|
170
|
-
|
|
171
|
-
const wasmBytes = await readFile(resolvedOutputPath);
|
|
172
|
-
return { wasmBytes, outputPath: resolvedOutputPath, tempDir };
|
|
173
223
|
}
|
|
174
224
|
|
|
175
|
-
// ---------------------------------------------------------------------------
|
|
176
|
-
// Public API
|
|
177
|
-
// ---------------------------------------------------------------------------
|
|
178
|
-
|
|
179
225
|
export async function compileModuleFromSource(options = {}) {
|
|
180
226
|
const manifest = options.manifest ?? {};
|
|
181
227
|
const sourceCode = String(options.sourceCode ?? "");
|
|
@@ -193,16 +239,27 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
193
239
|
}
|
|
194
240
|
|
|
195
241
|
const compiler = selectCompiler(options.language);
|
|
242
|
+
const invokeSurfaces = resolveInvokeSurfaces(manifest);
|
|
243
|
+
const includeCommandMain = invokeSurfaces.includes(InvokeSurface.COMMAND);
|
|
196
244
|
const { manifest: embeddedManifest, warnings } = toEmbeddedPluginManifest(
|
|
197
245
|
manifest,
|
|
198
246
|
);
|
|
199
247
|
const manifestSource = generateEmbeddedManifestSource({
|
|
200
248
|
manifest: embeddedManifest,
|
|
201
249
|
});
|
|
250
|
+
const invokeHeaderSource = generateInvokeSupportHeader();
|
|
251
|
+
const invokeSource = generateInvokeSupportSource({
|
|
252
|
+
manifest,
|
|
253
|
+
includeCommandMain,
|
|
254
|
+
});
|
|
202
255
|
|
|
203
256
|
const exportedSymbols = [
|
|
204
257
|
"plugin_get_manifest_flatbuffer",
|
|
205
258
|
"plugin_get_manifest_flatbuffer_size",
|
|
259
|
+
DefaultInvokeExports.invokeSymbol,
|
|
260
|
+
DefaultInvokeExports.allocSymbol,
|
|
261
|
+
DefaultInvokeExports.freeSymbol,
|
|
262
|
+
...(includeCommandMain ? [DefaultInvokeExports.commandSymbol] : []),
|
|
206
263
|
...new Set(
|
|
207
264
|
(Array.isArray(manifest.methods) ? manifest.methods : [])
|
|
208
265
|
.map((method) => String(method?.methodId ?? "").trim())
|
|
@@ -210,54 +267,36 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
210
267
|
),
|
|
211
268
|
];
|
|
212
269
|
|
|
213
|
-
// Try emception first, fall back to system emcc/em++
|
|
214
|
-
const emception = await loadEmception();
|
|
215
|
-
|
|
216
270
|
let wasmBytes;
|
|
217
271
|
let resolvedOutputPath = null;
|
|
218
272
|
let tempDir = null;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
options.outputPath,
|
|
238
|
-
options,
|
|
239
|
-
);
|
|
240
|
-
wasmBytes = result.wasmBytes;
|
|
241
|
-
resolvedOutputPath = result.outputPath;
|
|
242
|
-
tempDir = result.tempDir;
|
|
243
|
-
compilerBackend = `${compiler.command} (system)`;
|
|
244
|
-
}
|
|
273
|
+
const compileOptions = {
|
|
274
|
+
...options,
|
|
275
|
+
noEntry: includeCommandMain !== true,
|
|
276
|
+
};
|
|
277
|
+
const result = await compileWithEmception({
|
|
278
|
+
sourceCompilerCommand: compiler.command,
|
|
279
|
+
sourceExtension: compiler.extension,
|
|
280
|
+
sourceCode,
|
|
281
|
+
manifestSource,
|
|
282
|
+
invokeHeaderSource,
|
|
283
|
+
invokeSource,
|
|
284
|
+
exportedSymbols,
|
|
285
|
+
outputPath: options.outputPath,
|
|
286
|
+
compileOptions,
|
|
287
|
+
});
|
|
288
|
+
wasmBytes = result.wasmBytes;
|
|
289
|
+
resolvedOutputPath = result.outputPath;
|
|
290
|
+
tempDir = result.tempDir;
|
|
245
291
|
|
|
246
292
|
// 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
|
-
});
|
|
293
|
+
const report = await validateArtifactWithStandards({
|
|
294
|
+
manifest,
|
|
295
|
+
wasmPath: resolvedOutputPath,
|
|
296
|
+
});
|
|
258
297
|
|
|
259
298
|
return {
|
|
260
|
-
compiler:
|
|
299
|
+
compiler: "em++ (emception)",
|
|
261
300
|
language: compiler.language,
|
|
262
301
|
outputPath: resolvedOutputPath,
|
|
263
302
|
tempDir,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface EmceptionCommandResult {
|
|
2
|
+
command: string;
|
|
3
|
+
exitCode: number;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type SharedEmceptionFileContent =
|
|
9
|
+
| string
|
|
10
|
+
| Uint8Array
|
|
11
|
+
| ArrayBuffer
|
|
12
|
+
| ArrayBufferView;
|
|
13
|
+
|
|
14
|
+
export interface SharedEmceptionHandle {
|
|
15
|
+
getRaw(): unknown;
|
|
16
|
+
exists(targetPath: string): boolean;
|
|
17
|
+
mkdirTree(directoryPath: string): void;
|
|
18
|
+
writeFile(filePath: string, content: SharedEmceptionFileContent): void;
|
|
19
|
+
writeFiles(
|
|
20
|
+
rootDir: string,
|
|
21
|
+
files: Record<string, SharedEmceptionFileContent>,
|
|
22
|
+
): void;
|
|
23
|
+
readFile(filePath: string): Uint8Array;
|
|
24
|
+
readFile(filePath: string, options: { encoding: "utf8" }): string;
|
|
25
|
+
removeTree(targetPath: string): void;
|
|
26
|
+
run(
|
|
27
|
+
command: string,
|
|
28
|
+
options?: { throwOnNonZero?: boolean },
|
|
29
|
+
): EmceptionCommandResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SharedEmceptionSession {
|
|
33
|
+
load(): Promise<unknown>;
|
|
34
|
+
withLock<T>(
|
|
35
|
+
task: (handle: SharedEmceptionHandle) => T | Promise<T>,
|
|
36
|
+
): Promise<T>;
|
|
37
|
+
exists(targetPath: string): Promise<boolean>;
|
|
38
|
+
mkdirTree(directoryPath: string): Promise<void>;
|
|
39
|
+
writeFile(
|
|
40
|
+
filePath: string,
|
|
41
|
+
content: SharedEmceptionFileContent,
|
|
42
|
+
): Promise<void>;
|
|
43
|
+
writeFiles(
|
|
44
|
+
rootDir: string,
|
|
45
|
+
files: Record<string, SharedEmceptionFileContent>,
|
|
46
|
+
): Promise<void>;
|
|
47
|
+
readFile(filePath: string): Promise<Uint8Array>;
|
|
48
|
+
readFile(filePath: string, options: { encoding: "utf8" }): Promise<string>;
|
|
49
|
+
removeTree(targetPath: string): Promise<void>;
|
|
50
|
+
run(
|
|
51
|
+
command: string,
|
|
52
|
+
options?: { throwOnNonZero?: boolean },
|
|
53
|
+
): Promise<EmceptionCommandResult>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createSharedEmceptionSession(): SharedEmceptionSession;
|
|
57
|
+
export function loadSharedEmception(): Promise<unknown>;
|
|
58
|
+
export function withSharedEmception<T>(
|
|
59
|
+
task: (handle: SharedEmceptionHandle) => T | Promise<T>,
|
|
60
|
+
): Promise<T>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getSharedEmceptionController,
|
|
5
|
+
loadEmception,
|
|
6
|
+
runWithEmceptionLock,
|
|
7
|
+
} from "./emceptionNode.js";
|
|
8
|
+
|
|
9
|
+
const TEXT_DECODER = new TextDecoder();
|
|
10
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
11
|
+
|
|
12
|
+
function normalizeFileContent(content) {
|
|
13
|
+
if (typeof content === "string") {
|
|
14
|
+
return content;
|
|
15
|
+
}
|
|
16
|
+
if (content instanceof Uint8Array) {
|
|
17
|
+
return content;
|
|
18
|
+
}
|
|
19
|
+
if (content instanceof ArrayBuffer) {
|
|
20
|
+
return new Uint8Array(content);
|
|
21
|
+
}
|
|
22
|
+
if (ArrayBuffer.isView(content)) {
|
|
23
|
+
return new Uint8Array(
|
|
24
|
+
content.buffer,
|
|
25
|
+
content.byteOffset,
|
|
26
|
+
content.byteLength,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
throw new TypeError(
|
|
30
|
+
"Emception file content must be a string, Uint8Array, ArrayBuffer, or ArrayBufferView.",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cloneReadBytes(value) {
|
|
35
|
+
if (typeof value === "string") {
|
|
36
|
+
return TEXT_ENCODER.encode(value);
|
|
37
|
+
}
|
|
38
|
+
const bytes = normalizeFileContent(value);
|
|
39
|
+
return new Uint8Array(bytes);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function removeTree(emception, targetPath) {
|
|
43
|
+
const analysis = emception.FS.analyzePath(targetPath);
|
|
44
|
+
if (!analysis.exists) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const stat = emception.FS.stat(targetPath);
|
|
48
|
+
if (!emception.FS.isDir(stat.mode)) {
|
|
49
|
+
emception.FS.unlink(targetPath);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const entries = emception.FS.readdir(targetPath).filter(
|
|
53
|
+
(entry) => entry !== "." && entry !== "..",
|
|
54
|
+
);
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
removeTree(emception, path.posix.join(targetPath, entry));
|
|
57
|
+
}
|
|
58
|
+
emception.FS.rmdir(targetPath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeRunResult(command, result) {
|
|
62
|
+
const normalized = {
|
|
63
|
+
command,
|
|
64
|
+
exitCode: Number(result?.returncode ?? 0) >>> 0,
|
|
65
|
+
stdout: String(result?.stdout ?? ""),
|
|
66
|
+
stderr: String(result?.stderr ?? ""),
|
|
67
|
+
};
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function maybeThrowRunFailure(result, options = {}) {
|
|
72
|
+
if (options.throwOnNonZero === false || result.exitCode === 0) {
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
const detail = result.stderr || result.stdout || "unknown emception failure";
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Emception command failed with exit code ${result.exitCode}: ${result.command}\n${detail}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class SharedEmceptionHandle {
|
|
82
|
+
constructor(emception) {
|
|
83
|
+
this.emception = emception;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getRaw() {
|
|
87
|
+
return this.emception;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
exists(targetPath) {
|
|
91
|
+
return this.emception.FS.analyzePath(targetPath).exists;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
mkdirTree(directoryPath) {
|
|
95
|
+
this.emception.FS.mkdirTree(directoryPath);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
writeFile(filePath, content) {
|
|
99
|
+
this.emception.FS.mkdirTree(path.posix.dirname(filePath));
|
|
100
|
+
this.emception.writeFile(filePath, normalizeFileContent(content));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
writeFiles(rootDir, files) {
|
|
104
|
+
for (const [relativePath, content] of Object.entries(files ?? {})) {
|
|
105
|
+
this.writeFile(path.posix.join(rootDir, relativePath), content);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
readFile(filePath, options = {}) {
|
|
110
|
+
const bytes = cloneReadBytes(this.emception.readFile(filePath));
|
|
111
|
+
if (options.encoding === "utf8") {
|
|
112
|
+
return TEXT_DECODER.decode(bytes);
|
|
113
|
+
}
|
|
114
|
+
return bytes;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
removeTree(targetPath) {
|
|
118
|
+
removeTree(this.emception, targetPath);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
run(command, options = {}) {
|
|
122
|
+
const result = normalizeRunResult(command, this.emception.run(command));
|
|
123
|
+
return maybeThrowRunFailure(result, options);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
class SharedEmceptionSession {
|
|
128
|
+
constructor(controller = getSharedEmceptionController()) {
|
|
129
|
+
this.controller = controller;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async load() {
|
|
133
|
+
return this.controller.load();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async withLock(task) {
|
|
137
|
+
return this.controller.withLock(
|
|
138
|
+
(emception) => task(new SharedEmceptionHandle(emception)),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async exists(targetPath) {
|
|
143
|
+
return this.withLock((handle) => handle.exists(targetPath));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async mkdirTree(directoryPath) {
|
|
147
|
+
await this.withLock((handle) => {
|
|
148
|
+
handle.mkdirTree(directoryPath);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async writeFile(filePath, content) {
|
|
153
|
+
await this.withLock((handle) => {
|
|
154
|
+
handle.writeFile(filePath, content);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async writeFiles(rootDir, files) {
|
|
159
|
+
await this.withLock((handle) => {
|
|
160
|
+
handle.writeFiles(rootDir, files);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async readFile(filePath, options = {}) {
|
|
165
|
+
return this.withLock((handle) => handle.readFile(filePath, options));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async removeTree(targetPath) {
|
|
169
|
+
await this.withLock((handle) => {
|
|
170
|
+
handle.removeTree(targetPath);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async run(command, options = {}) {
|
|
175
|
+
return this.withLock((handle) => handle.run(command, options));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createSharedEmceptionSession() {
|
|
180
|
+
return new SharedEmceptionSession();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function loadSharedEmception() {
|
|
184
|
+
return loadEmception();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function withSharedEmception(task) {
|
|
188
|
+
return runWithEmceptionLock(
|
|
189
|
+
(emception) => task(new SharedEmceptionHandle(emception)),
|
|
190
|
+
);
|
|
191
|
+
}
|