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,532 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertDeploymentAuthorization,
|
|
3
|
+
createDeploymentAuthorization,
|
|
4
|
+
signAuthorization,
|
|
5
|
+
} from "../auth/index.js";
|
|
6
|
+
import { DefaultManifestExports } from "../runtime/constants.js";
|
|
7
|
+
import { encryptJsonForRecipient } from "../transport/index.js";
|
|
8
|
+
import {
|
|
9
|
+
base64ToBytes,
|
|
10
|
+
bytesToBase64,
|
|
11
|
+
bytesToHex,
|
|
12
|
+
toUint8Array,
|
|
13
|
+
} from "../utils/encoding.js";
|
|
14
|
+
import { sha256Bytes } from "../utils/crypto.js";
|
|
15
|
+
|
|
16
|
+
function maybeParseStructuredInput(input) {
|
|
17
|
+
if (typeof input === "string") {
|
|
18
|
+
const trimmed = input.trim();
|
|
19
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
20
|
+
return JSON.parse(trimmed);
|
|
21
|
+
}
|
|
22
|
+
return input;
|
|
23
|
+
}
|
|
24
|
+
if (input instanceof Uint8Array || ArrayBuffer.isView(input)) {
|
|
25
|
+
const decoded = new TextDecoder().decode(
|
|
26
|
+
input instanceof Uint8Array
|
|
27
|
+
? input
|
|
28
|
+
: new Uint8Array(input.buffer, input.byteOffset, input.byteLength),
|
|
29
|
+
);
|
|
30
|
+
const trimmed = decoded.trim();
|
|
31
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
32
|
+
return JSON.parse(trimmed);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (input instanceof ArrayBuffer) {
|
|
36
|
+
const decoded = new TextDecoder().decode(new Uint8Array(input));
|
|
37
|
+
const trimmed = decoded.trim();
|
|
38
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
39
|
+
return JSON.parse(trimmed);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return input;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function serializeTarget(target = null) {
|
|
46
|
+
if (typeof target === "string") {
|
|
47
|
+
return {
|
|
48
|
+
kind: "remote",
|
|
49
|
+
id: null,
|
|
50
|
+
audience: null,
|
|
51
|
+
url: target,
|
|
52
|
+
runtimeId: null,
|
|
53
|
+
transport: null,
|
|
54
|
+
protocolId: null,
|
|
55
|
+
peerId: null,
|
|
56
|
+
startupPhase: null,
|
|
57
|
+
adapter: null,
|
|
58
|
+
disconnected: false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
kind: target?.kind ?? "remote",
|
|
63
|
+
id: target?.id ?? target?.targetId ?? target?.runtimeId ?? null,
|
|
64
|
+
audience: target?.audience ?? null,
|
|
65
|
+
url: target?.url ?? null,
|
|
66
|
+
runtimeId: target?.runtimeId ?? target?.runtime_id ?? null,
|
|
67
|
+
transport: target?.transport ?? null,
|
|
68
|
+
protocolId: target?.protocolId ?? target?.protocol_id ?? null,
|
|
69
|
+
peerId: target?.peerId ?? target?.peer_id ?? null,
|
|
70
|
+
startupPhase: target?.startupPhase ?? target?.startup_phase ?? null,
|
|
71
|
+
adapter: target?.adapter ?? target?.hostAdapter ?? null,
|
|
72
|
+
disconnected: Boolean(target?.disconnected ?? false),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeManifestExports(exports = {}) {
|
|
77
|
+
return {
|
|
78
|
+
bytesSymbol:
|
|
79
|
+
exports.bytesSymbol ??
|
|
80
|
+
exports.bytes_symbol ??
|
|
81
|
+
DefaultManifestExports.flowBytesSymbol,
|
|
82
|
+
sizeSymbol:
|
|
83
|
+
exports.sizeSymbol ??
|
|
84
|
+
exports.size_symbol ??
|
|
85
|
+
DefaultManifestExports.flowSizeSymbol,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeRuntimeExports(exports = {}) {
|
|
90
|
+
return {
|
|
91
|
+
mallocSymbol: exports.mallocSymbol ?? exports.malloc_symbol ?? null,
|
|
92
|
+
freeSymbol: exports.freeSymbol ?? exports.free_symbol ?? null,
|
|
93
|
+
descriptorSymbol:
|
|
94
|
+
exports.descriptorSymbol ?? exports.descriptor_symbol ?? null,
|
|
95
|
+
typeDescriptorsSymbol:
|
|
96
|
+
exports.typeDescriptorsSymbol ?? exports.type_descriptors_symbol ?? null,
|
|
97
|
+
typeDescriptorCountSymbol:
|
|
98
|
+
exports.typeDescriptorCountSymbol ??
|
|
99
|
+
exports.type_descriptor_count_symbol ??
|
|
100
|
+
null,
|
|
101
|
+
acceptedTypeIndicesSymbol:
|
|
102
|
+
exports.acceptedTypeIndicesSymbol ??
|
|
103
|
+
exports.accepted_type_indices_symbol ??
|
|
104
|
+
null,
|
|
105
|
+
acceptedTypeIndexCountSymbol:
|
|
106
|
+
exports.acceptedTypeIndexCountSymbol ??
|
|
107
|
+
exports.accepted_type_index_count_symbol ??
|
|
108
|
+
null,
|
|
109
|
+
triggerDescriptorsSymbol:
|
|
110
|
+
exports.triggerDescriptorsSymbol ??
|
|
111
|
+
exports.trigger_descriptors_symbol ??
|
|
112
|
+
null,
|
|
113
|
+
triggerDescriptorCountSymbol:
|
|
114
|
+
exports.triggerDescriptorCountSymbol ??
|
|
115
|
+
exports.trigger_descriptor_count_symbol ??
|
|
116
|
+
null,
|
|
117
|
+
nodeDescriptorsSymbol:
|
|
118
|
+
exports.nodeDescriptorsSymbol ?? exports.node_descriptors_symbol ?? null,
|
|
119
|
+
nodeDescriptorCountSymbol:
|
|
120
|
+
exports.nodeDescriptorCountSymbol ??
|
|
121
|
+
exports.node_descriptor_count_symbol ??
|
|
122
|
+
null,
|
|
123
|
+
nodeDispatchDescriptorsSymbol:
|
|
124
|
+
exports.nodeDispatchDescriptorsSymbol ??
|
|
125
|
+
exports.node_dispatch_descriptors_symbol ??
|
|
126
|
+
null,
|
|
127
|
+
nodeDispatchDescriptorCountSymbol:
|
|
128
|
+
exports.nodeDispatchDescriptorCountSymbol ??
|
|
129
|
+
exports.node_dispatch_descriptor_count_symbol ??
|
|
130
|
+
null,
|
|
131
|
+
edgeDescriptorsSymbol:
|
|
132
|
+
exports.edgeDescriptorsSymbol ?? exports.edge_descriptors_symbol ?? null,
|
|
133
|
+
edgeDescriptorCountSymbol:
|
|
134
|
+
exports.edgeDescriptorCountSymbol ??
|
|
135
|
+
exports.edge_descriptor_count_symbol ??
|
|
136
|
+
null,
|
|
137
|
+
triggerBindingDescriptorsSymbol:
|
|
138
|
+
exports.triggerBindingDescriptorsSymbol ??
|
|
139
|
+
exports.trigger_binding_descriptors_symbol ??
|
|
140
|
+
null,
|
|
141
|
+
triggerBindingDescriptorCountSymbol:
|
|
142
|
+
exports.triggerBindingDescriptorCountSymbol ??
|
|
143
|
+
exports.trigger_binding_descriptor_count_symbol ??
|
|
144
|
+
null,
|
|
145
|
+
dependencyDescriptorsSymbol:
|
|
146
|
+
exports.dependencyDescriptorsSymbol ??
|
|
147
|
+
exports.dependency_descriptors_symbol ??
|
|
148
|
+
null,
|
|
149
|
+
dependencyCountSymbol:
|
|
150
|
+
exports.dependencyCountSymbol ?? exports.dependency_count_symbol ?? null,
|
|
151
|
+
resetStateSymbol:
|
|
152
|
+
exports.resetStateSymbol ?? exports.reset_state_symbol ?? null,
|
|
153
|
+
ingressDescriptorsSymbol:
|
|
154
|
+
exports.ingressDescriptorsSymbol ??
|
|
155
|
+
exports.ingress_descriptors_symbol ??
|
|
156
|
+
null,
|
|
157
|
+
ingressDescriptorCountSymbol:
|
|
158
|
+
exports.ingressDescriptorCountSymbol ??
|
|
159
|
+
exports.ingress_descriptor_count_symbol ??
|
|
160
|
+
null,
|
|
161
|
+
ingressFrameDescriptorsSymbol:
|
|
162
|
+
exports.ingressFrameDescriptorsSymbol ??
|
|
163
|
+
exports.ingress_frame_descriptors_symbol ??
|
|
164
|
+
null,
|
|
165
|
+
ingressFrameDescriptorCountSymbol:
|
|
166
|
+
exports.ingressFrameDescriptorCountSymbol ??
|
|
167
|
+
exports.ingress_frame_descriptor_count_symbol ??
|
|
168
|
+
null,
|
|
169
|
+
nodeIngressIndicesSymbol:
|
|
170
|
+
exports.nodeIngressIndicesSymbol ??
|
|
171
|
+
exports.node_ingress_indices_symbol ??
|
|
172
|
+
null,
|
|
173
|
+
nodeIngressIndexCountSymbol:
|
|
174
|
+
exports.nodeIngressIndexCountSymbol ??
|
|
175
|
+
exports.node_ingress_index_count_symbol ??
|
|
176
|
+
null,
|
|
177
|
+
externalInterfaceDescriptorsSymbol:
|
|
178
|
+
exports.externalInterfaceDescriptorsSymbol ??
|
|
179
|
+
exports.external_interface_descriptors_symbol ??
|
|
180
|
+
null,
|
|
181
|
+
externalInterfaceDescriptorCountSymbol:
|
|
182
|
+
exports.externalInterfaceDescriptorCountSymbol ??
|
|
183
|
+
exports.external_interface_descriptor_count_symbol ??
|
|
184
|
+
null,
|
|
185
|
+
ingressStatesSymbol:
|
|
186
|
+
exports.ingressStatesSymbol ?? exports.ingress_states_symbol ?? null,
|
|
187
|
+
ingressStateCountSymbol:
|
|
188
|
+
exports.ingressStateCountSymbol ??
|
|
189
|
+
exports.ingress_state_count_symbol ??
|
|
190
|
+
null,
|
|
191
|
+
nodeStatesSymbol:
|
|
192
|
+
exports.nodeStatesSymbol ?? exports.node_states_symbol ?? null,
|
|
193
|
+
nodeStateCountSymbol:
|
|
194
|
+
exports.nodeStateCountSymbol ?? exports.node_state_count_symbol ?? null,
|
|
195
|
+
currentInvocationDescriptorSymbol:
|
|
196
|
+
exports.currentInvocationDescriptorSymbol ??
|
|
197
|
+
exports.current_invocation_descriptor_symbol ??
|
|
198
|
+
null,
|
|
199
|
+
prepareInvocationDescriptorSymbol:
|
|
200
|
+
exports.prepareInvocationDescriptorSymbol ??
|
|
201
|
+
exports.prepare_invocation_descriptor_symbol ??
|
|
202
|
+
null,
|
|
203
|
+
enqueueTriggerSymbol:
|
|
204
|
+
exports.enqueueTriggerSymbol ?? exports.enqueue_trigger_symbol ?? null,
|
|
205
|
+
enqueueTriggerFrameSymbol:
|
|
206
|
+
exports.enqueueTriggerFrameSymbol ??
|
|
207
|
+
exports.enqueue_trigger_frame_symbol ??
|
|
208
|
+
null,
|
|
209
|
+
enqueueEdgeSymbol:
|
|
210
|
+
exports.enqueueEdgeSymbol ?? exports.enqueue_edge_symbol ?? null,
|
|
211
|
+
enqueueEdgeFrameSymbol:
|
|
212
|
+
exports.enqueueEdgeFrameSymbol ??
|
|
213
|
+
exports.enqueue_edge_frame_symbol ??
|
|
214
|
+
null,
|
|
215
|
+
readyNodeSymbol:
|
|
216
|
+
exports.readyNodeSymbol ?? exports.ready_node_symbol ?? null,
|
|
217
|
+
beginInvocationSymbol:
|
|
218
|
+
exports.beginInvocationSymbol ?? exports.begin_invocation_symbol ?? null,
|
|
219
|
+
completeInvocationSymbol:
|
|
220
|
+
exports.completeInvocationSymbol ??
|
|
221
|
+
exports.complete_invocation_symbol ??
|
|
222
|
+
null,
|
|
223
|
+
applyInvocationResultSymbol:
|
|
224
|
+
exports.applyInvocationResultSymbol ??
|
|
225
|
+
exports.apply_invocation_result_symbol ??
|
|
226
|
+
null,
|
|
227
|
+
dispatchHostInvocationSymbol:
|
|
228
|
+
exports.dispatchHostInvocationSymbol ??
|
|
229
|
+
exports.dispatch_host_invocation_symbol ??
|
|
230
|
+
null,
|
|
231
|
+
drainWithHostDispatchSymbol:
|
|
232
|
+
exports.drainWithHostDispatchSymbol ??
|
|
233
|
+
exports.drain_with_host_dispatch_symbol ??
|
|
234
|
+
null,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function normalizeCompiledArtifact(artifact = {}) {
|
|
239
|
+
if (artifact.wasm === undefined || artifact.wasm === null) {
|
|
240
|
+
throw new Error("Compiled flow artifact must include wasm bytes.");
|
|
241
|
+
}
|
|
242
|
+
if (
|
|
243
|
+
artifact.manifestBuffer === undefined &&
|
|
244
|
+
artifact.manifest_buffer === undefined
|
|
245
|
+
) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
"Compiled flow artifact must include an embedded FlatBuffer manifest.",
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const wasm = toUint8Array(artifact.wasm);
|
|
252
|
+
const manifestBuffer = toUint8Array(
|
|
253
|
+
artifact.manifestBuffer ?? artifact.manifest_buffer,
|
|
254
|
+
);
|
|
255
|
+
if (wasm.length === 0) {
|
|
256
|
+
throw new Error("Compiled flow artifact must include wasm bytes.");
|
|
257
|
+
}
|
|
258
|
+
if (manifestBuffer.length === 0) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
"Compiled flow artifact must include an embedded FlatBuffer manifest.",
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const graphHash = artifact.graphHash ?? bytesToHex(await sha256Bytes(wasm));
|
|
265
|
+
const manifestHash =
|
|
266
|
+
artifact.manifestHash ?? bytesToHex(await sha256Bytes(manifestBuffer));
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
artifactId:
|
|
270
|
+
artifact.artifactId ??
|
|
271
|
+
`${artifact.programId ?? "flow"}:${String(graphHash).slice(0, 16)}`,
|
|
272
|
+
programId: String(artifact.programId ?? "").trim(),
|
|
273
|
+
format: artifact.format ?? "application/wasm",
|
|
274
|
+
runtimeModel:
|
|
275
|
+
artifact.runtimeModel ?? artifact.runtime_model ?? "compiled-cpp-wasm",
|
|
276
|
+
wasm,
|
|
277
|
+
manifestBuffer,
|
|
278
|
+
manifestExports: normalizeManifestExports(
|
|
279
|
+
artifact.manifestExports ?? artifact.manifest_exports,
|
|
280
|
+
),
|
|
281
|
+
runtimeExports: normalizeRuntimeExports(
|
|
282
|
+
artifact.runtimeExports ?? artifact.runtime_exports,
|
|
283
|
+
),
|
|
284
|
+
entrypoint: artifact.entrypoint ?? "_start",
|
|
285
|
+
graphHash,
|
|
286
|
+
manifestHash,
|
|
287
|
+
requiredCapabilities: Array.isArray(artifact.requiredCapabilities)
|
|
288
|
+
? artifact.requiredCapabilities.map((value) => String(value))
|
|
289
|
+
: [],
|
|
290
|
+
pluginVersions: Array.isArray(artifact.pluginVersions)
|
|
291
|
+
? artifact.pluginVersions
|
|
292
|
+
: [],
|
|
293
|
+
schemaBindings: Array.isArray(artifact.schemaBindings)
|
|
294
|
+
? artifact.schemaBindings
|
|
295
|
+
: [],
|
|
296
|
+
abiVersion: Number(artifact.abiVersion ?? artifact.abi_version ?? 1),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function serializeCompiledArtifact(artifact) {
|
|
301
|
+
return {
|
|
302
|
+
artifactId: artifact.artifactId,
|
|
303
|
+
programId: artifact.programId,
|
|
304
|
+
format: artifact.format,
|
|
305
|
+
runtimeModel: artifact.runtimeModel,
|
|
306
|
+
wasmBase64: bytesToBase64(artifact.wasm),
|
|
307
|
+
manifestBase64: bytesToBase64(artifact.manifestBuffer),
|
|
308
|
+
manifestExports: artifact.manifestExports,
|
|
309
|
+
runtimeExports: artifact.runtimeExports,
|
|
310
|
+
entrypoint: artifact.entrypoint,
|
|
311
|
+
graphHash: artifact.graphHash,
|
|
312
|
+
manifestHash: artifact.manifestHash,
|
|
313
|
+
requiredCapabilities: artifact.requiredCapabilities,
|
|
314
|
+
pluginVersions: artifact.pluginVersions,
|
|
315
|
+
schemaBindings: artifact.schemaBindings,
|
|
316
|
+
abiVersion: artifact.abiVersion,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function deserializeCompiledArtifact(serializedArtifact = {}) {
|
|
321
|
+
if (
|
|
322
|
+
serializedArtifact.wasmBase64 !== undefined ||
|
|
323
|
+
serializedArtifact.manifestBase64 !== undefined
|
|
324
|
+
) {
|
|
325
|
+
return normalizeCompiledArtifact({
|
|
326
|
+
...serializedArtifact,
|
|
327
|
+
wasm: base64ToBytes(serializedArtifact.wasmBase64),
|
|
328
|
+
manifestBuffer: base64ToBytes(serializedArtifact.manifestBase64),
|
|
329
|
+
manifestExports:
|
|
330
|
+
serializedArtifact.manifestExports ??
|
|
331
|
+
serializedArtifact.manifest_exports,
|
|
332
|
+
runtimeExports:
|
|
333
|
+
serializedArtifact.runtimeExports ?? serializedArtifact.runtime_exports,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return normalizeCompiledArtifact(serializedArtifact);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function resolveCompiledArtifactEnvelope(
|
|
341
|
+
input = {},
|
|
342
|
+
options = {},
|
|
343
|
+
) {
|
|
344
|
+
const normalizedInput = maybeParseStructuredInput(input);
|
|
345
|
+
if (normalizedInput && typeof normalizedInput === "object") {
|
|
346
|
+
if (normalizedInput.encrypted === true && normalizedInput.envelope) {
|
|
347
|
+
if (typeof options.decrypt !== "function") {
|
|
348
|
+
throw new Error(
|
|
349
|
+
"Encrypted compiled flow deployments must be decrypted before host startup.",
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const decrypted = await options.decrypt(
|
|
353
|
+
normalizedInput.envelope,
|
|
354
|
+
normalizedInput,
|
|
355
|
+
);
|
|
356
|
+
return resolveCompiledArtifactEnvelope(decrypted, options);
|
|
357
|
+
}
|
|
358
|
+
if (
|
|
359
|
+
normalizedInput.payload &&
|
|
360
|
+
typeof normalizedInput.payload === "object" &&
|
|
361
|
+
normalizedInput.payload.kind === "compiled-flow-wasm-deployment"
|
|
362
|
+
) {
|
|
363
|
+
return normalizedInput.payload;
|
|
364
|
+
}
|
|
365
|
+
if (normalizedInput.kind === "compiled-flow-wasm-deployment") {
|
|
366
|
+
return normalizedInput;
|
|
367
|
+
}
|
|
368
|
+
if (normalizedInput.artifact) {
|
|
369
|
+
return {
|
|
370
|
+
kind: "compiled-flow-artifact-input",
|
|
371
|
+
artifact: normalizedInput.artifact,
|
|
372
|
+
source: normalizedInput,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
kind: "compiled-flow-artifact-input",
|
|
379
|
+
artifact: normalizedInput,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export async function resolveCompiledArtifactInput(input = {}, options = {}) {
|
|
384
|
+
const resolved = await resolveCompiledArtifactEnvelope(input, options);
|
|
385
|
+
if (
|
|
386
|
+
resolved &&
|
|
387
|
+
typeof resolved === "object" &&
|
|
388
|
+
resolved.kind === "compiled-flow-wasm-deployment"
|
|
389
|
+
) {
|
|
390
|
+
return deserializeCompiledArtifact(resolved.artifact);
|
|
391
|
+
}
|
|
392
|
+
if (resolved && typeof resolved === "object" && resolved.artifact) {
|
|
393
|
+
return deserializeCompiledArtifact(resolved.artifact);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return deserializeCompiledArtifact(resolved);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export class FlowDeploymentClient {
|
|
400
|
+
#fetch;
|
|
401
|
+
|
|
402
|
+
#now;
|
|
403
|
+
|
|
404
|
+
constructor(options = {}) {
|
|
405
|
+
this.#fetch = options.fetchImpl ?? globalThis.fetch ?? null;
|
|
406
|
+
this.#now = options.now ?? (() => Date.now());
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async prepareDeployment({
|
|
410
|
+
artifact,
|
|
411
|
+
target,
|
|
412
|
+
signer = null,
|
|
413
|
+
requiredCapabilities = null,
|
|
414
|
+
recipientPublicKey = null,
|
|
415
|
+
authorization = null,
|
|
416
|
+
encrypt = undefined,
|
|
417
|
+
} = {}) {
|
|
418
|
+
const normalizedArtifact = await normalizeCompiledArtifact(artifact);
|
|
419
|
+
const targetDescriptor = serializeTarget(target);
|
|
420
|
+
const capabilities =
|
|
421
|
+
requiredCapabilities ?? normalizedArtifact.requiredCapabilities;
|
|
422
|
+
const authorizationPayload =
|
|
423
|
+
authorization ??
|
|
424
|
+
await createDeploymentAuthorization({
|
|
425
|
+
artifact: normalizedArtifact,
|
|
426
|
+
target: targetDescriptor,
|
|
427
|
+
capabilities,
|
|
428
|
+
issuedAt: this.#now(),
|
|
429
|
+
});
|
|
430
|
+
const signedAuthorization = signer
|
|
431
|
+
? await signAuthorization({
|
|
432
|
+
authorization: authorizationPayload,
|
|
433
|
+
signer,
|
|
434
|
+
})
|
|
435
|
+
: null;
|
|
436
|
+
|
|
437
|
+
if (signedAuthorization) {
|
|
438
|
+
assertDeploymentAuthorization({
|
|
439
|
+
envelope: signedAuthorization,
|
|
440
|
+
artifact: normalizedArtifact,
|
|
441
|
+
target: targetDescriptor,
|
|
442
|
+
requiredCapabilities: capabilities,
|
|
443
|
+
now: this.#now(),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const payload = {
|
|
448
|
+
version: 1,
|
|
449
|
+
kind: "compiled-flow-wasm-deployment",
|
|
450
|
+
artifact: serializeCompiledArtifact(normalizedArtifact),
|
|
451
|
+
authorization: signedAuthorization,
|
|
452
|
+
target: targetDescriptor,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const shouldEncrypt =
|
|
456
|
+
encrypt ?? Boolean(recipientPublicKey ?? target?.recipientPublicKey);
|
|
457
|
+
if (shouldEncrypt) {
|
|
458
|
+
return {
|
|
459
|
+
version: 1,
|
|
460
|
+
encrypted: true,
|
|
461
|
+
envelope: await encryptJsonForRecipient({
|
|
462
|
+
payload,
|
|
463
|
+
recipientPublicKey: recipientPublicKey ?? target?.recipientPublicKey,
|
|
464
|
+
context: `sdn-flow/deploy:${normalizedArtifact.programId}`,
|
|
465
|
+
}),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
version: 1,
|
|
471
|
+
encrypted: false,
|
|
472
|
+
payload,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async deployLocal({ target, deployment }) {
|
|
477
|
+
if (!target || typeof target.deploy !== "function") {
|
|
478
|
+
throw new Error("Local deployment target must expose deploy().");
|
|
479
|
+
}
|
|
480
|
+
return target.deploy(deployment);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async deployRemote({ target, deployment }) {
|
|
484
|
+
if (!this.#fetch) {
|
|
485
|
+
throw new Error("Remote deployment requires fetch.");
|
|
486
|
+
}
|
|
487
|
+
const url = typeof target === "string" ? target : target?.url;
|
|
488
|
+
if (!url) {
|
|
489
|
+
throw new Error("Remote deployment target must define url.");
|
|
490
|
+
}
|
|
491
|
+
const response = await this.#fetch(url, {
|
|
492
|
+
method: "POST",
|
|
493
|
+
headers: {
|
|
494
|
+
"content-type": "application/json",
|
|
495
|
+
...(target?.headers ?? {}),
|
|
496
|
+
},
|
|
497
|
+
body: JSON.stringify(deployment),
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
const errorBody = contentType.includes("application/json")
|
|
503
|
+
? await response.json()
|
|
504
|
+
: await response.text();
|
|
505
|
+
throw new Error(
|
|
506
|
+
`Remote deployment failed (${response.status}): ${JSON.stringify(errorBody)}`,
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
return contentType.includes("application/json")
|
|
510
|
+
? response.json()
|
|
511
|
+
: response.text();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async deploy(options = {}) {
|
|
515
|
+
const deployment = await this.prepareDeployment(options);
|
|
516
|
+
if (
|
|
517
|
+
options.target?.kind === "local" ||
|
|
518
|
+
typeof options.target?.deploy === "function"
|
|
519
|
+
) {
|
|
520
|
+
return this.deployLocal({
|
|
521
|
+
target: options.target,
|
|
522
|
+
deployment,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
return this.deployRemote({
|
|
526
|
+
target: options.target,
|
|
527
|
+
deployment,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export default FlowDeploymentClient;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BackpressurePolicy,
|
|
3
|
+
DrainPolicy,
|
|
4
|
+
NodeKind,
|
|
5
|
+
TriggerKind,
|
|
6
|
+
normalizeProgram,
|
|
7
|
+
} from "../runtime/index.js";
|
|
8
|
+
import { summarizeProgramRequirements } from "./requirements.js";
|
|
9
|
+
|
|
10
|
+
export function createSinglePluginFlow(options = {}) {
|
|
11
|
+
const pluginId = String(options.pluginId ?? "").trim();
|
|
12
|
+
const methodId = String(options.methodId ?? "").trim();
|
|
13
|
+
if (!pluginId || !methodId) {
|
|
14
|
+
throw new Error("createSinglePluginFlow requires pluginId and methodId.");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const nodeId = String(options.nodeId ?? "node-1").trim();
|
|
18
|
+
const inputPortId = String(options.inputPortId ?? "in").trim();
|
|
19
|
+
const triggerId = options.trigger ? String(options.trigger.triggerId ?? "trigger-1").trim() : null;
|
|
20
|
+
|
|
21
|
+
return normalizeProgram({
|
|
22
|
+
programId: options.programId ?? `${pluginId}.${methodId}`,
|
|
23
|
+
name: options.name ?? `${pluginId}:${methodId}`,
|
|
24
|
+
version: options.version ?? "0.1.0",
|
|
25
|
+
nodes: [
|
|
26
|
+
{
|
|
27
|
+
nodeId,
|
|
28
|
+
pluginId,
|
|
29
|
+
methodId,
|
|
30
|
+
kind: options.nodeKind ?? NodeKind.TRANSFORM,
|
|
31
|
+
drainPolicy: options.drainPolicy ?? DrainPolicy.DRAIN_UNTIL_YIELD,
|
|
32
|
+
timeSliceMicros: options.timeSliceMicros ?? 1000,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
edges: Array.isArray(options.edges) ? options.edges : [],
|
|
36
|
+
triggers: triggerId
|
|
37
|
+
? [
|
|
38
|
+
{
|
|
39
|
+
triggerId,
|
|
40
|
+
kind: options.trigger.kind ?? TriggerKind.MANUAL,
|
|
41
|
+
source: options.trigger.source ?? null,
|
|
42
|
+
protocolId: options.trigger.protocolId ?? null,
|
|
43
|
+
defaultIntervalMs: options.trigger.defaultIntervalMs ?? 0,
|
|
44
|
+
acceptedTypes: options.trigger.acceptedTypes ?? [],
|
|
45
|
+
description: options.trigger.description ?? null,
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
: [],
|
|
49
|
+
triggerBindings: triggerId
|
|
50
|
+
? [
|
|
51
|
+
{
|
|
52
|
+
triggerId,
|
|
53
|
+
targetNodeId: nodeId,
|
|
54
|
+
targetPortId: inputPortId,
|
|
55
|
+
backpressurePolicy:
|
|
56
|
+
options.trigger.backpressurePolicy ?? BackpressurePolicy.QUEUE,
|
|
57
|
+
queueDepth: options.trigger.queueDepth ?? 1,
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: [],
|
|
61
|
+
requiredPlugins: [pluginId],
|
|
62
|
+
description:
|
|
63
|
+
options.description ??
|
|
64
|
+
"Single-plugin flow. A single plugin is modeled as a one-node flow.",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class FlowDesignerSession {
|
|
69
|
+
#program;
|
|
70
|
+
|
|
71
|
+
constructor(options = {}) {
|
|
72
|
+
this.#program = normalizeProgram(options.program ?? {});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static fromSinglePlugin(options = {}) {
|
|
76
|
+
return new FlowDesignerSession({
|
|
77
|
+
program: createSinglePluginFlow(options),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
loadProgram(program) {
|
|
82
|
+
this.#program = normalizeProgram(program);
|
|
83
|
+
return this.#program;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
snapshot() {
|
|
87
|
+
return normalizeProgram(this.#program);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
inspectRequirements({ registry = null, manifests = [] } = {}) {
|
|
91
|
+
return summarizeProgramRequirements({
|
|
92
|
+
program: this.#program,
|
|
93
|
+
registry,
|
|
94
|
+
manifests,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
addNode(node) {
|
|
99
|
+
const program = this.snapshot();
|
|
100
|
+
program.nodes.push(node);
|
|
101
|
+
this.#program = normalizeProgram(program);
|
|
102
|
+
return this.#program;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
addEdge(edge) {
|
|
106
|
+
const program = this.snapshot();
|
|
107
|
+
program.edges.push(edge);
|
|
108
|
+
this.#program = normalizeProgram(program);
|
|
109
|
+
return this.#program;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
addTrigger(trigger, binding = null) {
|
|
113
|
+
const program = this.snapshot();
|
|
114
|
+
program.triggers.push(trigger);
|
|
115
|
+
if (binding) {
|
|
116
|
+
program.triggerBindings.push(binding);
|
|
117
|
+
}
|
|
118
|
+
this.#program = normalizeProgram(program);
|
|
119
|
+
return this.#program;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async compile({ compiler, target = null, metadata = null } = {}) {
|
|
123
|
+
if (!compiler || typeof compiler.compile !== "function") {
|
|
124
|
+
throw new Error("FlowDesignerSession.compile requires a compiler adapter.");
|
|
125
|
+
}
|
|
126
|
+
return compiler.compile({
|
|
127
|
+
program: this.snapshot(),
|
|
128
|
+
target,
|
|
129
|
+
metadata,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async deploy({
|
|
134
|
+
compiler,
|
|
135
|
+
deploymentClient,
|
|
136
|
+
target,
|
|
137
|
+
signer,
|
|
138
|
+
recipientPublicKey = null,
|
|
139
|
+
requiredCapabilities = null,
|
|
140
|
+
metadata = null,
|
|
141
|
+
} = {}) {
|
|
142
|
+
if (!deploymentClient || typeof deploymentClient.deploy !== "function") {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"FlowDesignerSession.deploy requires a deployment client.",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const artifact = await this.compile({ compiler, target, metadata });
|
|
148
|
+
return deploymentClient.deploy({
|
|
149
|
+
artifact,
|
|
150
|
+
target,
|
|
151
|
+
signer,
|
|
152
|
+
recipientPublicKey,
|
|
153
|
+
requiredCapabilities,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export default FlowDesignerSession;
|