sdn-flow 0.2.0
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/.claude/SKILLS.md +7 -0
- package/.claude/skills/sdn-plugin-abi-compliance/SKILL.md +56 -0
- package/.claude/todo/001-js-host-startup-and-deno.md +85 -0
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/bin/sdn-flow-host.js +169 -0
- package/docs/.nojekyll +0 -0
- package/docs/ARCHITECTURE.md +200 -0
- package/docs/HOST_CAPABILITY_MODEL.md +317 -0
- package/docs/PLUGIN_ARCHITECTURE.md +145 -0
- package/docs/PLUGIN_COMPATIBILITY.md +61 -0
- package/docs/PLUGIN_COMPLIANCE_CHECKS.md +82 -0
- package/docs/PLUGIN_MANIFEST.md +94 -0
- package/docs/css/style.css +465 -0
- package/docs/index.html +218 -0
- package/docs/js/app.mjs +751 -0
- package/docs/js/editor-panel.mjs +203 -0
- package/docs/js/flow-canvas.mjs +515 -0
- package/docs/js/flow-model.mjs +391 -0
- package/docs/js/workers/emception.worker.js +146 -0
- package/docs/js/workers/pyodide.worker.js +134 -0
- package/native/flow_source_generator.cpp +1958 -0
- package/package.json +67 -0
- package/schemas/FlowRuntimeAbi.fbs +91 -0
- package/src/auth/canonicalize.js +5 -0
- package/src/auth/index.js +11 -0
- package/src/auth/permissions.js +8 -0
- package/src/compiler/CppFlowSourceGenerator.js +475 -0
- package/src/compiler/EmceptionCompilerAdapter.js +244 -0
- package/src/compiler/SignedArtifactCatalog.js +152 -0
- package/src/compiler/index.js +8 -0
- package/src/compiler/nativeFlowSourceGeneratorTool.js +144 -0
- package/src/compliance/index.js +13 -0
- package/src/compliance/pluginCompliance.js +11 -0
- package/src/deploy/FlowDeploymentClient.js +532 -0
- package/src/deploy/index.js +8 -0
- package/src/designer/FlowDesignerSession.js +158 -0
- package/src/designer/index.js +2 -0
- package/src/designer/requirements.js +184 -0
- package/src/generated/runtimeAbiLayouts.js +544 -0
- package/src/host/appHost.js +105 -0
- package/src/host/autoHost.js +113 -0
- package/src/host/browserHostAdapters.js +108 -0
- package/src/host/compiledFlowRuntimeHost.js +703 -0
- package/src/host/constants.js +55 -0
- package/src/host/dependencyRuntime.js +227 -0
- package/src/host/descriptorAbi.js +351 -0
- package/src/host/fetchService.js +237 -0
- package/src/host/httpHostAdapters.js +280 -0
- package/src/host/index.js +91 -0
- package/src/host/installedFlowHost.js +885 -0
- package/src/host/invocationAbi.js +440 -0
- package/src/host/normalize.js +372 -0
- package/src/host/packageManagers.js +369 -0
- package/src/host/profile.js +134 -0
- package/src/host/runtimeAbi.js +106 -0
- package/src/host/workspace.js +895 -0
- package/src/index.js +8 -0
- package/src/runtime/FlowRuntime.js +273 -0
- package/src/runtime/MethodRegistry.js +295 -0
- package/src/runtime/constants.js +44 -0
- package/src/runtime/index.js +19 -0
- package/src/runtime/normalize.js +377 -0
- package/src/transport/index.js +7 -0
- package/src/transport/pki.js +7 -0
- package/src/utils/crypto.js +7 -0
- package/src/utils/encoding.js +65 -0
- package/src/utils/wasmCrypto.js +69 -0
- package/tools/run-plugin-compliance-check.mjs +153 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { summarizeProgramRequirements } from "../designer/requirements.js";
|
|
2
|
+
import { normalizeProgram } from "../runtime/index.js";
|
|
3
|
+
import { bytesToHex, toUint8Array } from "../utils/encoding.js";
|
|
4
|
+
import { sha256Bytes } from "../utils/crypto.js";
|
|
5
|
+
import { generateCppFlowRuntimeSource } from "./CppFlowSourceGenerator.js";
|
|
6
|
+
import { SignedArtifactCatalog } from "./SignedArtifactCatalog.js";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_FLAGS = Object.freeze([
|
|
9
|
+
"-std=c++20",
|
|
10
|
+
"-O2",
|
|
11
|
+
"-sWASM=1",
|
|
12
|
+
"-sALLOW_MEMORY_GROWTH=1",
|
|
13
|
+
"-sNO_EXIT_RUNTIME=1",
|
|
14
|
+
"-sMODULARIZE=1",
|
|
15
|
+
"-sEXPORT_ES6=1",
|
|
16
|
+
"-sENVIRONMENT=web,worker,node",
|
|
17
|
+
"-sEXPORTED_FUNCTIONS=['_main','_malloc','_free','_flow_get_manifest_flatbuffer','_flow_get_manifest_flatbuffer_size','_sdn_flow_get_program_id','_sdn_flow_get_program_name','_sdn_flow_get_program_version','_sdn_flow_get_type_descriptors','_sdn_flow_get_type_descriptor_count','_sdn_flow_get_accepted_type_indices','_sdn_flow_get_accepted_type_index_count','_sdn_flow_get_trigger_descriptors','_sdn_flow_get_trigger_descriptor_count','_sdn_flow_get_node_descriptors','_sdn_flow_get_node_descriptor_count','_sdn_flow_get_node_dispatch_descriptors','_sdn_flow_get_node_dispatch_descriptor_count','_sdn_flow_get_edge_descriptors','_sdn_flow_get_edge_descriptor_count','_sdn_flow_get_trigger_binding_descriptors','_sdn_flow_get_trigger_binding_descriptor_count','_sdn_flow_get_dependency_descriptors','_sdn_flow_get_dependency_count','_sdn_flow_get_ingress_descriptors','_sdn_flow_get_ingress_descriptor_count','_sdn_flow_get_ingress_frame_descriptors','_sdn_flow_get_ingress_frame_descriptor_count','_sdn_flow_get_node_ingress_indices','_sdn_flow_get_node_ingress_index_count','_sdn_flow_get_external_interface_descriptors','_sdn_flow_get_external_interface_descriptor_count','_sdn_flow_get_ingress_runtime_states','_sdn_flow_get_ingress_runtime_state_count','_sdn_flow_get_node_runtime_states','_sdn_flow_get_node_runtime_state_count','_sdn_flow_get_current_invocation_descriptor','_sdn_flow_prepare_node_invocation_descriptor','_sdn_flow_reset_runtime_state','_sdn_flow_enqueue_trigger_frames','_sdn_flow_enqueue_trigger_frame','_sdn_flow_enqueue_edge_frames','_sdn_flow_enqueue_edge_frame','_sdn_flow_get_ready_node_index','_sdn_flow_begin_node_invocation','_sdn_flow_complete_node_invocation','_sdn_flow_apply_node_invocation_result','_sdn_flow_dispatch_next_ready_node_with_host','_sdn_flow_drain_with_host_dispatch','_sdn_flow_get_runtime_descriptor']",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const DEFAULT_RUNTIME_MODEL = "compiled-cpp-wasm";
|
|
21
|
+
const DEFAULT_RUNTIME_EXPORTS = Object.freeze({
|
|
22
|
+
mallocSymbol: "malloc",
|
|
23
|
+
freeSymbol: "free",
|
|
24
|
+
descriptorSymbol: "sdn_flow_get_runtime_descriptor",
|
|
25
|
+
typeDescriptorsSymbol: "sdn_flow_get_type_descriptors",
|
|
26
|
+
typeDescriptorCountSymbol: "sdn_flow_get_type_descriptor_count",
|
|
27
|
+
acceptedTypeIndicesSymbol: "sdn_flow_get_accepted_type_indices",
|
|
28
|
+
acceptedTypeIndexCountSymbol: "sdn_flow_get_accepted_type_index_count",
|
|
29
|
+
triggerDescriptorsSymbol: "sdn_flow_get_trigger_descriptors",
|
|
30
|
+
triggerDescriptorCountSymbol: "sdn_flow_get_trigger_descriptor_count",
|
|
31
|
+
nodeDescriptorsSymbol: "sdn_flow_get_node_descriptors",
|
|
32
|
+
nodeDescriptorCountSymbol: "sdn_flow_get_node_descriptor_count",
|
|
33
|
+
nodeDispatchDescriptorsSymbol: "sdn_flow_get_node_dispatch_descriptors",
|
|
34
|
+
nodeDispatchDescriptorCountSymbol:
|
|
35
|
+
"sdn_flow_get_node_dispatch_descriptor_count",
|
|
36
|
+
edgeDescriptorsSymbol: "sdn_flow_get_edge_descriptors",
|
|
37
|
+
edgeDescriptorCountSymbol: "sdn_flow_get_edge_descriptor_count",
|
|
38
|
+
triggerBindingDescriptorsSymbol: "sdn_flow_get_trigger_binding_descriptors",
|
|
39
|
+
triggerBindingDescriptorCountSymbol:
|
|
40
|
+
"sdn_flow_get_trigger_binding_descriptor_count",
|
|
41
|
+
dependencyDescriptorsSymbol: "sdn_flow_get_dependency_descriptors",
|
|
42
|
+
dependencyCountSymbol: "sdn_flow_get_dependency_count",
|
|
43
|
+
resetStateSymbol: "sdn_flow_reset_runtime_state",
|
|
44
|
+
ingressDescriptorsSymbol: "sdn_flow_get_ingress_descriptors",
|
|
45
|
+
ingressDescriptorCountSymbol: "sdn_flow_get_ingress_descriptor_count",
|
|
46
|
+
ingressFrameDescriptorsSymbol: "sdn_flow_get_ingress_frame_descriptors",
|
|
47
|
+
ingressFrameDescriptorCountSymbol:
|
|
48
|
+
"sdn_flow_get_ingress_frame_descriptor_count",
|
|
49
|
+
nodeIngressIndicesSymbol: "sdn_flow_get_node_ingress_indices",
|
|
50
|
+
nodeIngressIndexCountSymbol: "sdn_flow_get_node_ingress_index_count",
|
|
51
|
+
externalInterfaceDescriptorsSymbol:
|
|
52
|
+
"sdn_flow_get_external_interface_descriptors",
|
|
53
|
+
externalInterfaceDescriptorCountSymbol:
|
|
54
|
+
"sdn_flow_get_external_interface_descriptor_count",
|
|
55
|
+
ingressStatesSymbol: "sdn_flow_get_ingress_runtime_states",
|
|
56
|
+
ingressStateCountSymbol: "sdn_flow_get_ingress_runtime_state_count",
|
|
57
|
+
nodeStatesSymbol: "sdn_flow_get_node_runtime_states",
|
|
58
|
+
nodeStateCountSymbol: "sdn_flow_get_node_runtime_state_count",
|
|
59
|
+
currentInvocationDescriptorSymbol:
|
|
60
|
+
"sdn_flow_get_current_invocation_descriptor",
|
|
61
|
+
prepareInvocationDescriptorSymbol:
|
|
62
|
+
"sdn_flow_prepare_node_invocation_descriptor",
|
|
63
|
+
enqueueTriggerSymbol: "sdn_flow_enqueue_trigger_frames",
|
|
64
|
+
enqueueTriggerFrameSymbol: "sdn_flow_enqueue_trigger_frame",
|
|
65
|
+
enqueueEdgeSymbol: "sdn_flow_enqueue_edge_frames",
|
|
66
|
+
enqueueEdgeFrameSymbol: "sdn_flow_enqueue_edge_frame",
|
|
67
|
+
readyNodeSymbol: "sdn_flow_get_ready_node_index",
|
|
68
|
+
beginInvocationSymbol: "sdn_flow_begin_node_invocation",
|
|
69
|
+
completeInvocationSymbol: "sdn_flow_complete_node_invocation",
|
|
70
|
+
applyInvocationResultSymbol: "sdn_flow_apply_node_invocation_result",
|
|
71
|
+
dispatchHostInvocationSymbol: "sdn_flow_dispatch_next_ready_node_with_host",
|
|
72
|
+
drainWithHostDispatchSymbol: "sdn_flow_drain_with_host_dispatch",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
async function maybeCall(value) {
|
|
76
|
+
return value instanceof Promise ? value : Promise.resolve(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class EmceptionCompilerAdapter {
|
|
80
|
+
#emception;
|
|
81
|
+
|
|
82
|
+
#manifestBuilder;
|
|
83
|
+
|
|
84
|
+
#artifactCatalog;
|
|
85
|
+
|
|
86
|
+
#sourceGenerator;
|
|
87
|
+
|
|
88
|
+
#flags;
|
|
89
|
+
|
|
90
|
+
#outputName;
|
|
91
|
+
|
|
92
|
+
constructor(options = {}) {
|
|
93
|
+
this.#emception = options.emception ?? null;
|
|
94
|
+
this.#manifestBuilder = options.manifestBuilder ?? null;
|
|
95
|
+
this.#artifactCatalog =
|
|
96
|
+
options.artifactCatalog ?? new SignedArtifactCatalog();
|
|
97
|
+
this.#sourceGenerator =
|
|
98
|
+
options.sourceGenerator ?? generateCppFlowRuntimeSource;
|
|
99
|
+
this.#flags = Array.isArray(options.flags) ? options.flags : DEFAULT_FLAGS;
|
|
100
|
+
this.#outputName = String(options.outputName ?? "flow-runtime");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get artifactCatalog() {
|
|
104
|
+
return this.#artifactCatalog;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async #buildManifestBuffer({ program, metadata, dependencies }) {
|
|
108
|
+
if (metadata?.manifestBuffer) {
|
|
109
|
+
return toUint8Array(metadata.manifestBuffer);
|
|
110
|
+
}
|
|
111
|
+
if (typeof this.#manifestBuilder === "function") {
|
|
112
|
+
return toUint8Array(
|
|
113
|
+
await maybeCall(
|
|
114
|
+
this.#manifestBuilder({
|
|
115
|
+
program,
|
|
116
|
+
metadata,
|
|
117
|
+
dependencies,
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
throw new Error(
|
|
123
|
+
"EmceptionCompilerAdapter requires metadata.manifestBuffer or manifestBuilder().",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async prepareCompile({ program, metadata = null } = {}) {
|
|
128
|
+
const normalizedProgram = normalizeProgram(program);
|
|
129
|
+
const dependencies =
|
|
130
|
+
await this.#artifactCatalog.resolveProgramDependencies(normalizedProgram);
|
|
131
|
+
const manifestBuffer = await this.#buildManifestBuffer({
|
|
132
|
+
program: normalizedProgram,
|
|
133
|
+
metadata,
|
|
134
|
+
dependencies,
|
|
135
|
+
});
|
|
136
|
+
const generatedSource = await maybeCall(
|
|
137
|
+
this.#sourceGenerator({
|
|
138
|
+
program: normalizedProgram,
|
|
139
|
+
manifestBuffer,
|
|
140
|
+
dependencies,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
const source =
|
|
144
|
+
typeof generatedSource === "string"
|
|
145
|
+
? generatedSource
|
|
146
|
+
: (generatedSource?.source ?? "");
|
|
147
|
+
const sourceGeneratorModel =
|
|
148
|
+
typeof generatedSource === "string"
|
|
149
|
+
? "native-cpp-wasm"
|
|
150
|
+
: (generatedSource?.generatorModel ?? "native-cpp-wasm");
|
|
151
|
+
const outputName = String(metadata?.outputName ?? this.#outputName);
|
|
152
|
+
const flags = Array.isArray(metadata?.flags) ? metadata.flags : this.#flags;
|
|
153
|
+
return {
|
|
154
|
+
program: normalizedProgram,
|
|
155
|
+
manifestBuffer,
|
|
156
|
+
dependencies,
|
|
157
|
+
runtimeModel: DEFAULT_RUNTIME_MODEL,
|
|
158
|
+
runtimeExports: { ...DEFAULT_RUNTIME_EXPORTS },
|
|
159
|
+
sourceGeneratorModel,
|
|
160
|
+
outputName,
|
|
161
|
+
flags,
|
|
162
|
+
source,
|
|
163
|
+
sourceFiles: [
|
|
164
|
+
{
|
|
165
|
+
path: "/working/main.cpp",
|
|
166
|
+
content: source,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
command: `em++ ${flags.join(" ")} /working/main.cpp -o /working/${outputName}.mjs`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async compile({ program, metadata = null } = {}) {
|
|
174
|
+
const compilePlan = await this.prepareCompile({ program, metadata });
|
|
175
|
+
if (!this.#emception) {
|
|
176
|
+
return {
|
|
177
|
+
programId: compilePlan.program.programId,
|
|
178
|
+
manifestBuffer: compilePlan.manifestBuffer,
|
|
179
|
+
runtimeModel: compilePlan.runtimeModel,
|
|
180
|
+
runtimeExports: compilePlan.runtimeExports,
|
|
181
|
+
sourceGeneratorModel: compilePlan.sourceGeneratorModel,
|
|
182
|
+
compilePlan,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (typeof this.#emception.init === "function") {
|
|
187
|
+
await maybeCall(this.#emception.init());
|
|
188
|
+
}
|
|
189
|
+
for (const file of compilePlan.sourceFiles) {
|
|
190
|
+
await maybeCall(this.#emception.writeFile(file.path, file.content));
|
|
191
|
+
}
|
|
192
|
+
const result = await maybeCall(this.#emception.run(compilePlan.command));
|
|
193
|
+
if (Number(result?.returncode ?? 1) !== 0) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Emception compile failed: ${result?.stderr ?? result?.stdout ?? "unknown error"}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const wasm = toUint8Array(
|
|
200
|
+
await maybeCall(
|
|
201
|
+
this.#emception.readFile(`/working/${compilePlan.outputName}.wasm`),
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
const loaderModule = await maybeCall(
|
|
205
|
+
this.#emception.readFile(`/working/${compilePlan.outputName}.mjs`, {
|
|
206
|
+
encoding: "utf8",
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
209
|
+
const requirements = summarizeProgramRequirements({
|
|
210
|
+
program: compilePlan.program,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
artifactId: `${compilePlan.program.programId}:${bytesToHex(
|
|
215
|
+
await sha256Bytes(wasm),
|
|
216
|
+
).slice(0, 16)}`,
|
|
217
|
+
programId: compilePlan.program.programId,
|
|
218
|
+
runtimeModel: compilePlan.runtimeModel,
|
|
219
|
+
sourceGeneratorModel: compilePlan.sourceGeneratorModel,
|
|
220
|
+
format: "application/wasm",
|
|
221
|
+
wasm,
|
|
222
|
+
loaderModule,
|
|
223
|
+
manifestBuffer: compilePlan.manifestBuffer,
|
|
224
|
+
runtimeExports: compilePlan.runtimeExports,
|
|
225
|
+
entrypoint: "main",
|
|
226
|
+
graphHash: bytesToHex(
|
|
227
|
+
await sha256Bytes(
|
|
228
|
+
new TextEncoder().encode(JSON.stringify(compilePlan.program)),
|
|
229
|
+
),
|
|
230
|
+
),
|
|
231
|
+
requiredCapabilities: requirements.capabilities,
|
|
232
|
+
pluginVersions: compilePlan.dependencies.map((dependency) => ({
|
|
233
|
+
pluginId: dependency.pluginId,
|
|
234
|
+
version: dependency.version,
|
|
235
|
+
sha256: dependency.sha256,
|
|
236
|
+
})),
|
|
237
|
+
schemaBindings: metadata?.schemaBindings ?? [],
|
|
238
|
+
abiVersion: 1,
|
|
239
|
+
compilePlan,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export default EmceptionCompilerAdapter;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeArtifactDependency,
|
|
3
|
+
normalizeProgram,
|
|
4
|
+
} from "../runtime/index.js";
|
|
5
|
+
import { sha256Bytes } from "../utils/crypto.js";
|
|
6
|
+
import { bytesToHex, toUint8Array } from "../utils/encoding.js";
|
|
7
|
+
|
|
8
|
+
function normalizeSignedArtifact(artifact = {}) {
|
|
9
|
+
const normalized = normalizeArtifactDependency(artifact);
|
|
10
|
+
return {
|
|
11
|
+
...normalized,
|
|
12
|
+
wasm:
|
|
13
|
+
artifact.wasm === undefined || artifact.wasm === null
|
|
14
|
+
? null
|
|
15
|
+
: toUint8Array(artifact.wasm),
|
|
16
|
+
manifestBuffer:
|
|
17
|
+
artifact.manifestBuffer === undefined || artifact.manifestBuffer === null
|
|
18
|
+
? null
|
|
19
|
+
: toUint8Array(artifact.manifestBuffer),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function preferNonEmptyString(primary, fallback = null) {
|
|
24
|
+
if (typeof primary === "string" && primary.trim().length > 0) {
|
|
25
|
+
return primary;
|
|
26
|
+
}
|
|
27
|
+
return fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function dependencyKey(dependency) {
|
|
31
|
+
if (dependency.dependencyId) {
|
|
32
|
+
return `dependency:${dependency.dependencyId}`;
|
|
33
|
+
}
|
|
34
|
+
if (dependency.artifactId) {
|
|
35
|
+
return `artifact:${dependency.artifactId}`;
|
|
36
|
+
}
|
|
37
|
+
if (dependency.pluginId && dependency.version) {
|
|
38
|
+
return `plugin:${dependency.pluginId}@${dependency.version}`;
|
|
39
|
+
}
|
|
40
|
+
if (dependency.pluginId) {
|
|
41
|
+
return `plugin:${dependency.pluginId}`;
|
|
42
|
+
}
|
|
43
|
+
throw new Error(
|
|
44
|
+
"Signed artifact is missing dependencyId/artifactId/pluginId.",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class SignedArtifactCatalog {
|
|
49
|
+
#artifacts = new Map();
|
|
50
|
+
|
|
51
|
+
registerArtifact(artifact) {
|
|
52
|
+
const normalized = normalizeSignedArtifact(artifact);
|
|
53
|
+
const key = dependencyKey(normalized);
|
|
54
|
+
this.#artifacts.set(key, normalized);
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
registerArtifacts(artifacts = []) {
|
|
59
|
+
return artifacts.map((artifact) => this.registerArtifact(artifact));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getArtifact(selector) {
|
|
63
|
+
const normalized = normalizeSignedArtifact(selector);
|
|
64
|
+
const attemptedKeys = [];
|
|
65
|
+
for (const key of [
|
|
66
|
+
normalized.dependencyId ? `dependency:${normalized.dependencyId}` : null,
|
|
67
|
+
normalized.artifactId ? `artifact:${normalized.artifactId}` : null,
|
|
68
|
+
normalized.pluginId && normalized.version
|
|
69
|
+
? `plugin:${normalized.pluginId}@${normalized.version}`
|
|
70
|
+
: null,
|
|
71
|
+
normalized.pluginId ? `plugin:${normalized.pluginId}` : null,
|
|
72
|
+
]) {
|
|
73
|
+
if (!key) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
attemptedKeys.push(key);
|
|
77
|
+
const artifact = this.#artifacts.get(key);
|
|
78
|
+
if (artifact) {
|
|
79
|
+
return artifact;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Signed artifact dependency not found in catalog (${attemptedKeys.join(", ")}).`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async resolveProgramDependencies(program) {
|
|
88
|
+
const normalizedProgram = normalizeProgram(program);
|
|
89
|
+
const resolved = [];
|
|
90
|
+
for (const dependency of normalizedProgram.artifactDependencies) {
|
|
91
|
+
const artifact = this.getArtifact(dependency);
|
|
92
|
+
if (!artifact.signature || !artifact.signerPublicKey) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Signed artifact "${dependency.dependencyId || dependency.pluginId}" is missing signature metadata.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (!artifact.wasm || artifact.wasm.length === 0) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Signed artifact "${dependency.dependencyId || dependency.pluginId}" is missing wasm bytes.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const computedSha256 = bytesToHex(await sha256Bytes(artifact.wasm));
|
|
103
|
+
if (artifact.sha256 && artifact.sha256 !== computedSha256) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Signed artifact "${dependency.dependencyId || dependency.pluginId}" sha256 mismatch.`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
resolved.push({
|
|
109
|
+
...artifact,
|
|
110
|
+
...dependency,
|
|
111
|
+
wasm: artifact.wasm,
|
|
112
|
+
manifestBuffer: artifact.manifestBuffer,
|
|
113
|
+
sha256: artifact.sha256 ?? computedSha256,
|
|
114
|
+
manifestExports: {
|
|
115
|
+
bytesSymbol: preferNonEmptyString(
|
|
116
|
+
dependency.manifestExports?.bytesSymbol,
|
|
117
|
+
artifact.manifestExports?.bytesSymbol,
|
|
118
|
+
),
|
|
119
|
+
sizeSymbol: preferNonEmptyString(
|
|
120
|
+
dependency.manifestExports?.sizeSymbol,
|
|
121
|
+
artifact.manifestExports?.sizeSymbol,
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
runtimeExports: {
|
|
125
|
+
initSymbol: preferNonEmptyString(
|
|
126
|
+
dependency.runtimeExports?.initSymbol,
|
|
127
|
+
artifact.runtimeExports?.initSymbol,
|
|
128
|
+
),
|
|
129
|
+
destroySymbol: preferNonEmptyString(
|
|
130
|
+
dependency.runtimeExports?.destroySymbol,
|
|
131
|
+
artifact.runtimeExports?.destroySymbol,
|
|
132
|
+
),
|
|
133
|
+
mallocSymbol: preferNonEmptyString(
|
|
134
|
+
dependency.runtimeExports?.mallocSymbol,
|
|
135
|
+
artifact.runtimeExports?.mallocSymbol,
|
|
136
|
+
),
|
|
137
|
+
freeSymbol: preferNonEmptyString(
|
|
138
|
+
dependency.runtimeExports?.freeSymbol,
|
|
139
|
+
artifact.runtimeExports?.freeSymbol,
|
|
140
|
+
),
|
|
141
|
+
streamInvokeSymbol: preferNonEmptyString(
|
|
142
|
+
dependency.runtimeExports?.streamInvokeSymbol,
|
|
143
|
+
artifact.runtimeExports?.streamInvokeSymbol,
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return resolved;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default SignedArtifactCatalog;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { SignedArtifactCatalog } from "./SignedArtifactCatalog.js";
|
|
2
|
+
export { EmceptionCompilerAdapter } from "./EmceptionCompilerAdapter.js";
|
|
3
|
+
export { generateCppFlowRuntimeSource } from "./CppFlowSourceGenerator.js";
|
|
4
|
+
export {
|
|
5
|
+
ensureNativeFlowSourceGeneratorTool,
|
|
6
|
+
getNativeFlowSourceGeneratorToolInfo,
|
|
7
|
+
runNativeFlowSourceGenerator,
|
|
8
|
+
} from "./nativeFlowSourceGeneratorTool.js";
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { mkdirSync, statSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const PACKAGE_ROOT = fileURLToPath(new URL("../../", import.meta.url));
|
|
7
|
+
const NATIVE_SOURCE_PATH = fileURLToPath(
|
|
8
|
+
new URL("../../native/flow_source_generator.cpp", import.meta.url),
|
|
9
|
+
);
|
|
10
|
+
const GENERATED_DIR = fileURLToPath(
|
|
11
|
+
new URL("../../generated-tools/", import.meta.url),
|
|
12
|
+
);
|
|
13
|
+
const MODULE_PATH = path.join(GENERATED_DIR, "flow-source-generator.mjs");
|
|
14
|
+
const WASM_PATH = path.join(GENERATED_DIR, "flow-source-generator.wasm");
|
|
15
|
+
const EMXX_FLAGS = Object.freeze([
|
|
16
|
+
"-std=c++20",
|
|
17
|
+
"-O2",
|
|
18
|
+
"-sWASM=1",
|
|
19
|
+
"-sMODULARIZE=1",
|
|
20
|
+
"-sEXPORT_ES6=1",
|
|
21
|
+
"-sENVIRONMENT=node",
|
|
22
|
+
"-sALLOW_MEMORY_GROWTH=1",
|
|
23
|
+
"-sFORCE_FILESYSTEM=1",
|
|
24
|
+
"-sEXPORTED_RUNTIME_METHODS=['FS','callMain']",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function getMtimeMs(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return statSync(filePath).mtimeMs;
|
|
30
|
+
} catch {
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toolBuildIsCurrent() {
|
|
36
|
+
const sourceTime = getMtimeMs(NATIVE_SOURCE_PATH);
|
|
37
|
+
const moduleTime = getMtimeMs(MODULE_PATH);
|
|
38
|
+
const wasmTime = getMtimeMs(WASM_PATH);
|
|
39
|
+
return moduleTime >= sourceTime && wasmTime >= sourceTime;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function assertToolExists() {
|
|
43
|
+
if (!toolBuildIsCurrent()) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"flow source generator tool is not built. Run ensureNativeFlowSourceGeneratorTool() first.",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getNativeFlowSourceGeneratorToolInfo() {
|
|
51
|
+
return {
|
|
52
|
+
packageRoot: PACKAGE_ROOT,
|
|
53
|
+
sourcePath: NATIVE_SOURCE_PATH,
|
|
54
|
+
generatedDir: GENERATED_DIR,
|
|
55
|
+
modulePath: MODULE_PATH,
|
|
56
|
+
wasmPath: WASM_PATH,
|
|
57
|
+
command: ["em++", ...EMXX_FLAGS, NATIVE_SOURCE_PATH, "-o", MODULE_PATH],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function ensureNativeFlowSourceGeneratorTool({
|
|
62
|
+
force = false,
|
|
63
|
+
} = {}) {
|
|
64
|
+
mkdirSync(GENERATED_DIR, { recursive: true });
|
|
65
|
+
if (!force && toolBuildIsCurrent()) {
|
|
66
|
+
return getNativeFlowSourceGeneratorToolInfo();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result = spawnSync(
|
|
70
|
+
"em++",
|
|
71
|
+
[...EMXX_FLAGS, NATIVE_SOURCE_PATH, "-o", MODULE_PATH],
|
|
72
|
+
{
|
|
73
|
+
cwd: PACKAGE_ROOT,
|
|
74
|
+
encoding: "utf8",
|
|
75
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
if (result.error) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`failed to run em++ for flow source generator: ${result.error.message}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if ((result.status ?? 1) !== 0) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`failed to build flow source generator wasm tool:\n${result.stdout ?? ""}${result.stderr ?? ""}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
assertToolExists();
|
|
90
|
+
return getNativeFlowSourceGeneratorToolInfo();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function importToolModule(modulePath) {
|
|
94
|
+
const moduleUrl = `${pathToFileURL(modulePath).href}?v=${getMtimeMs(modulePath)}`;
|
|
95
|
+
return import(moduleUrl);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function runNativeFlowSourceGenerator(requestBytes, options = {}) {
|
|
99
|
+
const tool = await ensureNativeFlowSourceGeneratorTool(options);
|
|
100
|
+
const toolModule = await importToolModule(tool.modulePath);
|
|
101
|
+
const stdout = [];
|
|
102
|
+
const stderr = [];
|
|
103
|
+
const generator = await toolModule.default({
|
|
104
|
+
locateFile(file) {
|
|
105
|
+
return path.join(tool.generatedDir, file);
|
|
106
|
+
},
|
|
107
|
+
print(...args) {
|
|
108
|
+
stdout.push(args.join(" "));
|
|
109
|
+
},
|
|
110
|
+
printErr(...args) {
|
|
111
|
+
stderr.push(args.join(" "));
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
generator.FS.writeFile("/request.bin", requestBytes);
|
|
116
|
+
let exitError = null;
|
|
117
|
+
try {
|
|
118
|
+
generator.callMain(["/request.bin", "/output.cpp"]);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
exitError = error;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!generator.FS.analyzePath("/output.cpp").exists) {
|
|
124
|
+
const details = stderr.join("\n");
|
|
125
|
+
if (exitError) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`native flow source generator failed: ${exitError.message}\n${details}`.trim(),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
throw new Error(
|
|
131
|
+
`native flow source generator did not produce output.\n${details}`.trim(),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
source: generator.FS.readFile("/output.cpp", { encoding: "utf8" }),
|
|
137
|
+
stdout: stdout.join("\n"),
|
|
138
|
+
stderr: stderr.join("\n"),
|
|
139
|
+
tool,
|
|
140
|
+
generatorModel: "native-cpp-wasm",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default runNativeFlowSourceGenerator;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
findManifestFiles,
|
|
3
|
+
getWasmExportNames,
|
|
4
|
+
getWasmExportNamesFromFile,
|
|
5
|
+
loadManifestFromFile,
|
|
6
|
+
loadComplianceConfig,
|
|
7
|
+
RecommendedCapabilityIds,
|
|
8
|
+
resolveManifestFiles,
|
|
9
|
+
validateArtifactWithStandards,
|
|
10
|
+
validateManifestWithStandards,
|
|
11
|
+
validatePluginArtifact,
|
|
12
|
+
validatePluginManifest,
|
|
13
|
+
} from "space-data-module-sdk/compliance";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
findManifestFiles,
|
|
3
|
+
getWasmExportNames,
|
|
4
|
+
getWasmExportNamesFromFile,
|
|
5
|
+
loadManifestFromFile,
|
|
6
|
+
loadComplianceConfig,
|
|
7
|
+
RecommendedCapabilityIds,
|
|
8
|
+
resolveManifestFiles,
|
|
9
|
+
validatePluginArtifact,
|
|
10
|
+
validatePluginManifest,
|
|
11
|
+
} from "space-data-module-sdk/compliance";
|