diff options
| author | Adam Malczewski <[email protected]> | 2026-06-04 23:34:18 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-04 23:34:18 +0900 |
| commit | dbcf2193d45b3cd6e51869dc9587b08d26a27f3e (patch) | |
| tree | 3648b48b0faa8f3da15991844f5ca7572bf6f03e /packages/kernel/src/host/dag.ts | |
| parent | 9b611d614d123462e50492d78202dae696b99aa2 (diff) | |
| download | dispatch-dbcf2193d45b3cd6e51869dc9587b08d26a27f3e.tar.gz dispatch-dbcf2193d45b3cd6e51869dc9587b08d26a27f3e.zip | |
feat(kernel): extension host — discovery, DAG resolve, apiVersion check, activate, HostAPI (wraps bus); 50 tests
Diffstat (limited to 'packages/kernel/src/host/dag.ts')
| -rw-r--r-- | packages/kernel/src/host/dag.ts | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/packages/kernel/src/host/dag.ts b/packages/kernel/src/host/dag.ts new file mode 100644 index 0000000..5cde2f5 --- /dev/null +++ b/packages/kernel/src/host/dag.ts @@ -0,0 +1,64 @@ +import type { Manifest } from "../contracts/extension.js"; + +export function resolveActivationOrder(manifests: readonly Manifest[]): Manifest[] { + const byId = new Map<string, Manifest>(); + for (const m of manifests) { + if (byId.has(m.id)) { + throw new Error(`Duplicate extension id: "${m.id}"`); + } + byId.set(m.id, m); + } + + for (const m of manifests) { + for (const dep of m.dependsOn ?? []) { + if (!byId.has(dep)) { + throw new Error(`Extension "${m.id}" depends on "${dep}", which is not available.`); + } + } + } + + const inDegree = new Map<string, number>(); + const dependents = new Map<string, string[]>(); + + for (const m of manifests) { + inDegree.set(m.id, 0); + dependents.set(m.id, []); + } + + for (const m of manifests) { + for (const dep of m.dependsOn ?? []) { + const list = dependents.get(dep); + if (list !== undefined) list.push(m.id); + inDegree.set(m.id, (inDegree.get(m.id) ?? 0) + 1); + } + } + + const queue: string[] = []; + for (const [id, deg] of inDegree) { + if (deg === 0) queue.push(id); + } + + const result: Manifest[] = []; + let idx = 0; + while (idx < queue.length) { + const id = queue[idx]; + if (id === undefined) break; + idx++; + const m = byId.get(id); + if (m === undefined) continue; + result.push(m); + for (const dep of dependents.get(id) ?? []) { + const newDeg = (inDegree.get(dep) ?? 1) - 1; + inDegree.set(dep, newDeg); + if (newDeg === 0) queue.push(dep); + } + } + + if (result.length !== manifests.length) { + const remaining = manifests.filter((m) => !result.some((r) => r.id === m.id)); + const ids = remaining.map((m) => m.id).join(", "); + throw new Error(`Dependency cycle detected among extensions: ${ids}`); + } + + return result; +} |
