dna-sdk 0.1.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/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/adapters/embedding/onnx.d.ts +32 -0
- package/dist/adapters/embedding/onnx.js +51 -0
- package/dist/adapters/filesystem/cache.d.ts +16 -0
- package/dist/adapters/filesystem/cache.js +113 -0
- package/dist/adapters/filesystem/index.d.ts +2 -0
- package/dist/adapters/filesystem/index.js +2 -0
- package/dist/adapters/filesystem/source.d.ts +82 -0
- package/dist/adapters/filesystem/source.js +357 -0
- package/dist/adapters/postgres/index.d.ts +2 -0
- package/dist/adapters/postgres/index.js +2 -0
- package/dist/adapters/postgres/migrations.d.ts +14 -0
- package/dist/adapters/postgres/migrations.js +88 -0
- package/dist/adapters/postgres/source.d.ts +172 -0
- package/dist/adapters/postgres/source.js +569 -0
- package/dist/adapters/resolvers/github.d.ts +12 -0
- package/dist/adapters/resolvers/github.js +47 -0
- package/dist/adapters/resolvers/http.d.ts +22 -0
- package/dist/adapters/resolvers/http.js +145 -0
- package/dist/adapters/resolvers/index.d.ts +3 -0
- package/dist/adapters/resolvers/index.js +3 -0
- package/dist/adapters/resolvers/local.d.ts +24 -0
- package/dist/adapters/resolvers/local.js +132 -0
- package/dist/adapters/search/driver.d.ts +45 -0
- package/dist/adapters/search/driver.js +138 -0
- package/dist/adapters/search/migrations.d.ts +26 -0
- package/dist/adapters/search/migrations.js +58 -0
- package/dist/adapters/search/rrf.d.ts +23 -0
- package/dist/adapters/search/rrf.js +42 -0
- package/dist/adapters/search/sqlite-vec.d.ts +66 -0
- package/dist/adapters/search/sqlite-vec.js +294 -0
- package/dist/bootstrap.d.ts +45 -0
- package/dist/bootstrap.js +134 -0
- package/dist/extensions/agentskills/DOCS.md +18 -0
- package/dist/extensions/agentskills.d.ts +28 -0
- package/dist/extensions/agentskills.js +252 -0
- package/dist/extensions/agentsmd/DOCS.md +18 -0
- package/dist/extensions/agentsmd.d.ts +22 -0
- package/dist/extensions/agentsmd.js +170 -0
- package/dist/extensions/audit/kinds/audit-log.kind.yaml +148 -0
- package/dist/extensions/audit.d.ts +25 -0
- package/dist/extensions/audit.js +62 -0
- package/dist/extensions/automation/kinds/automation.kind.yaml +296 -0
- package/dist/extensions/automation/query.d.ts +49 -0
- package/dist/extensions/automation/query.js +77 -0
- package/dist/extensions/automation/write-guards.d.ts +59 -0
- package/dist/extensions/automation/write-guards.js +153 -0
- package/dist/extensions/automation.d.ts +41 -0
- package/dist/extensions/automation.js +54 -0
- package/dist/extensions/collab.d.ts +16 -0
- package/dist/extensions/collab.js +79 -0
- package/dist/extensions/doc/kinds/doc.kind.yaml +118 -0
- package/dist/extensions/doc.d.ts +25 -0
- package/dist/extensions/doc.js +10 -0
- package/dist/extensions/eval/kinds/eval-baseline.kind.yaml +65 -0
- package/dist/extensions/eval/kinds/eval-case.kind.yaml +140 -0
- package/dist/extensions/eval/kinds/eval-run.kind.yaml +134 -0
- package/dist/extensions/eval/kinds/eval-suite.kind.yaml +87 -0
- package/dist/extensions/eval.d.ts +24 -0
- package/dist/extensions/eval.js +31 -0
- package/dist/extensions/evidence/kinds/evidence.kind.yaml +92 -0
- package/dist/extensions/evidence.d.ts +17 -0
- package/dist/extensions/evidence.js +94 -0
- package/dist/extensions/federation.d.ts +22 -0
- package/dist/extensions/federation.js +173 -0
- package/dist/extensions/guardrails/DOCS.md +20 -0
- package/dist/extensions/guardrails.d.ts +31 -0
- package/dist/extensions/guardrails.js +228 -0
- package/dist/extensions/helix/DOCS-Actor.md +20 -0
- package/dist/extensions/helix/DOCS-Agent.md +20 -0
- package/dist/extensions/helix/DOCS-UseCase.md +20 -0
- package/dist/extensions/helix/layers.d.ts +12 -0
- package/dist/extensions/helix/layers.js +18 -0
- package/dist/extensions/helix/write-guards.d.ts +70 -0
- package/dist/extensions/helix/write-guards.js +172 -0
- package/dist/extensions/helix.d.ts +29 -0
- package/dist/extensions/helix.js +963 -0
- package/dist/extensions/helix_extras.d.ts +124 -0
- package/dist/extensions/helix_extras.js +328 -0
- package/dist/extensions/hooks.d.ts +31 -0
- package/dist/extensions/hooks.js +247 -0
- package/dist/extensions/kinddef/DOCS.md +24 -0
- package/dist/extensions/kinddef.d.ts +40 -0
- package/dist/extensions/kinddef.js +171 -0
- package/dist/extensions/lesson.d.ts +6 -0
- package/dist/extensions/lesson.js +194 -0
- package/dist/extensions/modelreg/kinds/model-profile.kind.yaml +155 -0
- package/dist/extensions/modelreg.d.ts +30 -0
- package/dist/extensions/modelreg.js +38 -0
- package/dist/extensions/recognizer.d.ts +34 -0
- package/dist/extensions/recognizer.js +272 -0
- package/dist/extensions/research.d.ts +32 -0
- package/dist/extensions/research.js +292 -0
- package/dist/extensions/safety.d.ts +33 -0
- package/dist/extensions/safety.js +230 -0
- package/dist/extensions/sdlc/kinds/adr.kind.yaml +136 -0
- package/dist/extensions/sdlc/kinds/archive-proposal.kind.yaml +107 -0
- package/dist/extensions/sdlc/kinds/changelog.kind.yaml +108 -0
- package/dist/extensions/sdlc/kinds/cognitive-policy.kind.yaml +470 -0
- package/dist/extensions/sdlc/kinds/forecast.kind.yaml +125 -0
- package/dist/extensions/sdlc/kinds/insight.kind.yaml +87 -0
- package/dist/extensions/sdlc/kinds/kaizen.kind.yaml +106 -0
- package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +270 -0
- package/dist/extensions/sdlc/kinds/narrative.kind.yaml +189 -0
- package/dist/extensions/sdlc/kinds/pattern-insight.kind.yaml +204 -0
- package/dist/extensions/sdlc/kinds/postmortem.kind.yaml +168 -0
- package/dist/extensions/sdlc/kinds/pre-mortem.kind.yaml +145 -0
- package/dist/extensions/sdlc/kinds/prompt-template.kind.yaml +109 -0
- package/dist/extensions/sdlc/kinds/retrospective.kind.yaml +186 -0
- package/dist/extensions/sdlc/kinds/risk-register.kind.yaml +178 -0
- package/dist/extensions/sdlc/kinds/saved-view.kind.yaml +127 -0
- package/dist/extensions/sdlc/kinds/status-report.kind.yaml +129 -0
- package/dist/extensions/sdlc/kinds/synthesis-run.kind.yaml +279 -0
- package/dist/extensions/sdlc/kinds/synthesizer-state.kind.yaml +196 -0
- package/dist/extensions/sdlc/kinds/workflow-event.kind.yaml +195 -0
- package/dist/extensions/sdlc.d.ts +32 -0
- package/dist/extensions/sdlc.js +1318 -0
- package/dist/extensions/soulspec/DOCS.md +19 -0
- package/dist/extensions/soulspec.d.ts +22 -0
- package/dist/extensions/soulspec.js +287 -0
- package/dist/extensions/tenant.d.ts +6 -0
- package/dist/extensions/tenant.js +291 -0
- package/dist/extensions/testkit.d.ts +24 -0
- package/dist/extensions/testkit.js +211 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +102 -0
- package/dist/kernel/_text.d.ts +28 -0
- package/dist/kernel/_text.js +42 -0
- package/dist/kernel/bundle-handle.d.ts +83 -0
- package/dist/kernel/bundle-handle.js +184 -0
- package/dist/kernel/capabilities.d.ts +157 -0
- package/dist/kernel/capabilities.js +109 -0
- package/dist/kernel/collaborator-ports.d.ts +91 -0
- package/dist/kernel/collaborator-ports.js +1 -0
- package/dist/kernel/composition-resolver.d.ts +215 -0
- package/dist/kernel/composition-resolver.js +558 -0
- package/dist/kernel/composition.d.ts +10 -0
- package/dist/kernel/composition.js +1 -0
- package/dist/kernel/descriptor-loader.d.ts +13 -0
- package/dist/kernel/descriptor-loader.js +48 -0
- package/dist/kernel/document.d.ts +43 -0
- package/dist/kernel/document.js +71 -0
- package/dist/kernel/embedding.d.ts +28 -0
- package/dist/kernel/embedding.js +78 -0
- package/dist/kernel/errors.d.ts +62 -0
- package/dist/kernel/errors.js +82 -0
- package/dist/kernel/events.d.ts +6 -0
- package/dist/kernel/events.js +21 -0
- package/dist/kernel/evidence-capture.d.ts +69 -0
- package/dist/kernel/evidence-capture.js +138 -0
- package/dist/kernel/fs.d.ts +20 -0
- package/dist/kernel/fs.js +128 -0
- package/dist/kernel/generic-rw.d.ts +33 -0
- package/dist/kernel/generic-rw.js +235 -0
- package/dist/kernel/hooks.d.ts +172 -0
- package/dist/kernel/hooks.js +256 -0
- package/dist/kernel/index.d.ts +624 -0
- package/dist/kernel/index.js +1638 -0
- package/dist/kernel/instance.d.ts +211 -0
- package/dist/kernel/instance.js +487 -0
- package/dist/kernel/kind-registry.d.ts +183 -0
- package/dist/kernel/kind-registry.js +710 -0
- package/dist/kernel/kind_base.d.ts +106 -0
- package/dist/kernel/kind_base.js +244 -0
- package/dist/kernel/layer-resolver.d.ts +48 -0
- package/dist/kernel/layer-resolver.js +284 -0
- package/dist/kernel/lock-manager.d.ts +21 -0
- package/dist/kernel/lock-manager.js +43 -0
- package/dist/kernel/lock.d.ts +38 -0
- package/dist/kernel/lock.js +115 -0
- package/dist/kernel/meta.d.ts +135 -0
- package/dist/kernel/meta.js +817 -0
- package/dist/kernel/models.d.ts +3089 -0
- package/dist/kernel/models.js +764 -0
- package/dist/kernel/navigator.d.ts +36 -0
- package/dist/kernel/navigator.js +132 -0
- package/dist/kernel/port-surface.d.ts +33 -0
- package/dist/kernel/port-surface.js +180 -0
- package/dist/kernel/preview.d.ts +60 -0
- package/dist/kernel/preview.js +54 -0
- package/dist/kernel/prompt-budget.d.ts +39 -0
- package/dist/kernel/prompt-budget.js +43 -0
- package/dist/kernel/prompt-builder.d.ts +25 -0
- package/dist/kernel/prompt-builder.js +237 -0
- package/dist/kernel/protocols.d.ts +751 -0
- package/dist/kernel/protocols.js +493 -0
- package/dist/kernel/reports.d.ts +26 -0
- package/dist/kernel/reports.js +215 -0
- package/dist/kernel/resolver.d.ts +146 -0
- package/dist/kernel/resolver.js +232 -0
- package/dist/kernel/resource.d.ts +57 -0
- package/dist/kernel/resource.js +97 -0
- package/dist/kernel/runtime.d.ts +16 -0
- package/dist/kernel/runtime.js +18 -0
- package/dist/kernel/safety-scanner.d.ts +38 -0
- package/dist/kernel/safety-scanner.js +228 -0
- package/dist/kernel/serialize-to-files.d.ts +29 -0
- package/dist/kernel/serialize-to-files.js +23 -0
- package/dist/kernel/spec-access.d.ts +23 -0
- package/dist/kernel/spec-access.js +40 -0
- package/dist/kernel/studio_ui.d.ts +88 -0
- package/dist/kernel/studio_ui.js +135 -0
- package/dist/kernel/templates.d.ts +77 -0
- package/dist/kernel/templates.js +116 -0
- package/dist/kernel/tool-registry.d.ts +52 -0
- package/dist/kernel/tool-registry.js +89 -0
- package/dist/kernel/write-pipeline.d.ts +65 -0
- package/dist/kernel/write-pipeline.js +271 -0
- package/dist/kernel/writer-helpers.d.ts +24 -0
- package/dist/kernel/writer-helpers.js +42 -0
- package/dist/memory/decay.d.ts +31 -0
- package/dist/memory/decay.js +97 -0
- package/dist/memory/ecphory.d.ts +25 -0
- package/dist/memory/ecphory.js +178 -0
- package/dist/memory/encodingContext.d.ts +21 -0
- package/dist/memory/encodingContext.js +62 -0
- package/dist/memory/index.d.ts +23 -0
- package/dist/memory/index.js +23 -0
- package/dist/memory/memoryType.d.ts +7 -0
- package/dist/memory/memoryType.js +24 -0
- package/dist/memory/policy.d.ts +35 -0
- package/dist/memory/policy.js +35 -0
- package/dist/memory/retrieval.d.ts +21 -0
- package/dist/memory/retrieval.js +145 -0
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/recordSearchConformance.d.ts +43 -0
- package/dist/testing/recordSearchConformance.js +147 -0
- package/dist/viz/ascii.d.ts +7 -0
- package/dist/viz/ascii.js +60 -0
- package/dist/viz/health.d.ts +8 -0
- package/dist/viz/health.js +161 -0
- package/dist/viz/index.d.ts +10 -0
- package/dist/viz/index.js +10 -0
- package/dist/viz/matrix.d.ts +8 -0
- package/dist/viz/matrix.js +84 -0
- package/dist/viz/mermaid.d.ts +38 -0
- package/dist/viz/mermaid.js +706 -0
- package/package.json +82 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HookRegistry — middleware (use) + events (on / onAsync) for kernel lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* 1:1 parity with Python dna.v3.kernel.hooks.
|
|
5
|
+
*
|
|
6
|
+
* Dual-channel events:
|
|
7
|
+
* - Sync listeners (legacy + in-process metrics/logging):
|
|
8
|
+
* k.on("post_build_prompt", fn) // fn: (ctx) => void
|
|
9
|
+
* k.hooks.emit("post_build_prompt", ctx)
|
|
10
|
+
* - Async listeners (I/O-heavy: event bus publishers, HTTP, DB writes):
|
|
11
|
+
* k.onAsync("post_save", fn) // fn: (ctx) => Promise<void>
|
|
12
|
+
* await k.hooks.emitAsync("post_save", ctx)
|
|
13
|
+
*
|
|
14
|
+
* Sync `on()` autodetects based on whether the handler returns a Promise.
|
|
15
|
+
* Callers that invoke `emit` (sync) skip async listeners — callers that
|
|
16
|
+
* invoke `emitAsync` run both channels. The skip is LOUD
|
|
17
|
+
* (s-kernel-fail-soft-audit, Py twin semantics): `emit` counts every skip
|
|
18
|
+
* in `skippedAsyncEmits` and console.warns ONCE per (hook, listener);
|
|
19
|
+
* `emit(hook, ctx, { strict: true })` throws instead — for call sites
|
|
20
|
+
* where skipping a listener would be a bug, not lost telemetry. The
|
|
21
|
+
* kernel's own sync emits (parse_error / kinddef_conflict /
|
|
22
|
+
* extension_error / sync-path post_build_prompt) are observability
|
|
23
|
+
* funnels and stay non-strict.
|
|
24
|
+
*
|
|
25
|
+
* Veto hooks (s-write-path-despecialize): ordered listeners that can BLOCK
|
|
26
|
+
* the operation by throwing. Unlike `emit`/`emitAsync` (fire-and-forget,
|
|
27
|
+
* errors swallowed), `emitVeto` runs listeners in ascending `priority`
|
|
28
|
+
* order and lets exceptions PROPAGATE — a throw IS the veto. Listeners may
|
|
29
|
+
* also mutate the typed context in place (e.g. fields on `ctx.raw`) — the
|
|
30
|
+
* write proceeds with the mutated payload.
|
|
31
|
+
*
|
|
32
|
+
* k.hooks.onVeto("pre_save", fn, { priority: 20, key: "helix.rule" });
|
|
33
|
+
* await k.hooks.emitVeto("pre_save", ctx);
|
|
34
|
+
*
|
|
35
|
+
* `key` makes registration idempotent: re-registering the same key REPLACES
|
|
36
|
+
* the previous listener (extensions may be re-loaded onto a shared registry).
|
|
37
|
+
*/
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Types
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/**
|
|
42
|
+
* The full vocabulary of hook names the SDK itself emits/consumes
|
|
43
|
+
* (s-dna-typed-hook-names). Py twin: `HookName` / `KNOWN_HOOK_NAMES` in
|
|
44
|
+
* dna/kernel/hooks.py. Both sides are locked to the shared fixture
|
|
45
|
+
* `tests/parity-fixtures/port-surface-parity.json` (section `hook_names`)
|
|
46
|
+
* by tests/hook-names.test.ts + tests/test_hook_names.py — vocabulary
|
|
47
|
+
* drift turns both suites red. Add a name HERE + fixture + Py, never ad hoc.
|
|
48
|
+
*/
|
|
49
|
+
export const KNOWN_HOOK_NAMES = [
|
|
50
|
+
"pre_build_prompt",
|
|
51
|
+
"post_build_prompt",
|
|
52
|
+
"pre_save",
|
|
53
|
+
"post_save",
|
|
54
|
+
"post_delete",
|
|
55
|
+
"kinddef_conflict",
|
|
56
|
+
"parse_error",
|
|
57
|
+
"extension_error",
|
|
58
|
+
];
|
|
59
|
+
const KNOWN_HOOK_SET = new Set(KNOWN_HOOK_NAMES);
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Registry
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
export class HookRegistry {
|
|
64
|
+
_middleware = new Map();
|
|
65
|
+
_events = new Map();
|
|
66
|
+
_asyncEvents = new Map();
|
|
67
|
+
/** Veto channel — hook → list of {priority, seq, key, fn}. `seq` keeps
|
|
68
|
+
* registration order stable among equal priorities. */
|
|
69
|
+
_veto = new Map();
|
|
70
|
+
_vetoSeq = 0;
|
|
71
|
+
/** s-kernel-fail-soft-audit — sync-emit async-listener skip tracking:
|
|
72
|
+
* warn ONCE per (hook, listener) + count every skip per hook so a
|
|
73
|
+
* mis-wired listener is visible instead of an invisible no-op. */
|
|
74
|
+
_asyncSkipWarned = new Set();
|
|
75
|
+
skippedAsyncEmits = new Map();
|
|
76
|
+
/** s-dna-typed-hook-names — unknown-name warn-once tracking (per
|
|
77
|
+
* registry, per name; same posture as _asyncSkipWarned). */
|
|
78
|
+
_unknownHookWarned = new Set();
|
|
79
|
+
/** Warn (once per registry+name) on a name outside `HookName`.
|
|
80
|
+
* Fail-loud, NOT fail-closed: custom hook names keep working
|
|
81
|
+
* (back-compat / third-party channels), but a typo of a builtin name —
|
|
82
|
+
* the listener that never fires — is now visible. Py twin semantics:
|
|
83
|
+
* `UnknownHookNameWarning`. */
|
|
84
|
+
_checkHookName(hook) {
|
|
85
|
+
if (KNOWN_HOOK_SET.has(hook) || this._unknownHookWarned.has(hook))
|
|
86
|
+
return;
|
|
87
|
+
this._unknownHookWarned.add(hook);
|
|
88
|
+
console.warn(`unknown hook name ${JSON.stringify(hook)} — known: ` +
|
|
89
|
+
`${JSON.stringify([...KNOWN_HOOK_NAMES].sort())}. A listener on (or ` +
|
|
90
|
+
`emit of) a misspelled hook never fires; custom names still work ` +
|
|
91
|
+
`(warning only).`);
|
|
92
|
+
}
|
|
93
|
+
/** Register middleware. Called in order, each receives output of previous. */
|
|
94
|
+
use(hook, fn) {
|
|
95
|
+
this._checkHookName(hook);
|
|
96
|
+
const list = this._middleware.get(hook) ?? [];
|
|
97
|
+
list.push(fn);
|
|
98
|
+
this._middleware.set(hook, list);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Register event subscriber. Fire-and-forget, errors logged but not raised.
|
|
102
|
+
*
|
|
103
|
+
* Autodetects sync vs async via `fn.constructor.name === "AsyncFunction"`.
|
|
104
|
+
* Async functions are routed to the async channel — callers using `emit`
|
|
105
|
+
* (sync) skip them; `emitAsync` runs both.
|
|
106
|
+
*/
|
|
107
|
+
on(hook, fn) {
|
|
108
|
+
this._checkHookName(hook);
|
|
109
|
+
// AsyncFunction detection: works for `async function`. For regular
|
|
110
|
+
// functions that happen to return Promises, prefer `onAsync` explicitly.
|
|
111
|
+
if (fn.constructor?.name === "AsyncFunction") {
|
|
112
|
+
const list = this._asyncEvents.get(hook) ?? [];
|
|
113
|
+
list.push(fn);
|
|
114
|
+
this._asyncEvents.set(hook, list);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const list = this._events.get(hook) ?? [];
|
|
118
|
+
list.push(fn);
|
|
119
|
+
this._events.set(hook, list);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Explicit async-only registration. Prefer `on()` — this is here for
|
|
123
|
+
* symmetry and for non-`async function` callables (arrow with Promise
|
|
124
|
+
* body, partials) where AsyncFunction detection returns false. */
|
|
125
|
+
onAsync(hook, fn) {
|
|
126
|
+
this._checkHookName(hook);
|
|
127
|
+
const list = this._asyncEvents.get(hook) ?? [];
|
|
128
|
+
list.push(fn);
|
|
129
|
+
this._asyncEvents.set(hook, list);
|
|
130
|
+
}
|
|
131
|
+
/** Run middleware chain. Returns modified context. */
|
|
132
|
+
runMiddleware(hook, ctx) {
|
|
133
|
+
this._checkHookName(hook);
|
|
134
|
+
for (const fn of this._middleware.get(hook) ?? []) {
|
|
135
|
+
ctx = fn(ctx);
|
|
136
|
+
}
|
|
137
|
+
return ctx;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Sync emit. Runs only sync listeners. If async listeners exist for this
|
|
141
|
+
* hook they CANNOT run here — the caller must use `emitAsync` to reach
|
|
142
|
+
* them. The skip is loud (s-kernel-fail-soft-audit):
|
|
143
|
+
*
|
|
144
|
+
* - default: each skipped listener is counted in `skippedAsyncEmits` and
|
|
145
|
+
* a console.warn fires once per (hook, listener) — a mis-wired async
|
|
146
|
+
* listener is visible, not an invisible no-op.
|
|
147
|
+
* - `strict: true`: throw instead — for emit sites where skipping a
|
|
148
|
+
* listener would be a bug, not lost telemetry.
|
|
149
|
+
*/
|
|
150
|
+
emit(hook, ctx, opts) {
|
|
151
|
+
this._checkHookName(hook);
|
|
152
|
+
for (const fn of this._events.get(hook) ?? []) {
|
|
153
|
+
try {
|
|
154
|
+
fn(ctx);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
console.warn(`Hook ${hook} subscriber error:`, e);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const skipped = this._asyncEvents.get(hook) ?? [];
|
|
161
|
+
if (skipped.length > 0) {
|
|
162
|
+
const total = (this.skippedAsyncEmits.get(hook) ?? 0) + skipped.length;
|
|
163
|
+
this.skippedAsyncEmits.set(hook, total);
|
|
164
|
+
if (opts?.strict) {
|
|
165
|
+
const names = skipped.map((fn) => fn.name || "<anonymous>");
|
|
166
|
+
throw new Error(`Hook ${JSON.stringify(hook)} was emitted via sync emit(strict) ` +
|
|
167
|
+
`but has ${skipped.length} async listener(s) ` +
|
|
168
|
+
`${JSON.stringify(names)} that cannot run — use emitAsync() at ` +
|
|
169
|
+
`this call site.`);
|
|
170
|
+
}
|
|
171
|
+
for (const fn of skipped) {
|
|
172
|
+
const key = `${hook}\0${fn.name || "<anonymous>"}`;
|
|
173
|
+
if (this._asyncSkipWarned.has(key))
|
|
174
|
+
continue;
|
|
175
|
+
this._asyncSkipWarned.add(key);
|
|
176
|
+
console.warn(`Hook ${hook} has async listener ${fn.name || "<anonymous>"} but ` +
|
|
177
|
+
`was fired via sync emit(); the listener was SKIPPED (total ` +
|
|
178
|
+
`skips for this hook: ${total}). Use emitAsync() to reach it. ` +
|
|
179
|
+
`(warned once per (hook, listener))`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Async emit. Runs sync listeners inline, then awaits each async listener.
|
|
185
|
+
* Use from async call sites (kernel.writeDocument, kernel.deleteDocument).
|
|
186
|
+
* Errors in any listener are logged and do NOT prevent other listeners.
|
|
187
|
+
*/
|
|
188
|
+
async emitAsync(hook, ctx) {
|
|
189
|
+
this._checkHookName(hook);
|
|
190
|
+
for (const fn of this._events.get(hook) ?? []) {
|
|
191
|
+
try {
|
|
192
|
+
fn(ctx);
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
console.warn(`Hook ${hook} (sync) subscriber error:`, e);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
for (const fn of this._asyncEvents.get(hook) ?? []) {
|
|
199
|
+
try {
|
|
200
|
+
await fn(ctx);
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
console.warn(`Hook ${hook} (async) subscriber error:`, e);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// -- Veto channel (s-write-path-despecialize) ------------------------------
|
|
208
|
+
/**
|
|
209
|
+
* Register a veto listener on `hook`. Listeners run in ascending
|
|
210
|
+
* `priority` (ties keep registration order). Throwing from a listener
|
|
211
|
+
* PROPAGATES to the emitter — that is the veto. Sync and async callables
|
|
212
|
+
* both work.
|
|
213
|
+
*
|
|
214
|
+
* `key` (recommended: `"<extension>.<rule>"`) makes registration
|
|
215
|
+
* idempotent — a second `onVeto` with the same key REPLACES the earlier
|
|
216
|
+
* listener instead of stacking a duplicate.
|
|
217
|
+
*/
|
|
218
|
+
onVeto(hook, fn, opts) {
|
|
219
|
+
this._checkHookName(hook);
|
|
220
|
+
const listeners = this._veto.get(hook) ?? [];
|
|
221
|
+
const key = opts?.key ?? null;
|
|
222
|
+
const filtered = key !== null
|
|
223
|
+
? listeners.filter((entry) => entry.key !== key)
|
|
224
|
+
: listeners;
|
|
225
|
+
this._vetoSeq += 1;
|
|
226
|
+
filtered.push({
|
|
227
|
+
priority: opts?.priority ?? 0, seq: this._vetoSeq, key, fn,
|
|
228
|
+
});
|
|
229
|
+
filtered.sort((a, b) => a.priority - b.priority || a.seq - b.seq);
|
|
230
|
+
this._veto.set(hook, filtered);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Run veto listeners for `hook` in priority order. Unlike
|
|
234
|
+
* `emit`/`emitAsync`, exceptions are NOT swallowed — the first throw
|
|
235
|
+
* aborts the chain and propagates to the caller (the operation is
|
|
236
|
+
* vetoed). Listeners may mutate `ctx` in place.
|
|
237
|
+
*/
|
|
238
|
+
async emitVeto(hook, ctx) {
|
|
239
|
+
this._checkHookName(hook);
|
|
240
|
+
for (const entry of this._veto.get(hook) ?? []) {
|
|
241
|
+
await entry.fn(ctx);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** Check if any veto listeners are registered for `hook`. */
|
|
245
|
+
hasVeto(hook) {
|
|
246
|
+
return (this._veto.get(hook)?.length ?? 0) > 0;
|
|
247
|
+
}
|
|
248
|
+
/** Check if any middleware, events (sync or async) or veto listeners are
|
|
249
|
+
* registered. */
|
|
250
|
+
has(hook) {
|
|
251
|
+
return ((this._middleware.get(hook)?.length ?? 0) > 0 ||
|
|
252
|
+
(this._events.get(hook)?.length ?? 0) > 0 ||
|
|
253
|
+
(this._asyncEvents.get(hook)?.length ?? 0) > 0 ||
|
|
254
|
+
(this._veto.get(hook)?.length ?? 0) > 0);
|
|
255
|
+
}
|
|
256
|
+
}
|