@nwire/hooks 0.8.17 → 0.9.1
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 +53 -690
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +45 -6
- package/dist/hook.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,745 +1,108 @@
|
|
|
1
1
|
# @nwire/hooks
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> subscription, plugin extension, and dev-tool tap in Nwire is built from one
|
|
5
|
-
> shape: a named hook that runs a koa-compose chain first, then fans out
|
|
6
|
-
> parallel listeners. Auditable, traceable, replayable, topology-aware.
|
|
3
|
+
The universal dispatch primitive. One `hook()` accepts both a sequential chain (`.use`) and parallel listeners (`.on`); every run is observable end-to-end and cancellable via `AbortSignal`.
|
|
7
4
|
|
|
8
5
|
```bash
|
|
9
6
|
pnpm add @nwire/hooks
|
|
10
7
|
```
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
## Why
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
Every framework reinvents the same three patterns: middleware chains, event listeners, lifecycle hooks. They all need ordering, cancellation, tracing, and replay. `@nwire/hooks` is the one substrate the rest of Nwire builds on — handlers, transports, plugin lifecycle, framework events all use the same primitive.
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
import { hook } from "@nwire/hooks";
|
|
18
|
-
|
|
19
|
-
const request = hook<{ url: string; user?: string }>("http.request");
|
|
20
|
-
|
|
21
|
-
request.use(async (ctx, next) => {
|
|
22
|
-
ctx.user = await auth(ctx);
|
|
23
|
-
await next();
|
|
24
|
-
});
|
|
25
|
-
request.use(
|
|
26
|
-
async (ctx, next) => {
|
|
27
|
-
/* metrics */ await next();
|
|
28
|
-
},
|
|
29
|
-
{ priority: 100 },
|
|
30
|
-
);
|
|
31
|
-
request.on((ctx) => analytics.track(ctx), { when: "success" });
|
|
32
|
-
|
|
33
|
-
await request.run({ url: "/api/x" });
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Two attachment kinds, one primitive:
|
|
13
|
+
About 100 lines of surface; sub-microsecond per-step overhead; standalone.
|
|
37
14
|
|
|
38
|
-
|
|
39
|
-
Can short-circuit by not calling `next()`. Throws bubble up.
|
|
40
|
-
- **`.on(fn)`** — listener. Observer `(ctx)`. Cannot mutate. Cannot bail.
|
|
41
|
-
Fired in parallel via `Promise.allSettled` after the chain settles.
|
|
42
|
-
|
|
43
|
-
Everything else — telemetry taps, source capture, run-id linkage, recording,
|
|
44
|
-
priorities, success/failure filters — sits on top of those two without
|
|
45
|
-
changing them.
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## Why this exists
|
|
50
|
-
|
|
51
|
-
The Nwire stack used to ship six overlapping middleware/hook substrates —
|
|
52
|
-
one per package, each with its own composer, error policy, and observability
|
|
53
|
-
story. `@nwire/hooks` is the single primitive they all reduce to:
|
|
54
|
-
|
|
55
|
-
| Substrate | Maps to |
|
|
56
|
-
| --------------------------------------------- | ------------------------------------------------------------- |
|
|
57
|
-
| `@nwire/handler` `defineMiddleware` + `pipe` | chain via `.use()` |
|
|
58
|
-
| `@nwire/handler` `defineHook("after", fn)` | listener via `.on(fn, { when: "success" })` |
|
|
59
|
-
| `@nwire/forge` `runtime.use(mw)` | chain via `.use()` on the dispatch hook |
|
|
60
|
-
| `@nwire/http` `httpInterface().use()` | chain via `.use()` on the http-request hook |
|
|
61
|
-
| `@nwire/http` per-route `middleware: […]` | per-route hook + chain |
|
|
62
|
-
| `definePlugin({ middleware, actorHooks })` | chain (middleware) + listener (actorHooks) |
|
|
63
|
-
| `definePlugin({ before, after })` | chain veto (`before`) + filtered listener (`after`) |
|
|
64
|
-
| `@nwire/app` framework events — `parallel` | listener fan-out |
|
|
65
|
-
| `@nwire/app` framework events — `series` | chain without bail |
|
|
66
|
-
| `@nwire/app` framework events — `series-bail` | chain with `.runDetailed()` reading `outcome === "prevented"` |
|
|
67
|
-
|
|
68
|
-
If you're building a new extension point: it's a hook.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## The contract — behavior matrix
|
|
73
|
-
|
|
74
|
-
| Question | Answer |
|
|
75
|
-
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
|
76
|
-
| Chain ordering | Higher `priority` first (outermost). Stable on ties — equal priority preserves insertion order. |
|
|
77
|
-
| Listener ordering | Higher `priority` first. Listeners run in parallel; ordering is _attempt_ order only. |
|
|
78
|
-
| What if a chain step doesn't call `next()`? | Chain short-circuits. `outcome === "prevented"`. Listeners still fire (subject to `when`). |
|
|
79
|
-
| What if a chain step throws? | `.run()` rejects with the error. `outcome === "failed"`. Listeners still fire (subject to `when`). `ctx.error` is set. |
|
|
80
|
-
| What if a listener throws? | Reported via `onListenerError` (default: `console.error`). The run does **not** fail. Opt in with `strictListeners: true`. |
|
|
81
|
-
| Can a listener cancel the run? | No. |
|
|
82
|
-
| Can a chain step mutate ctx? | Yes. Listeners see the post-chain ctx. |
|
|
83
|
-
| Can listeners run before the chain finishes? | No. |
|
|
84
|
-
| Is `next()` allowed to be called twice? | No — throws `Error("next() called multiple times in @nwire/hooks chain")`. |
|
|
85
|
-
| Are taps observers or participants? | Observers. Tap throws are swallowed. |
|
|
86
|
-
| Run-id propagation across nested `.run()` calls? | Automatic via `AsyncLocalStorage`. Inner run's `parentRunId` = outer run's `runId`. |
|
|
87
|
-
| Is recording side-effect free? | No — it executes the hook normally and snapshots ctx before + after. Replay re-executes the chain. |
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Core API
|
|
92
|
-
|
|
93
|
-
### `hook(name, options?)`
|
|
15
|
+
## Surface
|
|
94
16
|
|
|
95
17
|
```ts
|
|
96
18
|
import { hook } from "@nwire/hooks";
|
|
97
19
|
|
|
98
|
-
const
|
|
99
|
-
strictListeners: false, // default
|
|
100
|
-
onListenerError: (err, ctx, hookName) => logger.error(...),
|
|
101
|
-
});
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Creates a fresh hook. Captures the call-site source location for Studio +
|
|
105
|
-
`nwire scan`. Adds it to the process-wide registry so `listHooks()` sees it.
|
|
20
|
+
const onRequest = hook<RequestCtx>("app.onRequest");
|
|
106
21
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
readonly name: string;
|
|
112
|
-
readonly id: string; // process-unique
|
|
22
|
+
onRequest.use(fn, { priority?, name? }); // chain middleware — sequential, can short-circuit
|
|
23
|
+
onRequest.on(fn, { when?, priority? }); // listener — parallel, observes final ctx
|
|
24
|
+
onRequest.off(fn); // detach
|
|
25
|
+
onRequest.tap(observer); // subscribe to per-step observations
|
|
113
26
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
off(fn): this; // detach
|
|
117
|
-
|
|
118
|
-
run(ctx: Ctx, opts?: RunOptions): Promise<Ctx>;
|
|
119
|
-
runDetailed(ctx, opts?): Promise<RunResult<Ctx>>;
|
|
120
|
-
|
|
121
|
-
tap(observer): () => void; // per-step trace
|
|
122
|
-
stepCounts(): { chain: number; listeners: number };
|
|
123
|
-
}
|
|
27
|
+
await onRequest.run(ctx, { signal? }); // run chain + listeners
|
|
28
|
+
await onRequest.runDetailed(ctx, opts); // same + return full step trace
|
|
124
29
|
```
|
|
125
30
|
|
|
126
|
-
|
|
31
|
+
Every ctx that flows through a hook gets two fields injected (non-enumerable, so `structuredClone` walks past them):
|
|
127
32
|
|
|
128
33
|
```ts
|
|
129
|
-
interface
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
interface OnOptions extends UseOptions {
|
|
134
|
-
when?: "always" | "success" | "failure"; // default "always"
|
|
34
|
+
interface BaseHookCtx {
|
|
35
|
+
readonly signal: AbortSignal; // caller-supplied or non-aborting placeholder
|
|
36
|
+
set<K, V>(key: K, value: V): asserts this is this & { [P in K]: V };
|
|
135
37
|
}
|
|
136
38
|
```
|
|
137
39
|
|
|
138
|
-
|
|
40
|
+
`signal` is the cooperative cancellation contract — every async step can forward it (`fetch(url, { signal })`, `pg.query(…, { signal })`) and react via `signal.throwIfAborted()`.
|
|
139
41
|
|
|
140
|
-
|
|
141
|
-
interface RunOptions {
|
|
142
|
-
signal?: AbortSignal; // injects onto ctx.signal too; thrown if aborted before next step
|
|
143
|
-
parentRunId?: string; // override the ambient parent
|
|
144
|
-
}
|
|
145
|
-
```
|
|
42
|
+
`.set()` is the typed widening primitive — middleware contributes a new field, the TS assertion narrows `ctx` for downstream steps.
|
|
146
43
|
|
|
147
|
-
|
|
44
|
+
## Consumer example
|
|
148
45
|
|
|
149
46
|
```ts
|
|
150
|
-
|
|
151
|
-
ctx: Ctx;
|
|
152
|
-
outcome: "completed" | "prevented" | "failed";
|
|
153
|
-
runId: string;
|
|
154
|
-
parentRunId?: string;
|
|
155
|
-
steps: StepObservation[];
|
|
156
|
-
durationMs: number;
|
|
157
|
-
error?: unknown; // set when outcome === "failed"
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
`run(ctx)` is the simple form. It returns the (mutated) ctx and throws on
|
|
162
|
-
failure — the way every existing call site has worked for years.
|
|
163
|
-
`runDetailed(ctx)` returns the structured result instead. Use it when you
|
|
164
|
-
need the outcome distinction (e.g. framework events that need to know
|
|
165
|
-
"was this prevented?").
|
|
166
|
-
|
|
167
|
-
### `compose(fns)` / `pipe(...fns)` / `withTimeout(ms, fn)` / `withRetry(opts, fn)`
|
|
168
|
-
|
|
169
|
-
Helpers for assembling reusable chain pieces before attaching them to a hook.
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
import { pipe, withRetry, withTimeout } from "@nwire/hooks";
|
|
173
|
-
|
|
174
|
-
const protected = pipe(
|
|
175
|
-
authenticate,
|
|
176
|
-
withRetry({ attempts: 3, delayMs: 100 }, callExternalApi),
|
|
177
|
-
withTimeout(5_000, persistResult),
|
|
178
|
-
);
|
|
179
|
-
hook.use(protected);
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
## Observability — what every hook surfaces for free
|
|
185
|
-
|
|
186
|
-
Every hook emits a stream of `StepObservation` events. Subscribe with
|
|
187
|
-
`.tap(observer)` to wire it into:
|
|
188
|
-
|
|
189
|
-
- structured logs (`logger.info(obs)`)
|
|
190
|
-
- Studio Live (push over SSE)
|
|
191
|
-
- `nwire scan` (write to `.nwire/observations.json`)
|
|
192
|
-
- OTel adapter (one span per `start → end` pair)
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
interface StepObservation {
|
|
196
|
-
hookName: string;
|
|
197
|
-
hookId: string;
|
|
198
|
-
runId: string;
|
|
199
|
-
parentRunId?: string;
|
|
200
|
-
stepId: number;
|
|
201
|
-
stepKind: "chain" | "listener";
|
|
202
|
-
stepName?: string; // from UseOptions.name / OnOptions.name
|
|
203
|
-
phase: "start" | "end" | "error";
|
|
204
|
-
ts: number; // performance.now()
|
|
205
|
-
durationMs?: number; // set on end + error
|
|
206
|
-
error?: unknown; // set on error
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Topology — caller / callee linkage
|
|
211
|
-
|
|
212
|
-
`AsyncLocalStorage` propagates the active `runId` through the chain. When
|
|
213
|
-
your chain step calls `await otherHook.run(...)`, the nested run picks up
|
|
214
|
-
the outer `runId` as its `parentRunId` automatically — no plumbing.
|
|
215
|
-
|
|
216
|
-
```ts
|
|
217
|
-
import { hook, currentRun } from "@nwire/hooks";
|
|
47
|
+
import { hook } from "@nwire/hooks";
|
|
218
48
|
|
|
219
|
-
|
|
220
|
-
const
|
|
49
|
+
type ReqCtx = { url: string; method: string; status?: number };
|
|
50
|
+
const onRequest = hook<ReqCtx>("app.onRequest");
|
|
221
51
|
|
|
222
|
-
|
|
223
|
-
|
|
52
|
+
// Logger — plain async fn, wraps + measures
|
|
53
|
+
onRequest.use(async (ctx, next) => {
|
|
54
|
+
const t = performance.now();
|
|
55
|
+
console.log(`→ ${ctx.method} ${ctx.url}`);
|
|
224
56
|
await next();
|
|
57
|
+
console.log(`← ${ctx.status} (${(performance.now() - t).toFixed(1)}ms)`);
|
|
225
58
|
});
|
|
226
59
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
});
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
This is the substrate Studio's Trace page uses to render the causal tree.
|
|
233
|
-
|
|
234
|
-
### Source location
|
|
235
|
-
|
|
236
|
-
`hook()` captures its call site at construction. `listHooks()` exposes it
|
|
237
|
-
to `nwire scan` and Studio so every hook in the manifest has a click-to-open
|
|
238
|
-
pill — same UX as actions/events/projections.
|
|
239
|
-
|
|
240
|
-
```ts
|
|
241
|
-
import { listHooks } from "@nwire/hooks";
|
|
242
|
-
|
|
243
|
-
console.log(listHooks());
|
|
244
|
-
// [
|
|
245
|
-
// { id: "h1", name: "endpoint.boot", chain: 2, listeners: 1, source: { file, line } },
|
|
246
|
-
// { id: "h2", name: "http.request", chain: 4, listeners: 0, source: { file, line } },
|
|
247
|
-
// ]
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Recording + replay
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
import { record, replay } from "@nwire/hooks";
|
|
254
|
-
|
|
255
|
-
// Production: capture the full trace from one run.
|
|
256
|
-
const recording = await record(myHook, ctx);
|
|
257
|
-
await fetch("/__nwire/recordings", { method: "POST", body: JSON.stringify(recording) });
|
|
258
|
-
|
|
259
|
-
// Dev / debug: replay against the same hook and assert on drift.
|
|
260
|
-
const result = await replay(myHook, recording);
|
|
261
|
-
if (!result.matches) console.warn("drift:", result.drift);
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
`record` runs the hook once, snapshots `ctxIn`/`ctxOut` (via `structuredClone`
|
|
265
|
-
by default), and returns a self-contained, JSON-serializable trace. `replay`
|
|
266
|
-
re-runs the hook with the captured `ctxIn` (cloned) and compares step
|
|
267
|
-
sequences — flagging drift in step count, names, order, or outcome.
|
|
268
|
-
|
|
269
|
-
This is observational, not stubbing. Pure / idempotent chains match
|
|
270
|
-
deterministically; non-deterministic chains report exactly what diverged.
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## Consumer APIs — the surfaces every nwire package presents
|
|
275
|
-
|
|
276
|
-
This is the locked-in contract each downstream package exposes to its users.
|
|
277
|
-
Internally they all reduce to `hook()`, but the public API stays familiar
|
|
278
|
-
and ergonomic. This table is the source of truth — anything outside it is
|
|
279
|
-
not a public API of that package.
|
|
280
|
-
|
|
281
|
-
### `@nwire/forge`
|
|
282
|
-
|
|
283
|
-
```ts
|
|
284
|
-
// Runtime dispatch middleware — onion around every action handler.
|
|
285
|
-
runtime.use(mw: DispatchMiddleware): void;
|
|
286
|
-
type DispatchMiddleware = (
|
|
287
|
-
next: () => Promise<EventMessage | EventMessage[] | void>,
|
|
288
|
-
action: ActionDefinition,
|
|
289
|
-
input: unknown,
|
|
290
|
-
ctx: HandlerContext,
|
|
291
|
-
) => Promise<EventMessage | EventMessage[] | void>;
|
|
292
|
-
// → forge.action.dispatch hook · chain · priority by registration order
|
|
293
|
-
|
|
294
|
-
// Actor transition hook — observable, fires after every state change.
|
|
295
|
-
runtime.actorHook(fn: ActorTransitionHook): void;
|
|
296
|
-
type ActorTransitionHook = (
|
|
297
|
-
actor: ActorDefinition,
|
|
298
|
-
key: string,
|
|
299
|
-
fromState: string,
|
|
300
|
-
toState: string,
|
|
301
|
-
event: EventMessage,
|
|
302
|
-
envelope: MessageEnvelope,
|
|
303
|
-
) => Promise<void> | void;
|
|
304
|
-
// → forge.actor.transition hook · listener · when=always
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### `@nwire/forge` — plugin builder (closure form)
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
definePlugin("name", ({ bind, resolve, on, before, after, middleware, actorHook, boot, shutdown }) => {
|
|
311
|
-
// — Generic framework event subscription.
|
|
312
|
-
on<TPayload>(event, handler, priority?): void;
|
|
313
|
-
// → underlying framework-event hook · chain or listener per event.mode
|
|
314
|
-
|
|
315
|
-
// — Action-scoped sugar.
|
|
316
|
-
before(actionName, fn): void;
|
|
317
|
-
// fn returns void | false. false short-circuits ("prevented"); throw fails.
|
|
318
|
-
// → action.before:<name> hook · chain · use+next bail
|
|
319
|
-
after(actionName, fn): void;
|
|
320
|
-
// fn observes result + durationMs. Errors logged, not fatal.
|
|
321
|
-
// → action.after:<name> hook · listener · when=success
|
|
322
|
-
|
|
323
|
-
// — Dispatch middleware (same shape as runtime.use).
|
|
324
|
-
middleware(mw: DispatchMiddleware): void;
|
|
325
|
-
// → forge.action.dispatch hook · chain
|
|
326
|
-
|
|
327
|
-
// — Actor transitions (same shape as runtime.actorHook).
|
|
328
|
-
actorHook(fn: ActorTransitionHook): void;
|
|
329
|
-
// → forge.actor.transition hook · listener
|
|
330
|
-
|
|
331
|
-
// — Plugin lifecycle. Run order = registration; shutdown is reverse.
|
|
332
|
-
boot(fn: () => Promise<void> | void): void;
|
|
333
|
-
shutdown(fn: () => Promise<void> | void): void;
|
|
334
|
-
});
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### `@nwire/app` — framework events (3 dispatch modes)
|
|
338
|
-
|
|
339
|
-
```ts
|
|
340
|
-
// Subscribe (priority desc; default 0).
|
|
341
|
-
bus.on(event, handler, priority?): () => void;
|
|
342
|
-
|
|
343
|
-
// Fire. Returns false only when a series-bail handler vetoed.
|
|
344
|
-
bus.fire(event, payload): Promise<boolean>;
|
|
345
|
-
|
|
346
|
-
// Per-firing observer — sees every event, cannot veto. For dev-logger,
|
|
347
|
-
// Studio Live, OTel.
|
|
348
|
-
bus.onFire(observer): () => void;
|
|
349
|
-
type FrameworkEventObservation = {
|
|
350
|
-
eventName: string;
|
|
351
|
-
payload: unknown;
|
|
352
|
-
mode: "parallel" | "series" | "series-bail";
|
|
353
|
-
phase: "fired" | "prevented";
|
|
354
|
-
ts: string;
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// Modes — set when defining the event.
|
|
358
|
-
defineFrameworkEvent<TPayload>(name, mode: "parallel" | "series" | "series-bail");
|
|
359
|
-
// parallel → listener fan-out (Promise.allSettled, errors logged)
|
|
360
|
-
// series → chain without bail (sequential await; throw stops)
|
|
361
|
-
// series-bail → chain with bail (sequential await; return false short-circuits)
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### `@nwire/handler` — resolver middleware
|
|
365
|
-
|
|
366
|
-
```ts
|
|
367
|
-
defineMiddleware(name?, fn: (ctx, next) => Promise<void> | void): MiddlewareDefinition;
|
|
368
|
-
// → resolver hook · chain
|
|
369
|
-
|
|
370
|
-
defineHook("before", name?, fn: (ctx) => Promise<void> | void): HookDefinition;
|
|
371
|
-
// → resolver hook · listener · when=always · runs before handler
|
|
372
|
-
defineHook("after", name?, fn: (ctx, result) => Promise<void> | void): HookDefinition;
|
|
373
|
-
// → resolver hook · listener · when=success · runs after handler
|
|
374
|
-
|
|
375
|
-
pipe(...steps: PipeStep[]): MiddlewarePipe;
|
|
376
|
-
// compose middlewares + hooks; transports unwind into chain + listeners
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### `@nwire/http` — request pipeline
|
|
380
|
-
|
|
381
|
-
```ts
|
|
382
|
-
httpInterface(opts?)
|
|
383
|
-
.use(mw: KoaMiddleware): this;
|
|
384
|
-
// → http.request hook · chain · global to this interface
|
|
385
|
-
.provide(container): this;
|
|
386
|
-
.wire(route: RouteBinding, handler: HttpHandler): this;
|
|
387
|
-
// per-route middleware lives on the binding:
|
|
388
|
-
// route = post("/path", { body, middleware: [mw1, mw2], openapi })
|
|
389
|
-
// → http.request:<METHOD> <path> hook · chain · scoped to this route
|
|
390
|
-
.compile(): KoaApp;
|
|
391
|
-
.toExpress(): ExpressMiddleware;
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### `@nwire/auth` — canonical resolvers + middleware
|
|
395
|
-
|
|
396
|
-
```ts
|
|
397
|
-
// All canonical auth operations are resolvers (SignIn, SignOut, Refresh,
|
|
398
|
-
// Register, Me) and run through the @nwire/handler pipeline above.
|
|
399
|
-
identityPlugin({ adapter, scopes? }): PluginDefinition;
|
|
400
|
-
// contributes:
|
|
401
|
-
// middleware → forge.action.dispatch · chain · enriches ctx.envelope.user
|
|
402
|
-
// resolvers → /auth/*
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### `@nwire/rbac`
|
|
406
|
-
|
|
407
|
-
```ts
|
|
408
|
-
defineAbility((user, { allow, deny }) => { ... }): AbilityFactory;
|
|
409
|
-
rbacPlugin({ ability }): PluginDefinition;
|
|
410
|
-
// contributes:
|
|
411
|
-
// middleware → forge.action.dispatch · chain · enforces action.policy
|
|
412
|
-
|
|
413
|
-
// Resolver-level:
|
|
414
|
-
can(action: string, subject: string | Subject): MiddlewareDefinition;
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
### `@nwire/observability` (tracing + auth-as-plugin)
|
|
418
|
-
|
|
419
|
-
```ts
|
|
420
|
-
tracingPlugin({ tracer? }): PluginDefinition;
|
|
421
|
-
// wires OTel via .tap() on the dispatch + framework-event hooks.
|
|
422
|
-
|
|
423
|
-
// Application code rarely calls hooks directly — observability plugs in
|
|
424
|
-
// at boot, taps every hook in listHooks(), and emits spans / logs.
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Test surface — `@nwire/test-kit`
|
|
428
|
-
|
|
429
|
-
```ts
|
|
430
|
-
const harness = createTestHarness({ app });
|
|
431
|
-
// Every hook the app creates is .tap()-able through harness.observe():
|
|
432
|
-
harness.observe(hookName, (obs) => { ... });
|
|
433
|
-
// Recordings can be captured + diffed in tests:
|
|
434
|
-
const rec = await harness.record(hookName, ctx);
|
|
435
|
-
expect(rec.steps.map((s) => s.stepName)).toEqual([...]);
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Studio + scan
|
|
439
|
-
|
|
440
|
-
`nwire scan` emits `.nwire/hooks.json`:
|
|
441
|
-
|
|
442
|
-
```json
|
|
443
|
-
[
|
|
444
|
-
{
|
|
445
|
-
"id": "h1",
|
|
446
|
-
"name": "forge.action.dispatch",
|
|
447
|
-
"chain": 4,
|
|
448
|
-
"listeners": 0,
|
|
449
|
-
"source": { "file": "...", "line": 12 }
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
"id": "h2",
|
|
453
|
-
"name": "nwire.app.booting",
|
|
454
|
-
"chain": 2,
|
|
455
|
-
"listeners": 0,
|
|
456
|
-
"source": { "file": "...", "line": 8 }
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
"id": "h3",
|
|
460
|
-
"name": "action.after:CreateStation",
|
|
461
|
-
"chain": 0,
|
|
462
|
-
"listeners": 2,
|
|
463
|
-
"source": { "file": "...", "line": 23 }
|
|
464
|
-
}
|
|
465
|
-
]
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
Studio surfaces a **Hooks** page that lists these with source pills, a per-hook
|
|
469
|
-
inspector showing attached chain + listeners with their names + priorities,
|
|
470
|
-
and a Trace mode that overlays live `.tap()` observations onto the causation
|
|
471
|
-
tree using `runId` / `parentRunId`.
|
|
472
|
-
|
|
473
|
-
---
|
|
474
|
-
|
|
475
|
-
## Recipes — wiring the substrate
|
|
476
|
-
|
|
477
|
-
### 1. Forge — `runtime.use(middleware)`
|
|
478
|
-
|
|
479
|
-
```ts
|
|
480
|
-
const dispatch = hook<DispatchCtx>("forge.action.dispatch");
|
|
481
|
-
|
|
482
|
-
dispatch.use(
|
|
60
|
+
// Auth — contributes via typed .set, short-circuits on rejection
|
|
61
|
+
onRequest.use(
|
|
483
62
|
async (ctx, next) => {
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
} catch (err) {
|
|
489
|
-
span.recordException(err);
|
|
490
|
-
throw err;
|
|
63
|
+
const token = readToken(ctx.url);
|
|
64
|
+
if (!token) {
|
|
65
|
+
ctx.set("status", 401); // short-circuit (no next())
|
|
66
|
+
return;
|
|
491
67
|
}
|
|
492
|
-
|
|
493
|
-
{ name: "tracing", priority: 100 },
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
// `runtime.use(mw)` is sugar:
|
|
497
|
-
runtime.use = (mw) => dispatch.use(adaptDispatchMiddleware(mw));
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### 2. HTTP — global `.use()` + per-route `middleware: [...]`
|
|
501
|
-
|
|
502
|
-
```ts
|
|
503
|
-
const httpRequest = hook<KoaCtx>("http.request"); // global
|
|
504
|
-
const routeRequest = (route) => hook<KoaCtx>(`http.request:${route.method} ${route.path}`); // per route
|
|
505
|
-
|
|
506
|
-
httpRequest.use(cors());
|
|
507
|
-
httpRequest.use(bodyParser());
|
|
508
|
-
const routeHook = routeRequest(route);
|
|
509
|
-
for (const mw of route.middleware) routeHook.use(mw);
|
|
510
|
-
routeHook.use(invokeHandler(route));
|
|
511
|
-
|
|
512
|
-
// At dispatch:
|
|
513
|
-
await httpRequest.run(ctx);
|
|
514
|
-
await routeHook.run(ctx);
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### 3. Handler — `defineMiddleware` + `defineHook("after", ...)`
|
|
518
|
-
|
|
519
|
-
```ts
|
|
520
|
-
// Migrate:
|
|
521
|
-
const authenticate = defineMiddleware(async (ctx, next) => { ... });
|
|
522
|
-
const auditLog = defineHook("after", async (ctx, result) => { ... });
|
|
523
|
-
|
|
524
|
-
// To:
|
|
525
|
-
resolverHook.use(authenticate.fn, { name: "authenticate" });
|
|
526
|
-
resolverHook.on((ctx) => auditLog.fn(ctx, ctx.result), { when: "success", name: "audit-log" });
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
### 4. Plugin — `before(actionName, fn)` (series-bail)
|
|
530
|
-
|
|
531
|
-
```ts
|
|
532
|
-
const beforeAction = hook<BeforeCtx>("action.before:" + actionName);
|
|
533
|
-
|
|
534
|
-
beforeAction.use(async (ctx, next) => {
|
|
535
|
-
const allowed = await fn(ctx);
|
|
536
|
-
if (allowed === false) return; // short-circuit -> outcome "prevented"
|
|
537
|
-
await next();
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
// Dispatch:
|
|
541
|
-
const result = await beforeAction.runDetailed(ctx);
|
|
542
|
-
if (result.outcome === "prevented") return /* skip the action */;
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
### 5. Plugin — `after(actionName, fn)` (parallel observer, success-only)
|
|
546
|
-
|
|
547
|
-
```ts
|
|
548
|
-
const afterAction = hook<AfterCtx>("action.after:" + actionName);
|
|
549
|
-
afterAction.on(fn, { when: "success", name: actionName + ":after" });
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### 6. Plugin — `actorHooks.afterTransition`
|
|
553
|
-
|
|
554
|
-
```ts
|
|
555
|
-
const actorTransition = hook<TransitionCtx>("actor.transition");
|
|
556
|
-
actorTransition.on((ctx) => userHook(ctx.actor, ctx.key, ctx.from, ctx.to, ctx.event));
|
|
557
|
-
|
|
558
|
-
// runtime emits on every transition:
|
|
559
|
-
await actorTransition.run({ actor, key, from, to, event, envelope });
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
### 7. App framework events — three modes
|
|
563
|
-
|
|
564
|
-
```ts
|
|
565
|
-
const ev = hook<Payload>("nwire.app.booting", {
|
|
566
|
-
// strictListeners=false matches "parallel mode" — handler errors are logged.
|
|
567
|
-
strictListeners: false,
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// parallel:
|
|
571
|
-
ev.on(handler, { priority });
|
|
572
|
-
|
|
573
|
-
// series:
|
|
574
|
-
ev.use(
|
|
575
|
-
async (ctx, next) => {
|
|
576
|
-
await handler(ctx);
|
|
577
|
-
await next();
|
|
578
|
-
},
|
|
579
|
-
{ priority },
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
// series-bail:
|
|
583
|
-
ev.use(
|
|
584
|
-
async (ctx, next) => {
|
|
585
|
-
const ret = await handler(ctx);
|
|
586
|
-
if (ret === false) return; // short-circuit
|
|
68
|
+
ctx.set("userId", await verify(token)); // ctx widened to include userId
|
|
587
69
|
await next();
|
|
588
70
|
},
|
|
589
|
-
{ priority },
|
|
71
|
+
{ priority: 100 },
|
|
590
72
|
);
|
|
591
73
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
### 8. Studio / scan integration
|
|
74
|
+
// Metrics — listener, never mutates
|
|
75
|
+
onRequest.on((ctx) => metrics.tick(`http.${ctx.method}.${ctx.status}`));
|
|
597
76
|
|
|
598
|
-
|
|
599
|
-
import { listHooks } from "@nwire/hooks";
|
|
600
|
-
|
|
601
|
-
// scan emits per-hook metadata to .nwire/hooks.json
|
|
602
|
-
const manifest = listHooks().map((h) => ({
|
|
603
|
-
id: h.id,
|
|
604
|
-
name: h.name,
|
|
605
|
-
chain: h.chain,
|
|
606
|
-
listeners: h.listeners,
|
|
607
|
-
source: h.source,
|
|
608
|
-
}));
|
|
77
|
+
await onRequest.run({ url: "/api/orders", method: "GET" });
|
|
609
78
|
```
|
|
610
79
|
|
|
611
|
-
|
|
80
|
+
## Cancellation in practice
|
|
612
81
|
|
|
613
82
|
```ts
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const tracer = trace.getTracer("nwire");
|
|
617
|
-
const spans = new Map<string, ReturnType<typeof tracer.startSpan>>();
|
|
618
|
-
|
|
619
|
-
myHook.tap((obs) => {
|
|
620
|
-
const key = `${obs.runId}:${obs.stepId}:${obs.stepKind}`;
|
|
621
|
-
if (obs.phase === "start") {
|
|
622
|
-
spans.set(key, tracer.startSpan(`${obs.hookName}.${obs.stepName ?? obs.stepKind}`));
|
|
623
|
-
} else if (obs.phase === "end") {
|
|
624
|
-
spans.get(key)?.end();
|
|
625
|
-
spans.delete(key);
|
|
626
|
-
} else if (obs.phase === "error") {
|
|
627
|
-
const s = spans.get(key);
|
|
628
|
-
if (s) {
|
|
629
|
-
s.recordException(obs.error as Error);
|
|
630
|
-
s.end();
|
|
631
|
-
spans.delete(key);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
### 10. Structured logger tap
|
|
83
|
+
const ac = new AbortController();
|
|
84
|
+
setTimeout(() => ac.abort(new Error("user cancelled")), 5000);
|
|
638
85
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
msg: "hook.step",
|
|
643
|
-
hook: obs.hookName,
|
|
644
|
-
runId: obs.runId,
|
|
645
|
-
parentRun: obs.parentRunId,
|
|
646
|
-
step: obs.stepName ?? `#${obs.stepId}`,
|
|
647
|
-
phase: obs.phase,
|
|
648
|
-
durationMs: obs.durationMs,
|
|
649
|
-
error: obs.error && (obs.error as Error).message,
|
|
650
|
-
});
|
|
651
|
-
});
|
|
86
|
+
await onRequest.run(ctx, { signal: ac.signal });
|
|
87
|
+
// inside any step: signal.throwIfAborted() bails;
|
|
88
|
+
// the abort propagates through nested .run() calls too
|
|
652
89
|
```
|
|
653
90
|
|
|
654
|
-
|
|
91
|
+
Combine with timeouts:
|
|
655
92
|
|
|
656
93
|
```ts
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
await myHook.run(ctx, { signal: ac.signal });
|
|
661
|
-
// ctx.signal is set so inner code can poll:
|
|
662
|
-
// if (ctx.signal?.aborted) return;
|
|
94
|
+
const timeout = AbortSignal.timeout(2000);
|
|
95
|
+
await onRequest.run(ctx, { signal: AbortSignal.any([userSignal, timeout]) });
|
|
663
96
|
```
|
|
664
97
|
|
|
665
|
-
|
|
98
|
+
## Observability
|
|
666
99
|
|
|
667
|
-
|
|
100
|
+
`.tap(observer)` emits a `StepObservation` per chain/listener phase (`start` / `end` / `error`) with timing, runId, parentRunId. `runDetailed()` returns the full trace + outcome. Used by `@nwire/scan` to emit `.nwire/hooks.json` and by Studio for live tap streams.
|
|
668
101
|
|
|
669
|
-
|
|
670
|
-
import { createHooks, hook } from "@nwire/hooks";
|
|
671
|
-
|
|
672
|
-
const lifecycle = createHooks({
|
|
673
|
-
registering: hook<RegisteringCtx>("nwire.app.registering"),
|
|
674
|
-
booting: hook<BootingCtx>("nwire.app.booting"),
|
|
675
|
-
ready: hook<ReadyCtx>("nwire.app.ready"),
|
|
676
|
-
shutdown: hook<ShutdownCtx>("nwire.app.shutdown"),
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
// Apply a plugin across all four at once:
|
|
680
|
-
lifecycle.use({
|
|
681
|
-
booting: async (ctx, next) => {
|
|
682
|
-
logger.info("booting");
|
|
683
|
-
await next();
|
|
684
|
-
},
|
|
685
|
-
ready: { on: (ctx) => logger.info("ready", { port: ctx.port }) },
|
|
686
|
-
shutdown: async (ctx, next) => {
|
|
687
|
-
await flushOutbox();
|
|
688
|
-
await next();
|
|
689
|
-
},
|
|
690
|
-
});
|
|
691
|
-
```
|
|
102
|
+
## Used by
|
|
692
103
|
|
|
693
|
-
|
|
104
|
+
`@nwire/handler` is built on `hook<HandlerRunCtx>()`. `@nwire/forge` runtime, framework-event bus, plugin lifecycle, and every transport in Nwire share the same substrate.
|
|
694
105
|
|
|
695
|
-
|
|
696
|
-
const full = createHooks({ ...lifecycle.hooks, request: hook<...>("http.request") });
|
|
697
|
-
```
|
|
106
|
+
## Scope
|
|
698
107
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
## Production guidance
|
|
702
|
-
|
|
703
|
-
- **Hot paths.** A `.use()` step adds one wrapper closure + one observation
|
|
704
|
-
emit. Per-request HTTP middleware on a busy server: ~250 ns/step in our
|
|
705
|
-
microbench. If you have a 100k-rps inner loop and don't need observability,
|
|
706
|
-
prefer a hand-rolled compose.
|
|
707
|
-
- **Listener errors.** Default policy is log + continue. This matches Node's
|
|
708
|
-
`EventEmitter` and Koa's "don't crash because metrics broke." Opt into
|
|
709
|
-
`strictListeners: true` for testing and any place where listener failure
|
|
710
|
-
must surface.
|
|
711
|
-
- **Taps.** Taps fire synchronously inside `.run()`. Don't do blocking I/O.
|
|
712
|
-
Buffer + flush async.
|
|
713
|
-
- **AbortSignal.** Aborting throws between steps. A long-running step that
|
|
714
|
-
never `await`s won't notice. Plumb `ctx.signal` into your I/O calls.
|
|
715
|
-
- **Topology.** `AsyncLocalStorage` survives microtasks and timers. It does
|
|
716
|
-
_not_ survive `setImmediate` if you escape the await chain. Stay in
|
|
717
|
-
`await` land if you care about parent linkage.
|
|
718
|
-
- **Recording size.** A recording grows linearly with step count; ctx
|
|
719
|
-
snapshots dominate the size. Use a custom `clone` to redact PII before
|
|
720
|
-
persisting.
|
|
721
|
-
|
|
722
|
-
---
|
|
723
|
-
|
|
724
|
-
## When NOT to use a hook
|
|
725
|
-
|
|
726
|
-
- A single, hard-coded step that has no extension point. Just call the
|
|
727
|
-
function.
|
|
728
|
-
- A synchronous calculation that never spans I/O. The async overhead is
|
|
729
|
-
wasted.
|
|
730
|
-
- Something that's fundamentally request/response with no observers and no
|
|
731
|
-
cancellability. A normal function is fine.
|
|
732
|
-
|
|
733
|
-
Hooks are for extension. If nobody will ever attach to it, don't make it
|
|
734
|
-
one.
|
|
735
|
-
|
|
736
|
-
---
|
|
737
|
-
|
|
738
|
-
## Versioning + stability
|
|
739
|
-
|
|
740
|
-
`@nwire/hooks` follows the framework's semver. The contract matrix in this
|
|
741
|
-
README is the law — changing any row is a major bump.
|
|
742
|
-
|
|
743
|
-
Already locked: `Hook<Ctx>` surface, `RunResult`, `StepObservation`,
|
|
744
|
-
`Recording`. New optional capabilities (more `RunOptions`, more tap fields)
|
|
745
|
-
are minor bumps; never breaking.
|
|
108
|
+
Standalone, in-process. Cross-process dispatch lives in `@nwire/bus`. Use this for middleware chains, lifecycle hooks, and pub-sub-within-process.
|
package/dist/hook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAWjB;;;;;;GAMG;AACH,MAAM,WAAW,IAAI,CAAC,GAAG;IACvB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,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
|
@@ -19,6 +19,12 @@ import Emittery from "emittery";
|
|
|
19
19
|
import { compose } from "./compose.js";
|
|
20
20
|
import { captureSourceLocation, nextHookId, nextRunId, registerHook, withRunContext, currentRun, } from "./registry.js";
|
|
21
21
|
const RUN_EVENT = "__nwire_hooks_run__";
|
|
22
|
+
/**
|
|
23
|
+
* A non-aborting placeholder signal. Used when `.run()` is called without
|
|
24
|
+
* `opts.signal` — every chain ctx is guaranteed to have `ctx.signal`, so
|
|
25
|
+
* domain code can forward it (to fetch, pg, etc.) without null-guarding.
|
|
26
|
+
*/
|
|
27
|
+
const NEVER_ABORTS = new AbortController().signal;
|
|
22
28
|
/** Create a new hook. */
|
|
23
29
|
export function hook(name, options = {}) {
|
|
24
30
|
const source = captureSourceLocation(2);
|
|
@@ -126,11 +132,39 @@ class HookImpl {
|
|
|
126
132
|
}
|
|
127
133
|
}
|
|
128
134
|
};
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
// Ensure ctx carries the two BaseHookCtx fields by the time any chain
|
|
136
|
+
// step runs. `signal` is sourced from opts.signal, falling back to a
|
|
137
|
+
// non-aborting placeholder so steps can pass it through without
|
|
138
|
+
// null-guarding. `.set(key, value)` is a typed setter that mutates ctx
|
|
139
|
+
// in place; its assertion-function signature widens the ctx type for
|
|
140
|
+
// the rest of the chain (see `BaseHookCtx` in types.ts).
|
|
141
|
+
//
|
|
142
|
+
// Both fields are defined as NON-ENUMERABLE own properties so
|
|
143
|
+
// `structuredClone` and JSON.stringify (used by record/replay + tracing)
|
|
144
|
+
// walk past them — `signal` (an AbortSignal) and `set` (a function) are
|
|
145
|
+
// not serializable, but they're framework-internal, not domain state.
|
|
146
|
+
// User-provided values on ctx win — we never overwrite.
|
|
147
|
+
if (ctx && typeof ctx === "object") {
|
|
148
|
+
const obj = ctx;
|
|
149
|
+
const desc = Object.getOwnPropertyDescriptor;
|
|
150
|
+
if (!desc(obj, "signal")) {
|
|
151
|
+
Object.defineProperty(obj, "signal", {
|
|
152
|
+
value: signal ?? NEVER_ABORTS,
|
|
153
|
+
enumerable: false,
|
|
154
|
+
writable: false,
|
|
155
|
+
configurable: true,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (!desc(obj, "set")) {
|
|
159
|
+
Object.defineProperty(obj, "set", {
|
|
160
|
+
value: function set(key, value) {
|
|
161
|
+
obj[key] = value;
|
|
162
|
+
},
|
|
163
|
+
enumerable: false,
|
|
164
|
+
writable: false,
|
|
165
|
+
configurable: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
134
168
|
}
|
|
135
169
|
const composed = compose(this.chain.map((entry) => wrapChain(this, entry, runId, parentRunId, signal, emit)));
|
|
136
170
|
// A chain step that doesn't call its `next()` short-circuits — the tail
|
|
@@ -147,7 +181,12 @@ class HookImpl {
|
|
|
147
181
|
}
|
|
148
182
|
catch (err) {
|
|
149
183
|
chainError = err;
|
|
150
|
-
|
|
184
|
+
// Record the chain error onto ctx so listeners + observers can see
|
|
185
|
+
// it. Guard against undefined/primitive ctx (some hooks fire with
|
|
186
|
+
// `undefined` payloads — e.g., framework-event lifecycle hooks).
|
|
187
|
+
if (ctx && typeof ctx === "object") {
|
|
188
|
+
ctx.error = err;
|
|
189
|
+
}
|
|
151
190
|
}
|
|
152
191
|
});
|
|
153
192
|
// Empty chain — nothing to short-circuit, treat as completed.
|
package/dist/hook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,qBAAqB,EACrB,UAAU,EACV,SAAS,EACT,YAAY,EACZ,cAAc,EACd,UAAU,GACX,MAAM,YAAY,CAAC;AAcpB,MAAM,SAAS,GAAG,qBAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,5 +14,5 @@ export { compose } from "./compose.js";
|
|
|
14
14
|
export { pipe, withTimeout, withRetry } from "./pipe.js";
|
|
15
15
|
export { record, replay, type RecordOptions, type ReplayResult } from "./record.js";
|
|
16
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";
|
|
17
|
+
export type { BaseHookCtx, ChainFn, HookOptions, ListenerFn, OnOptions, Recording, RetryOpts, RunOptions, RunOutcome, RunResult, SourceLocation, StepKind, StepObservation, StepPhase, TapFn, UseOptions, } from "./types.js";
|
|
18
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;;;;;;;;;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,EAAE,SAAS,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC1E,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"}
|
|
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,EAAE,SAAS,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC1E,YAAY,EACV,WAAW,EACX,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/types.d.ts
CHANGED
|
@@ -8,6 +8,32 @@
|
|
|
8
8
|
* The observability surface (StepObservation / RunResult / Recording) is the
|
|
9
9
|
* contract every consumer relies on for tracing, scan, Studio, OTel, replay.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* The two fields every hook ctx carries, contributed by the runtime in
|
|
13
|
+
* `.run()`. Consumers extend this shape with whatever their handler / step
|
|
14
|
+
* needs — but `signal` + `set` are always there.
|
|
15
|
+
*
|
|
16
|
+
* - `signal` — Web-standard cooperative cancellation. The runtime sources
|
|
17
|
+
* it from `RunOptions.signal`; if none is provided, a non-aborting
|
|
18
|
+
* placeholder is supplied so handlers can pass it through without
|
|
19
|
+
* null checks.
|
|
20
|
+
* - `set(key, value)` — typed setter for contributing fields mid-chain.
|
|
21
|
+
* Uses a TS assertion function so subsequent steps + the rest of the
|
|
22
|
+
* current step see the widened type:
|
|
23
|
+
*
|
|
24
|
+
* ctx.set("user", user); // ctx now widened to (TCtx & { user: User })
|
|
25
|
+
* await next();
|
|
26
|
+
*
|
|
27
|
+
* Plain reassignment (`ctx.user = x`) still works when the field is
|
|
28
|
+
* declared on TCtx — `.set()` is the *contributing* primitive for cases
|
|
29
|
+
* where the field wasn't pre-declared on the ctx.
|
|
30
|
+
*/
|
|
31
|
+
export interface BaseHookCtx {
|
|
32
|
+
readonly signal: AbortSignal;
|
|
33
|
+
set<K extends string, V>(key: K, value: V): asserts this is this & {
|
|
34
|
+
[P in K]: V;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
11
37
|
/** A single link in a chain — koa-compose-shaped middleware. */
|
|
12
38
|
export type ChainFn<Ctx> = (ctx: Ctx, next: () => Promise<void>) => Promise<void> | void;
|
|
13
39
|
/** A listener — observes the final ctx state, never mutates it. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwire/hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Nwire — the universal dispatch primitive. One hook() accepts both .use() (sequential koa-compose chain) and .on() (parallel listener) attachments. Built on emittery + a ~30 LOC composer. Standalone, in-process only, ~100 LOC of surface.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dispatch",
|