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,105 @@
|
|
|
1
|
+
import { createInstalledFlowApp } from "./workspace.js";
|
|
2
|
+
import {
|
|
3
|
+
HostedRuntimeBindingDirection,
|
|
4
|
+
HostedRuntimeTransport,
|
|
5
|
+
} from "./constants.js";
|
|
6
|
+
import { normalizeHostedRuntimePlan } from "./normalize.js";
|
|
7
|
+
|
|
8
|
+
function normalizeString(value, fallback = null) {
|
|
9
|
+
if (typeof value !== "string") {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
const normalized = value.trim();
|
|
13
|
+
return normalized.length > 0 ? normalized : fallback;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function listInstalledFlowHttpBindings(options = {}) {
|
|
17
|
+
const workspace = options.workspace ?? null;
|
|
18
|
+
const rawHostPlan = options.hostPlan ?? workspace?.hostPlan ?? null;
|
|
19
|
+
const hostPlan = rawHostPlan ? normalizeHostedRuntimePlan(rawHostPlan) : null;
|
|
20
|
+
const programId =
|
|
21
|
+
normalizeString(options.programId, null) ??
|
|
22
|
+
normalizeString(workspace?.program?.programId, null);
|
|
23
|
+
const runtimeBindings = [];
|
|
24
|
+
|
|
25
|
+
for (const runtime of hostPlan?.runtimes ?? []) {
|
|
26
|
+
if (programId && runtime.programId && runtime.programId !== programId) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
for (const binding of runtime.bindings ?? []) {
|
|
30
|
+
if (binding.direction !== HostedRuntimeBindingDirection.LISTEN) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (binding.transport !== HostedRuntimeTransport.HTTP) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
runtimeBindings.push({
|
|
37
|
+
runtimeId: runtime.runtimeId,
|
|
38
|
+
programId: runtime.programId ?? null,
|
|
39
|
+
adapter: runtime.adapter ?? hostPlan?.adapter ?? null,
|
|
40
|
+
engine: runtime.engine ?? hostPlan?.engine ?? null,
|
|
41
|
+
binding,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return runtimeBindings;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toListenerRecord(bindingContext, handle) {
|
|
50
|
+
const close =
|
|
51
|
+
typeof handle === "function"
|
|
52
|
+
? handle
|
|
53
|
+
: typeof handle?.close === "function"
|
|
54
|
+
? handle.close.bind(handle)
|
|
55
|
+
: async () => {};
|
|
56
|
+
return {
|
|
57
|
+
...bindingContext,
|
|
58
|
+
handle: handle ?? null,
|
|
59
|
+
async close() {
|
|
60
|
+
await close();
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function startInstalledFlowAppHost(options = {}) {
|
|
66
|
+
const app = options.app ?? (await createInstalledFlowApp(options));
|
|
67
|
+
const startup = await app.start();
|
|
68
|
+
const serveHttp = options.serveHttp ?? null;
|
|
69
|
+
const bindingContexts = listInstalledFlowHttpBindings({
|
|
70
|
+
workspace: app.getWorkspace(),
|
|
71
|
+
});
|
|
72
|
+
const listeners = [];
|
|
73
|
+
|
|
74
|
+
if (typeof serveHttp === "function") {
|
|
75
|
+
for (const bindingContext of bindingContexts) {
|
|
76
|
+
const handle = await serveHttp({
|
|
77
|
+
...bindingContext,
|
|
78
|
+
app,
|
|
79
|
+
workspace: app.getWorkspace(),
|
|
80
|
+
handler: app.fetchHandler,
|
|
81
|
+
});
|
|
82
|
+
listeners.push(toListenerRecord(bindingContext, handle));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
app,
|
|
88
|
+
startup,
|
|
89
|
+
listeners,
|
|
90
|
+
listBindings() {
|
|
91
|
+
return bindingContexts;
|
|
92
|
+
},
|
|
93
|
+
async stop() {
|
|
94
|
+
for (const listener of listeners) {
|
|
95
|
+
await listener.close();
|
|
96
|
+
}
|
|
97
|
+
app.stop();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default {
|
|
103
|
+
listInstalledFlowHttpBindings,
|
|
104
|
+
startInstalledFlowAppHost,
|
|
105
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { HostedRuntimeEngine } from "./constants.js";
|
|
2
|
+
import {
|
|
3
|
+
startInstalledFlowBrowserFetchHost,
|
|
4
|
+
} from "./browserHostAdapters.js";
|
|
5
|
+
import {
|
|
6
|
+
startInstalledFlowBunHttpHost,
|
|
7
|
+
startInstalledFlowDenoHttpHost,
|
|
8
|
+
startInstalledFlowNodeHttpHost,
|
|
9
|
+
} from "./httpHostAdapters.js";
|
|
10
|
+
import { normalizeHostedRuntimeEngine } from "./profile.js";
|
|
11
|
+
import {
|
|
12
|
+
readInstalledFlowWorkspace,
|
|
13
|
+
resolveInstalledFlowWorkspace,
|
|
14
|
+
} from "./workspace.js";
|
|
15
|
+
|
|
16
|
+
function resolveGlobalEngineFallback(options = {}) {
|
|
17
|
+
if (typeof globalThis.Bun?.serve === "function") {
|
|
18
|
+
return HostedRuntimeEngine.BUN;
|
|
19
|
+
}
|
|
20
|
+
if (typeof globalThis.Deno?.serve === "function") {
|
|
21
|
+
return HostedRuntimeEngine.DENO;
|
|
22
|
+
}
|
|
23
|
+
if (
|
|
24
|
+
typeof options.addEventListener === "function" ||
|
|
25
|
+
typeof globalThis.addEventListener === "function"
|
|
26
|
+
) {
|
|
27
|
+
return HostedRuntimeEngine.BROWSER;
|
|
28
|
+
}
|
|
29
|
+
return HostedRuntimeEngine.NODE;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveWorkspaceEngine(workspace = null) {
|
|
33
|
+
return normalizeHostedRuntimeEngine(
|
|
34
|
+
workspace?.engine ?? workspace?.hostPlan?.engine,
|
|
35
|
+
null,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function resolveInstalledFlowAutoHostEngine(options = {}) {
|
|
40
|
+
const explicitEngine = normalizeHostedRuntimeEngine(options.engine, null);
|
|
41
|
+
if (explicitEngine) {
|
|
42
|
+
return explicitEngine;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof options.app?.getWorkspace === "function") {
|
|
46
|
+
const appEngine = resolveWorkspaceEngine(options.app.getWorkspace());
|
|
47
|
+
if (appEngine) {
|
|
48
|
+
return appEngine;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (options.workspace !== undefined) {
|
|
53
|
+
const workspace = await resolveInstalledFlowWorkspace(
|
|
54
|
+
options.workspace,
|
|
55
|
+
options,
|
|
56
|
+
);
|
|
57
|
+
const workspaceEngine = resolveWorkspaceEngine(workspace);
|
|
58
|
+
if (workspaceEngine) {
|
|
59
|
+
return workspaceEngine;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof options.workspacePath === "string" && options.workspacePath.trim()) {
|
|
64
|
+
const workspace = await readInstalledFlowWorkspace(options.workspacePath, options);
|
|
65
|
+
const workspaceEngine = resolveWorkspaceEngine(workspace);
|
|
66
|
+
if (workspaceEngine) {
|
|
67
|
+
return workspaceEngine;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return resolveGlobalEngineFallback(options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function startInstalledFlowAutoHost(options = {}) {
|
|
75
|
+
const engine = await resolveInstalledFlowAutoHostEngine(options);
|
|
76
|
+
const startBrowserHost =
|
|
77
|
+
options.startBrowserHost ?? startInstalledFlowBrowserFetchHost;
|
|
78
|
+
const startDenoHost =
|
|
79
|
+
options.startDenoHost ?? startInstalledFlowDenoHttpHost;
|
|
80
|
+
const startBunHost =
|
|
81
|
+
options.startBunHost ?? startInstalledFlowBunHttpHost;
|
|
82
|
+
const startNodeHost =
|
|
83
|
+
options.startNodeHost ?? startInstalledFlowNodeHttpHost;
|
|
84
|
+
|
|
85
|
+
switch (engine) {
|
|
86
|
+
case HostedRuntimeEngine.BROWSER:
|
|
87
|
+
return startBrowserHost({
|
|
88
|
+
...options,
|
|
89
|
+
engine,
|
|
90
|
+
});
|
|
91
|
+
case HostedRuntimeEngine.DENO:
|
|
92
|
+
return startDenoHost({
|
|
93
|
+
...options,
|
|
94
|
+
engine,
|
|
95
|
+
});
|
|
96
|
+
case HostedRuntimeEngine.BUN:
|
|
97
|
+
return startBunHost({
|
|
98
|
+
...options,
|
|
99
|
+
engine,
|
|
100
|
+
});
|
|
101
|
+
case HostedRuntimeEngine.NODE:
|
|
102
|
+
default:
|
|
103
|
+
return startNodeHost({
|
|
104
|
+
...options,
|
|
105
|
+
engine,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
resolveInstalledFlowAutoHostEngine,
|
|
112
|
+
startInstalledFlowAutoHost,
|
|
113
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { listInstalledFlowHttpBindings } from "./appHost.js";
|
|
2
|
+
import { createInstalledFlowApp } from "./workspace.js";
|
|
3
|
+
|
|
4
|
+
function normalizeString(value, fallback = null) {
|
|
5
|
+
if (typeof value !== "string") {
|
|
6
|
+
return fallback;
|
|
7
|
+
}
|
|
8
|
+
const normalized = value.trim();
|
|
9
|
+
return normalized.length > 0 ? normalized : fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resolveRequestUrl(request) {
|
|
13
|
+
if (request instanceof Request) {
|
|
14
|
+
return new URL(request.url);
|
|
15
|
+
}
|
|
16
|
+
const requestUrl = normalizeString(request?.url, null);
|
|
17
|
+
if (!requestUrl) {
|
|
18
|
+
throw new Error("Browser fetch host requires a Request or request-like object with url.");
|
|
19
|
+
}
|
|
20
|
+
return new URL(requestUrl);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function matchesInstalledFlowHttpBindingRequest(bindingContext, request) {
|
|
24
|
+
const requestUrl = resolveRequestUrl(request);
|
|
25
|
+
const bindingUrl = new URL(bindingContext.binding.url);
|
|
26
|
+
return requestUrl.pathname === bindingUrl.pathname;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createInstalledFlowBrowserFetchEventListener(options = {}) {
|
|
30
|
+
const app = options.app;
|
|
31
|
+
if (!app) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"createInstalledFlowBrowserFetchEventListener requires an installed flow app.",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
const bindingContexts =
|
|
37
|
+
options.bindingContexts ??
|
|
38
|
+
listInstalledFlowHttpBindings({
|
|
39
|
+
workspace: app.getWorkspace(),
|
|
40
|
+
});
|
|
41
|
+
const handler = options.handler ?? app.fetchHandler;
|
|
42
|
+
|
|
43
|
+
return function onFetch(event) {
|
|
44
|
+
const request = event?.request ?? event;
|
|
45
|
+
const bindingContext = bindingContexts.find((candidate) =>
|
|
46
|
+
matchesInstalledFlowHttpBindingRequest(candidate, request),
|
|
47
|
+
);
|
|
48
|
+
if (!bindingContext) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const responsePromise = Promise.resolve(
|
|
52
|
+
handler(request, {
|
|
53
|
+
event,
|
|
54
|
+
bindingContext,
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
if (typeof event?.respondWith === "function") {
|
|
58
|
+
event.respondWith(responsePromise);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return responsePromise;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function startInstalledFlowBrowserFetchHost(options = {}) {
|
|
66
|
+
const app = options.app ?? (await createInstalledFlowApp(options));
|
|
67
|
+
const startup = await app.start();
|
|
68
|
+
const bindingContexts = listInstalledFlowHttpBindings({
|
|
69
|
+
workspace: app.getWorkspace(),
|
|
70
|
+
});
|
|
71
|
+
const listener = createInstalledFlowBrowserFetchEventListener({
|
|
72
|
+
app,
|
|
73
|
+
bindingContexts,
|
|
74
|
+
handler: options.handler,
|
|
75
|
+
});
|
|
76
|
+
const addEventListenerFn =
|
|
77
|
+
options.addEventListener ??
|
|
78
|
+
globalThis.addEventListener?.bind(globalThis) ??
|
|
79
|
+
null;
|
|
80
|
+
const removeEventListenerFn =
|
|
81
|
+
options.removeEventListener ??
|
|
82
|
+
globalThis.removeEventListener?.bind(globalThis) ??
|
|
83
|
+
null;
|
|
84
|
+
|
|
85
|
+
if (typeof addEventListenerFn === "function") {
|
|
86
|
+
addEventListenerFn("fetch", listener);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
app,
|
|
91
|
+
startup,
|
|
92
|
+
bindingContexts,
|
|
93
|
+
handler: app.fetchHandler,
|
|
94
|
+
listener,
|
|
95
|
+
stop() {
|
|
96
|
+
if (typeof removeEventListenerFn === "function") {
|
|
97
|
+
removeEventListenerFn("fetch", listener);
|
|
98
|
+
}
|
|
99
|
+
app.stop();
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default {
|
|
105
|
+
createInstalledFlowBrowserFetchEventListener,
|
|
106
|
+
matchesInstalledFlowHttpBindingRequest,
|
|
107
|
+
startInstalledFlowBrowserFetchHost,
|
|
108
|
+
};
|