@setu-ts/kernel 0.0.0-stage → 0.10.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.
Files changed (79) hide show
  1. package/AGENT_CARD.md +47 -0
  2. package/LICENSE +21 -0
  3. package/README.md +152 -2
  4. package/dist/application/application.d.ts +174 -0
  5. package/dist/application/application.d.ts.map +1 -0
  6. package/dist/application/application.js +1905 -0
  7. package/dist/application/application.js.map +1 -0
  8. package/dist/application/inject-body.d.ts +58 -0
  9. package/dist/application/inject-body.d.ts.map +1 -0
  10. package/dist/application/inject-body.js +82 -0
  11. package/dist/application/inject-body.js.map +1 -0
  12. package/dist/context/request-context.d.ts +32 -0
  13. package/dist/context/request-context.d.ts.map +1 -0
  14. package/dist/context/request-context.js +136 -0
  15. package/dist/context/request-context.js.map +1 -0
  16. package/dist/context/response.d.ts +60 -0
  17. package/dist/context/response.d.ts.map +1 -0
  18. package/dist/context/response.js +193 -0
  19. package/dist/context/response.js.map +1 -0
  20. package/dist/diagnostics/buffer.d.ts +143 -0
  21. package/dist/diagnostics/buffer.d.ts.map +1 -0
  22. package/dist/diagnostics/buffer.js +190 -0
  23. package/dist/diagnostics/buffer.js.map +1 -0
  24. package/dist/diagnostics/collector.d.ts +284 -0
  25. package/dist/diagnostics/collector.d.ts.map +1 -0
  26. package/dist/diagnostics/collector.js +810 -0
  27. package/dist/diagnostics/collector.js.map +1 -0
  28. package/dist/diagnostics/projection.d.ts +153 -0
  29. package/dist/diagnostics/projection.d.ts.map +1 -0
  30. package/dist/diagnostics/projection.js +226 -0
  31. package/dist/diagnostics/projection.js.map +1 -0
  32. package/dist/index.d.ts +12 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +11 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/lifecycle/lifecycle-manager.d.ts +136 -0
  37. package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
  38. package/dist/lifecycle/lifecycle-manager.js +206 -0
  39. package/dist/lifecycle/lifecycle-manager.js.map +1 -0
  40. package/dist/pipeline/execute-chain.d.ts +71 -0
  41. package/dist/pipeline/execute-chain.d.ts.map +1 -0
  42. package/dist/pipeline/execute-chain.js +104 -0
  43. package/dist/pipeline/execute-chain.js.map +1 -0
  44. package/dist/pipeline/middleware-pipeline.d.ts +68 -0
  45. package/dist/pipeline/middleware-pipeline.d.ts.map +1 -0
  46. package/dist/pipeline/middleware-pipeline.js +82 -0
  47. package/dist/pipeline/middleware-pipeline.js.map +1 -0
  48. package/dist/registry/plugin-resolver.d.ts +42 -0
  49. package/dist/registry/plugin-resolver.d.ts.map +1 -0
  50. package/dist/registry/plugin-resolver.js +140 -0
  51. package/dist/registry/plugin-resolver.js.map +1 -0
  52. package/dist/registry/service-registry.d.ts +110 -0
  53. package/dist/registry/service-registry.d.ts.map +1 -0
  54. package/dist/registry/service-registry.js +197 -0
  55. package/dist/registry/service-registry.js.map +1 -0
  56. package/dist/router/route-matcher.d.ts +85 -0
  57. package/dist/router/route-matcher.d.ts.map +1 -0
  58. package/dist/router/route-matcher.js +105 -0
  59. package/dist/router/route-matcher.js.map +1 -0
  60. package/dist/router/router.d.ts +130 -0
  61. package/dist/router/router.d.ts.map +1 -0
  62. package/dist/router/router.js +350 -0
  63. package/dist/router/router.js.map +1 -0
  64. package/package.json +49 -3
  65. package/src/application/application.ts +2326 -0
  66. package/src/application/inject-body.ts +130 -0
  67. package/src/context/request-context.ts +169 -0
  68. package/src/context/response.ts +246 -0
  69. package/src/diagnostics/buffer.ts +243 -0
  70. package/src/diagnostics/collector.ts +1030 -0
  71. package/src/diagnostics/projection.ts +322 -0
  72. package/src/index.ts +20 -0
  73. package/src/lifecycle/lifecycle-manager.ts +282 -0
  74. package/src/pipeline/execute-chain.ts +162 -0
  75. package/src/pipeline/middleware-pipeline.ts +129 -0
  76. package/src/registry/plugin-resolver.ts +185 -0
  77. package/src/registry/service-registry.ts +262 -0
  78. package/src/router/route-matcher.ts +134 -0
  79. package/src/router/router.ts +485 -0
