@nwire/hooks 0.7.0 → 0.8.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/README.md +646 -57
- package/dist/hook.d.ts +32 -15
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +196 -96
- package/dist/hook.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/record.d.ts +50 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +86 -0
- package/dist/record.js.map +1 -0
- package/dist/registry.d.ts +54 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +103 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +97 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
package/dist/hook.d.ts
CHANGED
|
@@ -5,30 +5,47 @@
|
|
|
5
5
|
* attachments. `.run()` fires the chain first (sequential, can short-circuit),
|
|
6
6
|
* then fires listeners in parallel via Promise.allSettled.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* On top of that core, every hook is:
|
|
9
|
+
* - introspectable — listed by `listHooks()`, keyed by `id` + `name`
|
|
10
|
+
* - traceable — `.tap()` receives a StepObservation per phase
|
|
11
|
+
* - linked — nested runs auto-pick a `parentRunId`
|
|
12
|
+
* - source-located — `hook("x")` captures its call site
|
|
13
|
+
* - cancellable — `.run(ctx, { signal })` propagates an AbortSignal
|
|
14
|
+
* - prioritised — `.use(fn, { priority })` orders the chain
|
|
15
|
+
* - filterable — `.on(fn, { when: "success" | "failure" })`
|
|
16
|
+
* - replayable — `.runDetailed()` returns the full step trace
|
|
17
|
+
*/
|
|
18
|
+
import type { ChainFn, HookOptions, ListenerFn, OnOptions, RunOptions, RunResult, TapFn, UseOptions } from "./types.js";
|
|
19
|
+
/**
|
|
20
|
+
* The public Hook surface.
|
|
21
|
+
*
|
|
22
|
+
* The signatures stay backward-compatible: `.use(fn)`, `.on(fn)`, `.run(ctx)`
|
|
23
|
+
* work exactly as before. Each method accepts new optional arguments —
|
|
24
|
+
* priority, name, when, signal — that opt into the richer behavior.
|
|
9
25
|
*/
|
|
10
|
-
import type { ChainFn, HookOptions, ListenerFn } from "./types.js";
|
|
11
|
-
/** The public Hook surface. See architecture-sketch.html §06. */
|
|
12
26
|
export interface Hook<Ctx> {
|
|
13
27
|
/** Hook name. Stable for logging + telemetry. */
|
|
14
28
|
readonly name: string;
|
|
29
|
+
/** Process-wide unique id for this hook instance. */
|
|
30
|
+
readonly id: string;
|
|
15
31
|
/** Attach a chain middleware (sequential, can short-circuit). */
|
|
16
|
-
use(fn: ChainFn<Ctx
|
|
32
|
+
use(fn: ChainFn<Ctx>, opts?: UseOptions): this;
|
|
17
33
|
/** Attach a listener (parallel, observes final ctx, cannot mutate). */
|
|
18
|
-
on(fn: ListenerFn<Ctx
|
|
34
|
+
on(fn: ListenerFn<Ctx>, opts?: OnOptions): this;
|
|
19
35
|
/** Detach a previously attached `.use()` or `.on()` fn. No-op if unknown. */
|
|
20
36
|
off(fn: ChainFn<Ctx> | ListenerFn<Ctx>): this;
|
|
21
37
|
/** Run the chain, then fire listeners. Returns the (possibly mutated) ctx. */
|
|
22
|
-
run(ctx: Ctx): Promise<Ctx>;
|
|
38
|
+
run(ctx: Ctx, opts?: RunOptions): Promise<Ctx>;
|
|
39
|
+
/** Same as `.run()`, but returns the full observation trace + outcome. */
|
|
40
|
+
runDetailed(ctx: Ctx, opts?: RunOptions): Promise<RunResult<Ctx>>;
|
|
41
|
+
/** Subscribe to per-step observations. Returns an unsubscribe handle. */
|
|
42
|
+
tap(observer: TapFn): () => void;
|
|
43
|
+
/** Step counts — `{ chain, listeners }`. Used by the registry + tests. */
|
|
44
|
+
stepCounts(): {
|
|
45
|
+
readonly chain: number;
|
|
46
|
+
readonly listeners: number;
|
|
47
|
+
};
|
|
23
48
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Create a new hook.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* const request = hook<RequestCtx>("http.request");
|
|
29
|
-
* request.use(async (ctx, next) => { ctx.start = Date.now(); await next(); });
|
|
30
|
-
* request.on(async (ctx) => { await analytics.track(ctx); });
|
|
31
|
-
* await request.run(ctx);
|
|
32
|
-
*/
|
|
49
|
+
/** Create a new hook. */
|
|
33
50
|
export declare function hook<Ctx>(name: string, options?: HookOptions<Ctx>): Hook<Ctx>;
|
|
34
51
|
//# sourceMappingURL=hook.d.ts.map
|
package/dist/hook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAaH,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EACX,UAAU,EACV,SAAS,EACT,UAAU,EAEV,SAAS,EAET,KAAK,EACL,UAAU,EACX,MAAM,SAAS,CAAC;AAIjB;;;;;;GAMG;AACH,MAAM,WAAW,IAAI,CAAC,GAAG;IACvB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,EAAI,MAAM,CAAC;IAEtB,iEAAiE;IACjE,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAE/C,uEAAuE;IACvE,EAAE,CAAC,EAAE,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAEhD,6EAA6E;IAC7E,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAE9C,8EAA8E;IAC9E,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAE/C,0EAA0E;IAC1E,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAElE,yEAAyE;IACzE,GAAG,CAAC,QAAQ,EAAE,KAAK,GAAG,MAAM,IAAI,CAAC;IAEjC,0EAA0E;IAC1E,UAAU,IAAI;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE;AAED,yBAAyB;AACzB,wBAAgB,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAW,CAAC,GAAG,CAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAKjF"}
|
package/dist/hook.js
CHANGED
|
@@ -5,144 +5,244 @@
|
|
|
5
5
|
* attachments. `.run()` fires the chain first (sequential, can short-circuit),
|
|
6
6
|
* then fires listeners in parallel via Promise.allSettled.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* On top of that core, every hook is:
|
|
9
|
+
* - introspectable — listed by `listHooks()`, keyed by `id` + `name`
|
|
10
|
+
* - traceable — `.tap()` receives a StepObservation per phase
|
|
11
|
+
* - linked — nested runs auto-pick a `parentRunId`
|
|
12
|
+
* - source-located — `hook("x")` captures its call site
|
|
13
|
+
* - cancellable — `.run(ctx, { signal })` propagates an AbortSignal
|
|
14
|
+
* - prioritised — `.use(fn, { priority })` orders the chain
|
|
15
|
+
* - filterable — `.on(fn, { when: "success" | "failure" })`
|
|
16
|
+
* - replayable — `.runDetailed()` returns the full step trace
|
|
9
17
|
*/
|
|
10
18
|
import Emittery from "emittery";
|
|
11
19
|
import { compose } from "./compose.js";
|
|
12
|
-
|
|
13
|
-
* Internal emittery event name. We piggy-back on emittery for typed
|
|
14
|
-
* subscription bookkeeping (and as the documented foundation of @nwire/hooks),
|
|
15
|
-
* but we iterate listeners ourselves via a parallel Set so we can apply the
|
|
16
|
-
* contract-mandated `Promise.allSettled` semantics — emittery's `.emit()`
|
|
17
|
-
* uses Promise.all which fails fast on the first rejection.
|
|
18
|
-
*/
|
|
20
|
+
import { captureSourceLocation, nextHookId, nextRunId, registerHook, withRunContext, currentRun, } from "./registry.js";
|
|
19
21
|
const RUN_EVENT = "__nwire_hooks_run__";
|
|
20
|
-
/**
|
|
21
|
-
* Create a new hook.
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* const request = hook<RequestCtx>("http.request");
|
|
25
|
-
* request.use(async (ctx, next) => { ctx.start = Date.now(); await next(); });
|
|
26
|
-
* request.on(async (ctx) => { await analytics.track(ctx); });
|
|
27
|
-
* await request.run(ctx);
|
|
28
|
-
*/
|
|
22
|
+
/** Create a new hook. */
|
|
29
23
|
export function hook(name, options = {}) {
|
|
30
|
-
|
|
24
|
+
const source = captureSourceLocation(2);
|
|
25
|
+
const h = new HookImpl(name, options);
|
|
26
|
+
registerHook(h, source);
|
|
27
|
+
return h;
|
|
31
28
|
}
|
|
32
29
|
class HookImpl {
|
|
33
30
|
name;
|
|
31
|
+
id;
|
|
34
32
|
chain = [];
|
|
35
|
-
|
|
36
|
-
listeners = new Set();
|
|
37
|
-
/** Bookkeeping mirror — emittery is the documented foundation. */
|
|
33
|
+
listeners = [];
|
|
38
34
|
emitter = new Emittery();
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
taps = new Set();
|
|
36
|
+
stepCounter = 0;
|
|
41
37
|
strictListeners;
|
|
42
38
|
onListenerError;
|
|
43
39
|
constructor(name, options) {
|
|
44
40
|
this.name = name;
|
|
41
|
+
this.id = nextHookId();
|
|
45
42
|
this.strictListeners = options.strictListeners === true;
|
|
46
43
|
this.onListenerError =
|
|
47
44
|
options.onListenerError ??
|
|
48
45
|
((err, _ctx, hookName) => {
|
|
49
|
-
// Conservative default — log + continue. Hosts (createHooks)
|
|
50
|
-
// override this to route through their `lifecycle.error` hook.
|
|
51
46
|
// eslint-disable-next-line no-console
|
|
52
47
|
console.error(`[@nwire/hooks] listener error in "${hookName}":`, err);
|
|
53
48
|
});
|
|
54
49
|
}
|
|
55
|
-
|
|
56
|
-
this.chain.
|
|
50
|
+
stepCounts() {
|
|
51
|
+
return { chain: this.chain.length, listeners: this.listeners.length };
|
|
52
|
+
}
|
|
53
|
+
use(fn, opts) {
|
|
54
|
+
const entry = {
|
|
55
|
+
fn,
|
|
56
|
+
stepId: this.stepCounter++,
|
|
57
|
+
name: opts?.name,
|
|
58
|
+
priority: opts?.priority ?? 0,
|
|
59
|
+
};
|
|
60
|
+
this.chain.push(entry);
|
|
61
|
+
// Higher priority runs first (outermost). Stable sort preserves insertion
|
|
62
|
+
// order for equal priorities.
|
|
63
|
+
this.chain.sort((a, b) => b.priority - a.priority || a.stepId - b.stepId);
|
|
57
64
|
return this;
|
|
58
65
|
}
|
|
59
|
-
on(fn) {
|
|
60
|
-
if (this.listeners.
|
|
66
|
+
on(fn, opts) {
|
|
67
|
+
if (this.listeners.find((l) => l.fn === fn))
|
|
61
68
|
return this;
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
69
|
+
const adapter = async (ctx) => { await fn(ctx); };
|
|
70
|
+
const entry = {
|
|
71
|
+
fn,
|
|
72
|
+
stepId: this.stepCounter++,
|
|
73
|
+
name: opts?.name,
|
|
74
|
+
priority: opts?.priority ?? 0,
|
|
75
|
+
when: opts?.when ?? "always",
|
|
76
|
+
adapter,
|
|
65
77
|
};
|
|
66
|
-
this.
|
|
78
|
+
this.listeners.push(entry);
|
|
79
|
+
this.listeners.sort((a, b) => b.priority - a.priority || a.stepId - b.stepId);
|
|
67
80
|
this.emitter.on(RUN_EVENT, adapter);
|
|
68
81
|
return this;
|
|
69
82
|
}
|
|
70
83
|
off(fn) {
|
|
71
|
-
|
|
72
|
-
const chainIdx = this.chain.indexOf(fn);
|
|
84
|
+
const chainIdx = this.chain.findIndex((c) => c.fn === fn);
|
|
73
85
|
if (chainIdx >= 0) {
|
|
74
86
|
this.chain.splice(chainIdx, 1);
|
|
75
87
|
return this;
|
|
76
88
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.emitter.off(RUN_EVENT, adapter);
|
|
83
|
-
this.adapters.delete(listenerFn);
|
|
84
|
-
}
|
|
89
|
+
const listenerIdx = this.listeners.findIndex((l) => l.fn === fn);
|
|
90
|
+
if (listenerIdx >= 0) {
|
|
91
|
+
const entry = this.listeners[listenerIdx];
|
|
92
|
+
this.emitter.off(RUN_EVENT, entry.adapter);
|
|
93
|
+
this.listeners.splice(listenerIdx, 1);
|
|
85
94
|
}
|
|
86
95
|
return this;
|
|
87
96
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// next() or short-circuit.
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
chainError = err;
|
|
101
|
-
// Stash on ctx so listeners can observe failure mode. Mutating even
|
|
102
|
-
// when `Ctx` doesn't declare an `error` field is the documented
|
|
103
|
-
// contract (§07 row 2 + row 11).
|
|
104
|
-
ctx.error = err;
|
|
97
|
+
tap(observer) {
|
|
98
|
+
this.taps.add(observer);
|
|
99
|
+
return () => { this.taps.delete(observer); };
|
|
100
|
+
}
|
|
101
|
+
async run(ctx, opts) {
|
|
102
|
+
const result = await this.runDetailed(ctx, opts);
|
|
103
|
+
if (result.outcome === "failed" && result.error !== undefined) {
|
|
104
|
+
throw result.error;
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Object.defineProperty(primary, "cause", {
|
|
120
|
-
value: failures.slice(1).map((f) => f.reason),
|
|
121
|
-
configurable: true,
|
|
122
|
-
writable: true,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
throw primary;
|
|
126
|
-
}
|
|
127
|
-
// Default mode: report each, never fail the run.
|
|
128
|
-
for (const failure of failures) {
|
|
129
|
-
try {
|
|
130
|
-
this.onListenerError(failure.reason, ctx, this.name);
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// Reporter itself blew up — swallow. Listener-error reporting
|
|
134
|
-
// must never crash the hook.
|
|
135
|
-
}
|
|
106
|
+
return result.ctx;
|
|
107
|
+
}
|
|
108
|
+
async runDetailed(ctx, opts) {
|
|
109
|
+
const runId = nextRunId();
|
|
110
|
+
const parentRunId = opts?.parentRunId ?? currentRun()?.runId;
|
|
111
|
+
const signal = opts?.signal;
|
|
112
|
+
const steps = [];
|
|
113
|
+
const startedAt = nowMs();
|
|
114
|
+
const emit = (obs) => {
|
|
115
|
+
steps.push(obs);
|
|
116
|
+
for (const tap of this.taps) {
|
|
117
|
+
try {
|
|
118
|
+
tap(obs);
|
|
136
119
|
}
|
|
120
|
+
catch { /* tap throws are swallowed */ }
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
// Inject abort signal onto ctx (only if ctx is an object). Domain code
|
|
124
|
+
// can read `ctx.signal` to bail early. We don't overwrite a user-provided
|
|
125
|
+
// signal on ctx; user wins.
|
|
126
|
+
if (signal && ctx && typeof ctx === "object" && !ctx.signal) {
|
|
127
|
+
ctx.signal = signal;
|
|
128
|
+
}
|
|
129
|
+
const composed = compose(this.chain.map((entry) => wrapChain(this, entry, runId, parentRunId, signal, emit)));
|
|
130
|
+
// A chain step that doesn't call its `next()` short-circuits — the tail
|
|
131
|
+
// is the only place that *reliably* signals "every step yielded down to
|
|
132
|
+
// me." We toggle a flag inside the tail; if it stays false after the
|
|
133
|
+
// compose resolves without throwing, the chain was prevented.
|
|
134
|
+
let chainError = undefined;
|
|
135
|
+
let reachedTail = false;
|
|
136
|
+
await withRunContext({ runId, hookId: this.id, parentRunId }, async () => {
|
|
137
|
+
try {
|
|
138
|
+
await composed(ctx, async () => { reachedTail = true; });
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
chainError = err;
|
|
142
|
+
ctx.error = err;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
// Empty chain — nothing to short-circuit, treat as completed.
|
|
146
|
+
if (this.chain.length === 0)
|
|
147
|
+
reachedTail = true;
|
|
148
|
+
const outcome = chainError !== undefined ? "failed" : reachedTail ? "completed" : "prevented";
|
|
149
|
+
await this.fireListeners(ctx, outcome, runId, parentRunId, emit);
|
|
150
|
+
return {
|
|
151
|
+
ctx,
|
|
152
|
+
outcome,
|
|
153
|
+
runId,
|
|
154
|
+
parentRunId,
|
|
155
|
+
steps,
|
|
156
|
+
durationMs: nowMs() - startedAt,
|
|
157
|
+
error: chainError,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async fireListeners(ctx, outcome, runId, parentRunId, emit) {
|
|
161
|
+
const eligible = this.listeners.filter((l) => {
|
|
162
|
+
if (l.when === "always")
|
|
163
|
+
return true;
|
|
164
|
+
if (l.when === "success")
|
|
165
|
+
return outcome === "completed";
|
|
166
|
+
if (l.when === "failure")
|
|
167
|
+
return outcome === "failed";
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
170
|
+
if (eligible.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
const results = await Promise.allSettled(eligible.map(async (entry) => {
|
|
173
|
+
const startTs = nowMs();
|
|
174
|
+
emit(makeObs(this, entry.stepId, "listener", entry.name, "start", startTs, runId, parentRunId));
|
|
175
|
+
try {
|
|
176
|
+
await entry.fn(ctx);
|
|
177
|
+
emit(makeObs(this, entry.stepId, "listener", entry.name, "end", nowMs(), runId, parentRunId, nowMs() - startTs));
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
emit(makeObs(this, entry.stepId, "listener", entry.name, "error", nowMs(), runId, parentRunId, nowMs() - startTs, err));
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
}));
|
|
184
|
+
const failures = results.filter((r) => r.status === "rejected");
|
|
185
|
+
if (failures.length === 0)
|
|
186
|
+
return;
|
|
187
|
+
if (this.strictListeners) {
|
|
188
|
+
const primary = failures[0].reason;
|
|
189
|
+
if (failures.length > 1 &&
|
|
190
|
+
primary instanceof Error &&
|
|
191
|
+
primary.cause === undefined) {
|
|
192
|
+
Object.defineProperty(primary, "cause", {
|
|
193
|
+
value: failures.slice(1).map((f) => f.reason),
|
|
194
|
+
configurable: true,
|
|
195
|
+
writable: true,
|
|
196
|
+
});
|
|
137
197
|
}
|
|
198
|
+
throw primary;
|
|
138
199
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
200
|
+
for (const f of failures) {
|
|
201
|
+
try {
|
|
202
|
+
this.onListenerError(f.reason, ctx, this.name);
|
|
203
|
+
}
|
|
204
|
+
catch { /* reporter blew up — swallow */ }
|
|
144
205
|
}
|
|
145
|
-
return ctx;
|
|
146
206
|
}
|
|
147
207
|
}
|
|
208
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
209
|
+
function wrapChain(hook, entry, runId, parentRunId, signal, emit) {
|
|
210
|
+
return async function instrumented(ctx, next) {
|
|
211
|
+
if (signal?.aborted) {
|
|
212
|
+
throw signal.reason ?? new Error("hook aborted");
|
|
213
|
+
}
|
|
214
|
+
const startTs = nowMs();
|
|
215
|
+
emit(makeObs(hook, entry.stepId, "chain", entry.name, "start", startTs, runId, parentRunId));
|
|
216
|
+
try {
|
|
217
|
+
await entry.fn(ctx, next);
|
|
218
|
+
const endTs = nowMs();
|
|
219
|
+
emit(makeObs(hook, entry.stepId, "chain", entry.name, "end", endTs, runId, parentRunId, endTs - startTs));
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
const endTs = nowMs();
|
|
223
|
+
emit(makeObs(hook, entry.stepId, "chain", entry.name, "error", endTs, runId, parentRunId, endTs - startTs, err));
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function makeObs(hook, stepId, stepKind, stepName, phase, ts, runId, parentRunId, durationMs, error) {
|
|
229
|
+
return {
|
|
230
|
+
hookName: hook.name,
|
|
231
|
+
hookId: hook.id,
|
|
232
|
+
runId,
|
|
233
|
+
parentRunId,
|
|
234
|
+
stepId,
|
|
235
|
+
stepKind,
|
|
236
|
+
stepName,
|
|
237
|
+
phase,
|
|
238
|
+
ts,
|
|
239
|
+
durationMs,
|
|
240
|
+
error,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// performance.now() is available in Node 16+; fall back to Date.now() only
|
|
244
|
+
// if a host VM lacks it (vitest can stub it).
|
|
245
|
+
const nowMs = typeof performance !== "undefined" && typeof performance.now === "function"
|
|
246
|
+
? () => performance.now()
|
|
247
|
+
: () => Date.now();
|
|
148
248
|
//# sourceMappingURL=hook.js.map
|
package/dist/hook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nwire/hooks — the universal dispatch primitive
|
|
2
|
+
* @nwire/hooks — the universal dispatch primitive.
|
|
3
3
|
*
|
|
4
|
-
* One `hook()` accepts
|
|
5
|
-
* attachments.
|
|
6
|
-
*
|
|
4
|
+
* One `hook()` accepts both `.use()` (chain middleware) and `.on()` (parallel
|
|
5
|
+
* listener) attachments. Every run is observable end-to-end via `.tap()`,
|
|
6
|
+
* yields a runId that links nested runs into a topology tree, and can be
|
|
7
|
+
* recorded for replay.
|
|
8
|
+
*
|
|
9
|
+
* See README.md for the full contract + recipes.
|
|
7
10
|
*/
|
|
8
11
|
export { hook, type Hook } from "./hook.js";
|
|
9
|
-
export { createHooks, type Host, type HookMap, type Plugin, type PluginEntry } from "./create-hooks.js";
|
|
12
|
+
export { createHooks, type Host, type HookMap, type Plugin, type PluginEntry, } from "./create-hooks.js";
|
|
10
13
|
export { compose } from "./compose.js";
|
|
11
14
|
export { pipe, withTimeout, withRetry } from "./pipe.js";
|
|
12
|
-
export
|
|
15
|
+
export { record, replay, type RecordOptions, type ReplayResult } from "./record.js";
|
|
16
|
+
export { listHooks, currentRun, captureSourceLocation, } from "./registry.js";
|
|
17
|
+
export type { ChainFn, HookOptions, ListenerFn, OnOptions, Recording, RetryOpts, RunOptions, RunOutcome, RunResult, SourceLocation, StepKind, StepObservation, StepPhase, TapFn, UseOptions, } from "./types.js";
|
|
13
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EACL,WAAW,EACX,KAAK,IAAI,EACT,KAAK,OAAO,EACZ,KAAK,MAAM,EACX,KAAK,WAAW,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EACL,SAAS,EACT,UAAU,EACV,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,OAAO,EACP,WAAW,EACX,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,cAAc,EACd,QAAQ,EACR,eAAe,EACf,SAAS,EACT,KAAK,EACL,UAAU,GACX,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nwire/hooks — the universal dispatch primitive
|
|
2
|
+
* @nwire/hooks — the universal dispatch primitive.
|
|
3
3
|
*
|
|
4
|
-
* One `hook()` accepts
|
|
5
|
-
* attachments.
|
|
6
|
-
*
|
|
4
|
+
* One `hook()` accepts both `.use()` (chain middleware) and `.on()` (parallel
|
|
5
|
+
* listener) attachments. Every run is observable end-to-end via `.tap()`,
|
|
6
|
+
* yields a runId that links nested runs into a topology tree, and can be
|
|
7
|
+
* recorded for replay.
|
|
8
|
+
*
|
|
9
|
+
* See README.md for the full contract + recipes.
|
|
7
10
|
*/
|
|
8
11
|
export { hook } from "./hook.js";
|
|
9
|
-
export { createHooks } from "./create-hooks.js";
|
|
12
|
+
export { createHooks, } from "./create-hooks.js";
|
|
10
13
|
export { compose } from "./compose.js";
|
|
11
14
|
export { pipe, withTimeout, withRetry } from "./pipe.js";
|
|
15
|
+
export { record, replay } from "./record.js";
|
|
16
|
+
export { listHooks, currentRun, captureSourceLocation, } from "./registry.js";
|
|
12
17
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAa,MAAM,QAAQ,CAAC;AACzC,OAAO,EACL,WAAW,GAKZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAyC,MAAM,UAAU,CAAC;AACjF,OAAO,EACL,SAAS,EACT,UAAU,EACV,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recording + replay.
|
|
3
|
+
*
|
|
4
|
+
* Recording a hook run produces a {@link Recording} — the full step trace
|
|
5
|
+
* plus user-supplied ctx snapshots taken before and after. Replays the
|
|
6
|
+
* recording later (or in another process) and assert step-by-step shape
|
|
7
|
+
* matches. This is the substrate Studio's "replay this trace" button and
|
|
8
|
+
* the deterministic debug flow are built on.
|
|
9
|
+
*
|
|
10
|
+
* Two scopes:
|
|
11
|
+
*
|
|
12
|
+
* record(hook, ctx, opts?) one-shot: run the hook, snapshot ctx, return Recording
|
|
13
|
+
* replay(hook, recording, opts) re-run with the recorded ctxIn; compare shapes
|
|
14
|
+
*
|
|
15
|
+
* The clone strategy is pluggable; default is `structuredClone`. Pass
|
|
16
|
+
* `clone: (x) => JSON.parse(JSON.stringify(x))` for non-clonable shapes.
|
|
17
|
+
*/
|
|
18
|
+
import type { Hook } from "./hook.js";
|
|
19
|
+
import type { Recording, RunOptions } from "./types.js";
|
|
20
|
+
export interface RecordOptions extends RunOptions {
|
|
21
|
+
/** Custom ctx-cloner for snapshotting. Default: `structuredClone`. */
|
|
22
|
+
readonly clone?: <T>(ctx: T) => T;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Run the hook once, capture ctx-in/out + every observation. Result is a
|
|
26
|
+
* self-contained {@link Recording} that can be persisted (JSON-safe if the
|
|
27
|
+
* caller's clone yields JSON-safe shapes), shipped to Studio, or fed back
|
|
28
|
+
* into `replay()`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function record<Ctx>(hook: Hook<Ctx>, ctx: Ctx, opts?: RecordOptions): Promise<Recording>;
|
|
31
|
+
/** Comparison result returned by {@link replay}. */
|
|
32
|
+
export interface ReplayResult<Ctx> {
|
|
33
|
+
readonly matches: boolean;
|
|
34
|
+
readonly drift: ReadonlyArray<string>;
|
|
35
|
+
readonly recorded: Recording;
|
|
36
|
+
readonly replayed: {
|
|
37
|
+
readonly ctx: Ctx;
|
|
38
|
+
readonly stepCount: number;
|
|
39
|
+
readonly outcome: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Replay a recording. Re-runs the hook with `recording.ctxIn` (cloned),
|
|
44
|
+
* compares the resulting step sequence + outcome against the recording,
|
|
45
|
+
* and reports drift. This is observational — it does not stub side effects.
|
|
46
|
+
* Pure / idempotent chains should match; non-deterministic chains report
|
|
47
|
+
* their drift so a human can decide whether it's expected.
|
|
48
|
+
*/
|
|
49
|
+
export declare function replay<Ctx>(hook: Hook<Ctx>, recording: Recording, opts?: RecordOptions): Promise<ReplayResult<Ctx>>;
|
|
50
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErD,MAAM,WAAW,aAAc,SAAQ,UAAU;IAC/C,sEAAsE;IACtE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;CACnC;AAQD;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAC9B,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EACf,GAAG,EAAE,GAAG,EACR,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,SAAS,CAAC,CAepB;AAED,oDAAoD;AACpD,MAAM,WAAW,YAAY,CAAC,GAAG;IAC/B,QAAQ,CAAC,OAAO,EAAG,OAAO,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAK,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAChG;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAC9B,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EACf,SAAS,EAAE,SAAS,EACpB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAoC5B"}
|