@@ -0,0 +1,206 @@
1
+ import { monotonicElapsed } from "../diagnostics/projection.js";
2
+ /**
3
+ * Default implementation of {@linkcode ILifecycleApi}. Stores hook arrays
4
+ * and exposes execution methods for each lifecycle phase.
5
+ */
6
+ export class LifecycleManager {
7
+ #register = [];
8
+ /** Count of onRegister hooks already drained by {@linkcode runRegister}. */
9
+ #registerCursor = 0;
10
+ #init = [];
11
+ #bootstrap = [];
12
+ #request = [];
13
+ #response = [];
14
+ #error = [];
15
+ #stopping = [];
16
+ #shutdown = [];
17
+ #close = [];
18
+ #observer;
19
+ /**
20
+ * Installs the diagnostics observer for hook invocations. Pass `undefined`
21
+ * to detach (teardown).
22
+ *
23
+ * @param observer - The observer, or `undefined`
24
+ * @since 0.8.0
25
+ */
26
+ setLifecycleObserver(observer) {
27
+ this.#observer = observer;
28
+ }
29
+ onRegister(fn) {
30
+ this.#register.push(fn);
31
+ }
32
+ onInit(fn) {
33
+ this.#init.push(fn);
34
+ }
35
+ onBootstrap(fn) {
36
+ this.#bootstrap.push(fn);
37
+ }
38
+ onRequest(fn) {
39
+ this.#request.push(fn);
40
+ }
41
+ onResponse(fn) {
42
+ this.#response.push(fn);
43
+ }
44
+ onError(fn) {
45
+ this.#error.push(fn);
46
+ }
47
+ onStopping(fn) {
48
+ this.#stopping.push(fn);
49
+ }
50
+ onShutdown(fn) {
51
+ this.#shutdown.push(fn);
52
+ }
53
+ onClose(fn) {
54
+ this.#close.push(fn);
55
+ }
56
+ /**
57
+ * Runs onRegister hooks added since the previous call, in registration
58
+ * order. The kernel invokes this immediately after each plugin's
59
+ * `register()` returns, so a plugin's onRegister hooks run "during the
60
+ * owning plugin's registration" (per {@linkcode ILifecycleApi.onRegister})
61
+ * — after that plugin and before the next one. A cursor tracks how many
62
+ * hooks have already run so each plugin only fires the hooks it added; a
63
+ * hook that registers a further onRegister hook drains it in the same pass.
64
+ */
65
+ async runRegister() {
66
+ while (this.#registerCursor < this.#register.length) {
67
+ const fn = this.#register[this.#registerCursor];
68
+ this.#registerCursor++;
69
+ await this.#runObserved('register-hook', this.#registerCursor, fn);
70
+ }
71
+ }
72
+ /** Runs all onInit hooks in registration order. */
73
+ async runInit() {
74
+ let ordinal = 0;
75
+ for (const fn of this.#init) {
76
+ ordinal++;
77
+ await this.#runObserved('init', ordinal, fn);
78
+ }
79
+ }
80
+ /** Runs all onBootstrap hooks in registration order. */
81
+ async runBootstrap() {
82
+ let ordinal = 0;
83
+ for (const fn of this.#bootstrap) {
84
+ ordinal++;
85
+ await this.#runObserved('bootstrap', ordinal, fn);
86
+ }
87
+ }
88
+ /**
89
+ * Whether any onStopping hook is registered.
90
+ *
91
+ * `Application` checks this rather than awaiting unconditionally: awaiting
92
+ * an already-resolved promise still defers the rest of `stop()` by a
93
+ * microtask, which would move when `#stopping` flips and change the answer a
94
+ * request arriving in that same tick gets. Branching keeps the new phase
95
+ * genuinely zero-width for an application that registers no hook.
96
+ */
97
+ hasStopping() {
98
+ return this.#stopping.length > 0;
99
+ }
100
+ /**
101
+ * Runs stopping hooks in reverse registration order (LIFO), before the
102
+ * application starts refusing requests.
103
+ */
104
+ async runStopping() {
105
+ for (let i = this.#stopping.length - 1; i >= 0; i--) {
106
+ await this.#runObserved('stopping', this.#stopping.length - i, this.#stopping[i]);
107
+ }
108
+ }
109
+ /** Runs shutdown hooks in reverse registration order (LIFO cleanup). */
110
+ async runShutdown() {
111
+ for (let i = this.#shutdown.length - 1; i >= 0; i--) {
112
+ await this.#runObserved('shutdown', this.#shutdown.length - i, this.#shutdown[i]);
113
+ }
114
+ }
115
+ /**
116
+ * Runs close hooks in registration order (after shutdown completes).
117
+ *
118
+ * EVERY hook runs, even when an earlier one rejects: a close hook releases
119
+ * one plugin's resources, and letting the first failure abort the loop meant
120
+ * one plugin that could not disconnect kept every later plugin from
121
+ * releasing anything — the M50 `onStopping` defect in a second place. The
122
+ * caller still learns about the failures, from an `AggregateError` raised
123
+ * once the whole list has run; a single failure is rethrown as itself, so an
124
+ * existing `instanceof` check on the hook's own error still matches.
125
+ *
126
+ * @throws {AggregateError} When more than one hook rejected
127
+ */
128
+ async runClose() {
129
+ const errors = [];
130
+ let ordinal = 0;
131
+ for (const fn of this.#close) {
132
+ ordinal++;
133
+ try {
134
+ await this.#runObserved('close', ordinal, fn);
135
+ }
136
+ catch (error) {
137
+ errors.push(error);
138
+ }
139
+ }
140
+ if (errors.length === 1) {
141
+ throw errors[0];
142
+ }
143
+ if (errors.length > 1) {
144
+ throw new AggregateError(errors, 'One or more close hooks failed.');
145
+ }
146
+ }
147
+ /**
148
+ * Returns the request hooks list for the application to invoke during
149
+ * request processing.
150
+ */
151
+ getRequestHooks() {
152
+ return this.#request;
153
+ }
154
+ /**
155
+ * Returns the response hooks list for the application to invoke after
156
+ * the response is produced.
157
+ */
158
+ getResponseHooks() {
159
+ return this.#response;
160
+ }
161
+ /**
162
+ * Returns the error hooks list for the application to invoke when an
163
+ * error escapes middleware or a handler.
164
+ */
165
+ getErrorHooks() {
166
+ return this.#error;
167
+ }
168
+ /**
169
+ * Runs one hook, observing its completion when an observer is installed.
170
+ * With no observer this is a bare `await fn()` — zero added work. A
171
+ * throwing hook is recorded as `failed` (never with the error itself) and
172
+ * re-thrown with its identity intact, so ordering, aggregation, and
173
+ * propagation semantics are unchanged.
174
+ */
175
+ async #runObserved(phase, ordinal, fn) {
176
+ const observer = this.#observer;
177
+ if (observer === undefined) {
178
+ await fn();
179
+ return;
180
+ }
181
+ const startedAtMs = observer.clock();
182
+ let failed = false;
183
+ try {
184
+ await fn();
185
+ }
186
+ catch (error) {
187
+ failed = true;
188
+ observer.onHook({
189
+ phase,
190
+ ordinal,
191
+ failed,
192
+ startedAtMs,
193
+ durationMs: monotonicElapsed(observer.clock, startedAtMs),
194
+ });
195
+ throw error;
196
+ }
197
+ observer.onHook({
198
+ phase,
199
+ ordinal,
200
+ failed,
201
+ startedAtMs,
202
+ durationMs: monotonicElapsed(observer.clock, startedAtMs),
203
+ });
204
+ }
205
+ }
206
+ //# sourceMappingURL=lifecycle-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-manager.js","sourceRoot":"","sources":["../../src/lifecycle/lifecycle-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared middleware-chain executor — classic next()-chaining with a
3
+ * double-`next` guard and defense-in-depth against a stage that responds
4
+ * AND calls `next()`.
5
+ *
6
+ * Both the global pipeline ({@linkcode MiddlewarePipeline}) and the
7
+ * per-route middleware chain (dispatched from the application) delegate to
8
+ * this helper so the chaining semantics are defined in exactly one place
9
+ * (AI_GUIDELINES §11.1 — DRY).
10
+ *
11
+ * @module
12
+ */
13
+ import type { DiagnosticsEventOutcome, IRequestContext, MiddlewareFunction } from '@setu-ts/common';
14
+ /**
15
+ * One executed stage's completion record, reported in actual completion order.
16
+ * Skipped stages (the defense-in-depth early returns) emit no record.
17
+ *
18
+ * @since 0.8.0
19
+ */
20
+ export interface ChainStageObservation {
21
+ /** 0-based index of the stage in the chain it was registered in. */
22
+ readonly position: number;
23
+ /** How the stage completed. */
24
+ readonly outcome: Extract<DiagnosticsEventOutcome, 'ok' | 'error' | 'short-circuit' | 'downstream-skipped'>;
25
+ /** Monotonic start offset, or `null` when no runtime clock is available. */
26
+ readonly startedAtMs: number | null;
27
+ /** Inclusive monotonic elapsed ms (including downstream `next()` work). */
28
+ readonly durationMs: number | null;
29
+ }
30
+ /**
31
+ * The diagnostics observer the application passes to instrument a chain. The
32
+ * clock is injected because observation must not read ambient time.
33
+ *
34
+ * @since 0.8.0
35
+ */
36
+ export interface ChainObserver {
37
+ /** Monotonic clock; returns `null` when no runtime clock is available. */
38
+ readonly clock: () => number | null;
39
+ /** Called once per EXECUTED stage, in completion order. */
40
+ readonly onStage: (observation: ChainStageObservation) => void;
41
+ }
42
+ /**
43
+ * Executes a chain of middleware with classic `next()`-chaining semantics.
44
+ *
45
+ * Each middleware receives a `next` function that advances to the next
46
+ * stage. A stage that returns without calling `next()` short-circuits the
47
+ * chain — subsequent stages and the terminal are not invoked. As
48
+ * defense-in-depth, before invoking the next stage (and before the
49
+ * terminal) the executor also checks `ResponseBuilder.ended` on
50
+ * `ctx.response`: if a stage has already produced a terminal response
51
+ * (e.g. called `json()`), downstream stages are skipped even if that
52
+ * stage incorrectly called `next()`.
53
+ *
54
+ * @param chain - The ordered middleware functions to execute
55
+ * @param ctx - The request context (its `response` is a {@linkcode ResponseBuilder})
56
+ * @param terminal - Called when every middleware has called `next()`
57
+ * @param names - Optional diagnostic names, positionally matching `chain`, used
58
+ * to identify the offending stage in the double-`next()` error. The global
59
+ * pipeline supplies the names registered through `MiddlewareOptions.name`;
60
+ * route-level chains have none and fall back to the function's own name.
61
+ * @param observer - Optional diagnostics observer. When absent the executor
62
+ * performs no additional work beyond the original path; when present, each
63
+ * stage's completion is observed inline without changing dispatch semantics —
64
+ * a throw is recorded and re-thrown with its identity intact, and the
65
+ * `downstream-skipped` outcome records a stage that called `next()` after the
66
+ * response had already ended.
67
+ * @throws {Error} If `next()` is called multiple times within a single middleware
68
+ * @since 0.1.0
69
+ */
70
+ export declare function executeChain(chain: readonly MiddlewareFunction[], ctx: IRequestContext, terminal: () => Promise<void>, names?: readonly string[], observer?: ChainObserver): Promise<void>;
71
+ //# sourceMappingURL=execute-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute-chain.d.ts","sourceRoot":"","sources":["../../src/pipeline/execute-chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,uBAAuB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAKpG;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CACvB,uBAAuB,EACvB,IAAI,GAAG,OAAO,GAAG,eAAe,GAAG,oBAAoB,CACxD,CAAC;IACF,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,CAAC,WAAW,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAChE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC7B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,EACzB,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,IAAI,CAAC,CA6Ef"}
@@ -0,0 +1,104 @@
1
+ import { monotonicElapsed } from "../diagnostics/projection.js";
2
+ /**
3
+ * Executes a chain of middleware with classic `next()`-chaining semantics.
4
+ *
5
+ * Each middleware receives a `next` function that advances to the next
6
+ * stage. A stage that returns without calling `next()` short-circuits the
7
+ * chain — subsequent stages and the terminal are not invoked. As
8
+ * defense-in-depth, before invoking the next stage (and before the
9
+ * terminal) the executor also checks `ResponseBuilder.ended` on
10
+ * `ctx.response`: if a stage has already produced a terminal response
11
+ * (e.g. called `json()`), downstream stages are skipped even if that
12
+ * stage incorrectly called `next()`.
13
+ *
14
+ * @param chain - The ordered middleware functions to execute
15
+ * @param ctx - The request context (its `response` is a {@linkcode ResponseBuilder})
16
+ * @param terminal - Called when every middleware has called `next()`
17
+ * @param names - Optional diagnostic names, positionally matching `chain`, used
18
+ * to identify the offending stage in the double-`next()` error. The global
19
+ * pipeline supplies the names registered through `MiddlewareOptions.name`;
20
+ * route-level chains have none and fall back to the function's own name.
21
+ * @param observer - Optional diagnostics observer. When absent the executor
22
+ * performs no additional work beyond the original path; when present, each
23
+ * stage's completion is observed inline without changing dispatch semantics —
24
+ * a throw is recorded and re-thrown with its identity intact, and the
25
+ * `downstream-skipped` outcome records a stage that called `next()` after the
26
+ * response had already ended.
27
+ * @throws {Error} If `next()` is called multiple times within a single middleware
28
+ * @since 0.1.0
29
+ */
30
+ export async function executeChain(chain, ctx, terminal, names, observer) {
31
+ let index = 0;
32
+ // Defense in depth: stop once a prior stage has ended the response. The
33
+ // check is guarded so callers passing a minimal context (e.g. the
34
+ // pipeline unit tests with an empty object) don't crash on a missing
35
+ // `response` — only a real ResponseBuilder exposes `ended`.
36
+ const responseEnded = () => {
37
+ const response = ctx.response;
38
+ return typeof response?.ended === 'boolean' ? response.ended : false;
39
+ };
40
+ const elapsed = (startedAtMs) => observer === undefined ? null : monotonicElapsed(observer.clock, startedAtMs);
41
+ const run = async () => {
42
+ if (index >= chain.length) {
43
+ // Defense in depth: do not run the terminal if a prior stage
44
+ // already ended the response.
45
+ if (responseEnded()) {
46
+ return;
47
+ }
48
+ await terminal();
49
+ return;
50
+ }
51
+ // Defense in depth: a prior stage ended the response — stop.
52
+ if (responseEnded()) {
53
+ return;
54
+ }
55
+ const fn = chain[index];
56
+ const position = index;
57
+ index++;
58
+ let nextCalled = false;
59
+ let downstreamSkipped = false;
60
+ const next = () => {
61
+ if (nextCalled) {
62
+ // Prefer the name registered via `MiddlewareOptions.name`, then the
63
+ // function's own name. `fn.name` is ALWAYS a string — `''` for an
64
+ // anonymous function — so `??` never fell back and the message read
65
+ // "…in middleware " with a blank name. `||` is the correct operator.
66
+ const label = names?.[position] || fn.name || '<anonymous>';
67
+ throw new Error(`next() called multiple times in middleware ${label}`);
68
+ }
69
+ nextCalled = true;
70
+ if (observer !== undefined && responseEnded()) {
71
+ // The stage called next() after ending the response; run()'s own
72
+ // ended check below will skip the downstream. Recorded for the
73
+ // caller's completion record — the outcome, not a new behavior.
74
+ downstreamSkipped = true;
75
+ }
76
+ return run();
77
+ };
78
+ const startedAtMs = observer === undefined ? null : observer.clock();
79
+ try {
80
+ await fn(ctx, next);
81
+ }
82
+ catch (error) {
83
+ if (observer !== undefined) {
84
+ observer.onStage({
85
+ position,
86
+ outcome: 'error',
87
+ startedAtMs,
88
+ durationMs: elapsed(startedAtMs),
89
+ });
90
+ }
91
+ throw error;
92
+ }
93
+ if (observer !== undefined) {
94
+ observer.onStage({
95
+ position,
96
+ outcome: !nextCalled ? 'short-circuit' : downstreamSkipped ? 'downstream-skipped' : 'ok',
97
+ startedAtMs,
98
+ durationMs: elapsed(startedAtMs),
99
+ });
100
+ }
101
+ };
102
+ await run();
103
+ }
104
+ //# sourceMappingURL=execute-chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute-chain.js","sourceRoot":"","sources":["../../src/pipeline/execute-chain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Middleware pipeline — ASP.NET Core-style ordered execution with priority
3
+ * bands, short-circuiting, and double-next protection.
4
+ *
5
+ * @module
6
+ */
7
+ import type { IMiddlewareApi, IRequestContext, MiddlewareFunction, MiddlewareOptions } from '@setu-ts/common';
8
+ import type { ChainObserver } from './execute-chain.ts';
9
+ /**
10
+ * One compiled global middleware stage, projected for diagnostics: the
11
+ * declared name, its priority, and its execution position in the stable
12
+ * priority sort.
13
+ *
14
+ * @since 0.8.0
15
+ */
16
+ export interface MiddlewareStageDescriptor {
17
+ /** The name passed through `MiddlewareOptions`, or the generated placeholder. */
18
+ readonly name: string;
19
+ /** The stage's execution priority. */
20
+ readonly priority: number;
21
+ /** 1-based execution position in the compiled chain. */
22
+ readonly position: number;
23
+ }
24
+ /**
25
+ * Middleware pipeline: collect middleware with priorities, compile into a
26
+ * sorted chain, then execute with classic next()-chaining semantics.
27
+ */
28
+ export declare class MiddlewarePipeline implements IMiddlewareApi {
29
+ #private;
30
+ add(middleware: MiddlewareFunction, options?: MiddlewareOptions): void;
31
+ /**
32
+ * Sorts middleware stably by (priority, insertion order) and freezes the
33
+ * chain. After calling, no further middleware may be added.
34
+ */
35
+ compile(): readonly MiddlewareFunction[];
36
+ /**
37
+ * Diagnostic names of the compiled chain, in execution order.
38
+ *
39
+ * Each entry is the `name` passed through {@linkcode MiddlewareOptions}, or a
40
+ * generated `<anonymous-N>` placeholder. Returns an empty array before
41
+ * {@linkcode MiddlewarePipeline.compile} runs.
42
+ *
43
+ * @returns The compiled stage names, positionally matching the compiled chain
44
+ * @internal Diagnostics seam — the kernel passes these to `executeChain` so a
45
+ * double-`next()` error can name the offending stage.
46
+ */
47
+ compiledNames(): readonly string[];
48
+ /**
49
+ * The compiled stages projected as bounded descriptors, in execution order.
50
+ *
51
+ * @returns Empty before {@linkcode MiddlewarePipeline.compile} runs
52
+ * @internal Diagnostics seam — the collector captures these once, at
53
+ * compile, so middleware nodes and stage records carry the same identity.
54
+ * @since 0.8.0
55
+ */
56
+ compiledDescriptors(): readonly MiddlewareStageDescriptor[];
57
+ /**
58
+ * Executes the compiled pipeline using classic next()-chaining.
59
+ *
60
+ * @param ctx - The request context
61
+ * @param terminal - Called when all middleware have completed
62
+ * @param observer - Optional diagnostics observer, forwarded to the shared
63
+ * chain executor; absent performs no additional work
64
+ * @throws {Error} If next() is called multiple times in a single middleware
65
+ */
66
+ execute(ctx: IRequestContext, terminal: () => Promise<void>, observer?: ChainObserver): Promise<void>;
67
+ }
68
+ //# sourceMappingURL=middleware-pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware-pipeline.d.ts","sourceRoot":"","sources":["../../src/pipeline/middleware-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAWxD;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,cAAc;;IAQvD,GAAG,CAAC,UAAU,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAYtE;;;OAGG;IACH,OAAO,IAAI,SAAS,kBAAkB,EAAE;IAexC;;;;;;;;;;OAUG;IACH,aAAa,IAAI,SAAS,MAAM,EAAE;IAIlC;;;;;;;OAOG;IACH,mBAAmB,IAAI,SAAS,yBAAyB,EAAE;IAI3D;;;;;;;;OAQG;IACG,OAAO,CACX,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAC7B,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,IAAI,CAAC;CAIjB"}
@@ -0,0 +1,82 @@
1
+ import { executeChain } from "./execute-chain.js";
2
+ const DEFAULT_PRIORITY = 500;
3
+ /**
4
+ * Middleware pipeline: collect middleware with priorities, compile into a
5
+ * sorted chain, then execute with classic next()-chaining semantics.
6
+ */
7
+ export class MiddlewarePipeline {
8
+ #entries = [];
9
+ #compiled = null;
10
+ /** Diagnostic names, positionally matching {@linkcode MiddlewarePipeline.compile}'s output. */
11
+ #compiledNames = null;
12
+ /** Compiled stage descriptors, positionally matching the chain (diagnostics seam). */
13
+ #compiledDescriptors = null;
14
+ add(middleware, options) {
15
+ if (this.#compiled !== null) {
16
+ throw new Error('Cannot add middleware after the pipeline has been compiled.');
17
+ }
18
+ this.#entries.push({
19
+ fn: middleware,
20
+ priority: options?.priority ?? DEFAULT_PRIORITY,
21
+ name: options?.name ?? `<anonymous-${this.#entries.length}>`,
22
+ index: this.#entries.length,
23
+ });
24
+ }
25
+ /**
26
+ * Sorts middleware stably by (priority, insertion order) and freezes the
27
+ * chain. After calling, no further middleware may be added.
28
+ */
29
+ compile() {
30
+ if (this.#compiled !== null) {
31
+ return this.#compiled;
32
+ }
33
+ const sorted = [...this.#entries].sort((a, b) => a.priority - b.priority || a.index - b.index);
34
+ this.#compiled = sorted.map((entry) => entry.fn);
35
+ this.#compiledNames = sorted.map((entry) => entry.name);
36
+ this.#compiledDescriptors = sorted.map((entry, position) => ({
37
+ name: entry.name,
38
+ priority: entry.priority,
39
+ position: position + 1,
40
+ }));
41
+ return this.#compiled;
42
+ }
43
+ /**
44
+ * Diagnostic names of the compiled chain, in execution order.
45
+ *
46
+ * Each entry is the `name` passed through {@linkcode MiddlewareOptions}, or a
47
+ * generated `<anonymous-N>` placeholder. Returns an empty array before
48
+ * {@linkcode MiddlewarePipeline.compile} runs.
49
+ *
50
+ * @returns The compiled stage names, positionally matching the compiled chain
51
+ * @internal Diagnostics seam — the kernel passes these to `executeChain` so a
52
+ * double-`next()` error can name the offending stage.
53
+ */
54
+ compiledNames() {
55
+ return this.#compiledNames ?? [];
56
+ }
57
+ /**
58
+ * The compiled stages projected as bounded descriptors, in execution order.
59
+ *
60
+ * @returns Empty before {@linkcode MiddlewarePipeline.compile} runs
61
+ * @internal Diagnostics seam — the collector captures these once, at
62
+ * compile, so middleware nodes and stage records carry the same identity.
63
+ * @since 0.8.0
64
+ */
65
+ compiledDescriptors() {
66
+ return this.#compiledDescriptors ?? [];
67
+ }
68
+ /**
69
+ * Executes the compiled pipeline using classic next()-chaining.
70
+ *
71
+ * @param ctx - The request context
72
+ * @param terminal - Called when all middleware have completed
73
+ * @param observer - Optional diagnostics observer, forwarded to the shared
74
+ * chain executor; absent performs no additional work
75
+ * @throws {Error} If next() is called multiple times in a single middleware
76
+ */
77
+ async execute(ctx, terminal, observer) {
78
+ const chain = this.#compiled ?? this.compile();
79
+ await executeChain(chain, ctx, terminal, this.compiledNames(), observer);
80
+ }
81
+ }
82
+ //# sourceMappingURL=middleware-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware-pipeline.js","sourceRoot":"","sources":["../../src/pipeline/middleware-pipeline.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUlD,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAkB7B;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IACpB,QAAQ,GAAsB,EAAE,CAAC;IAC1C,SAAS,GAAgC,IAAI,CAAC;IAC9C,+FAA+F;IAC/F,cAAc,GAAoB,IAAI,CAAC;IACvC,sFAAsF;IACtF,oBAAoB,GAAuC,IAAI,CAAC;IAEhE,GAAG,CAAC,UAA8B,EAAE,OAA2B;QAC7D,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACjB,EAAE,EAAE,UAAU;YACd,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,gBAAgB;YAC/C,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,cAAc,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG;YAC5D,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;SAC5B,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC/F,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,CAAC,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC3D,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,QAAQ,GAAG,CAAC;SACvB,CAAC,CAAC,CAAC;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED;;;;;;;;;;OAUG;IACH,aAAa;QACX,OAAO,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC;IACnC,CAAC;IAED;;;;;;;OAOG;IACH,mBAAmB;QACjB,OAAO,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC;IACzC,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,CACX,GAAoB,EACpB,QAA6B,EAC7B,QAAwB;QAExB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/C,MAAM,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC3E,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ import type { CapabilityToken, IPlugin } from '@setu-ts/common';
2
+ /**
3
+ * Resolves plugin registration order.
4
+ *
5
+ * Order is determined by: dependency edges first (a plugin's dependencies
6
+ * register before it), then declared priority (lower first), then original
7
+ * registration order. The plugin providing the `runtime` capability always
8
+ * registers first, regardless of priority.
9
+ */
10
+ export declare function resolvePluginOrder(plugins: readonly IPlugin[]): readonly IPlugin[];
11
+ /**
12
+ * One plugin's declared `consumes` capability for which no provider exists.
13
+ * Used to build a soft startup diagnostic in the application.
14
+ *
15
+ * Unlike a missing {@linkcode IPlugin.dependencies} entry (which throws in
16
+ * {@linkcode resolvePluginOrder}), an unsatisfied `consumes` is not a
17
+ * resolve-time error: the plugin resolves the capability lazily at request
18
+ * time, so the failure would otherwise surface only later, as a runtime throw
19
+ * from `services.get`. Reporting it at startup names the consumer up front.
20
+ */
21
+ export interface UnsatisfiedConsumer {
22
+ /** Name of the plugin that declares the consumed capability. */
23
+ readonly plugin: string;
24
+ /** The consumed capability token that no registered plugin provides. */
25
+ readonly capability: CapabilityToken;
26
+ }
27
+ /**
28
+ * Finds every `consumes` capability, across all plugins, that `isProvided`
29
+ * reports as absent.
30
+ *
31
+ * Pure by design: the caller supplies the provider check — in the kernel, the
32
+ * live service registry's `has` after all plugins have registered, so
33
+ * capabilities registered imperatively (not declared in `provides`) still
34
+ * count as satisfied and never produce a false warning.
35
+ *
36
+ * @param plugins - All registered plugins
37
+ * @param isProvided - Predicate: does a provider exist for this capability?
38
+ * @returns One entry per unsatisfied (plugin, capability) pair, in plugin then
39
+ * declaration order
40
+ */
41
+ export declare function findUnsatisfiedConsumers(plugins: readonly IPlugin[], isProvided: (token: CapabilityToken) => boolean): readonly UnsatisfiedConsumer[];
42
+ //# sourceMappingURL=plugin-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-resolver.d.ts","sourceRoot":"","sources":["../../src/registry/plugin-resolver.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,OAAO,EAAE,CAwClF;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,UAAU,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,GAC9C,SAAS,mBAAmB,EAAE,CAUhC"}