@setu-ts/kernel 0.0.0-stage → 0.9.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -2
  3. package/dist/application/application.d.ts +172 -0
  4. package/dist/application/application.d.ts.map +1 -0
  5. package/dist/application/application.js +1862 -0
  6. package/dist/application/application.js.map +1 -0
  7. package/dist/application/inject-body.d.ts +58 -0
  8. package/dist/application/inject-body.d.ts.map +1 -0
  9. package/dist/application/inject-body.js +82 -0
  10. package/dist/application/inject-body.js.map +1 -0
  11. package/dist/context/request-context.d.ts +32 -0
  12. package/dist/context/request-context.d.ts.map +1 -0
  13. package/dist/context/request-context.js +136 -0
  14. package/dist/context/request-context.js.map +1 -0
  15. package/dist/context/response.d.ts +60 -0
  16. package/dist/context/response.d.ts.map +1 -0
  17. package/dist/context/response.js +193 -0
  18. package/dist/context/response.js.map +1 -0
  19. package/dist/diagnostics/buffer.d.ts +143 -0
  20. package/dist/diagnostics/buffer.d.ts.map +1 -0
  21. package/dist/diagnostics/buffer.js +185 -0
  22. package/dist/diagnostics/buffer.js.map +1 -0
  23. package/dist/diagnostics/collector.d.ts +284 -0
  24. package/dist/diagnostics/collector.d.ts.map +1 -0
  25. package/dist/diagnostics/collector.js +812 -0
  26. package/dist/diagnostics/collector.js.map +1 -0
  27. package/dist/diagnostics/projection.d.ts +153 -0
  28. package/dist/diagnostics/projection.d.ts.map +1 -0
  29. package/dist/diagnostics/projection.js +226 -0
  30. package/dist/diagnostics/projection.js.map +1 -0
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +11 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lifecycle/lifecycle-manager.d.ts +136 -0
  36. package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
  37. package/dist/lifecycle/lifecycle-manager.js +206 -0
  38. package/dist/lifecycle/lifecycle-manager.js.map +1 -0
  39. package/dist/pipeline/execute-chain.d.ts +71 -0
  40. package/dist/pipeline/execute-chain.d.ts.map +1 -0
  41. package/dist/pipeline/execute-chain.js +104 -0
  42. package/dist/pipeline/execute-chain.js.map +1 -0
  43. package/dist/pipeline/middleware-pipeline.d.ts +68 -0
  44. package/dist/pipeline/middleware-pipeline.d.ts.map +1 -0
  45. package/dist/pipeline/middleware-pipeline.js +82 -0
  46. package/dist/pipeline/middleware-pipeline.js.map +1 -0
  47. package/dist/registry/plugin-resolver.d.ts +42 -0
  48. package/dist/registry/plugin-resolver.d.ts.map +1 -0
  49. package/dist/registry/plugin-resolver.js +140 -0
  50. package/dist/registry/plugin-resolver.js.map +1 -0
  51. package/dist/registry/service-registry.d.ts +110 -0
  52. package/dist/registry/service-registry.d.ts.map +1 -0
  53. package/dist/registry/service-registry.js +197 -0
  54. package/dist/registry/service-registry.js.map +1 -0
  55. package/dist/router/route-matcher.d.ts +85 -0
  56. package/dist/router/route-matcher.d.ts.map +1 -0
  57. package/dist/router/route-matcher.js +102 -0
  58. package/dist/router/route-matcher.js.map +1 -0
  59. package/dist/router/router.d.ts +130 -0
  60. package/dist/router/router.d.ts.map +1 -0
  61. package/dist/router/router.js +350 -0
  62. package/dist/router/router.js.map +1 -0
  63. package/package.json +48 -3
  64. package/src/application/application.ts +2299 -0
  65. package/src/application/inject-body.ts +131 -0
  66. package/src/context/request-context.ts +169 -0
  67. package/src/context/response.ts +254 -0
  68. package/src/diagnostics/buffer.ts +241 -0
  69. package/src/diagnostics/collector.ts +1031 -0
  70. package/src/diagnostics/projection.ts +322 -0
  71. package/src/index.ts +20 -0
  72. package/src/lifecycle/lifecycle-manager.ts +287 -0
  73. package/src/pipeline/execute-chain.ts +164 -0
  74. package/src/pipeline/middleware-pipeline.ts +131 -0
  75. package/src/registry/plugin-resolver.ts +185 -0
  76. package/src/registry/service-registry.ts +267 -0
  77. package/src/router/route-matcher.ts +131 -0
  78. package/src/router/router.ts +485 -0
@@ -0,0 +1,193 @@
1
+ /** Opaque brand — only the kernel constructs values of this type. */
2
+ const HANDLER_RESULT = { __handlerResult: true };
3
+ /** Internal immutable header sources for the common terminal response shapes. */
4
+ const JSON_INIT = responseInit({ 'content-type': 'application/json; charset=utf-8' });
5
+ const TEXT_INIT = responseInit({ 'content-type': 'text/plain; charset=utf-8' });
6
+ const HTML_INIT = responseInit({ 'content-type': 'text/html; charset=utf-8' });
7
+ const BINARY_INIT = responseInit({ 'content-type': 'application/octet-stream' });
8
+ /**
9
+ * Default implementation of {@linkcode IResponse}. Configuration methods
10
+ * chain; terminal methods mark the builder as ended and return the brand.
11
+ */
12
+ export class ResponseBuilder {
13
+ #status = 200;
14
+ #headers;
15
+ #responseInit;
16
+ #initHeaderName;
17
+ #body = null;
18
+ #streaming = false;
19
+ #ended = false;
20
+ status(code) {
21
+ this.#status = code;
22
+ return this;
23
+ }
24
+ header(name, value) {
25
+ this.#materializeHeaders().set(name, value);
26
+ return this;
27
+ }
28
+ appendHeader(name, value) {
29
+ this.#materializeHeaders().append(name, value);
30
+ return this;
31
+ }
32
+ json(body) {
33
+ this.#body = JSON.stringify(body);
34
+ this.#setBuiltInHeader('content-type', 'application/json; charset=utf-8', JSON_INIT);
35
+ this.#ended = true;
36
+ return HANDLER_RESULT;
37
+ }
38
+ text(body) {
39
+ this.#body = body;
40
+ this.#setBuiltInHeader('content-type', 'text/plain; charset=utf-8', TEXT_INIT);
41
+ this.#ended = true;
42
+ return HANDLER_RESULT;
43
+ }
44
+ /**
45
+ * Sends an HTML response with the `text/html; charset=utf-8` media type.
46
+ *
47
+ * @param body - The HTML document text
48
+ * @returns The handler result
49
+ */
50
+ html(body) {
51
+ this.#body = body;
52
+ this.#setBuiltInHeader('content-type', 'text/html; charset=utf-8', HTML_INIT);
53
+ this.#ended = true;
54
+ return HANDLER_RESULT;
55
+ }
56
+ send(body) {
57
+ this.#body = body ?? null;
58
+ if (body !== undefined && !this.#hasHeader('content-type')) {
59
+ this.#setBuiltInHeader('content-type', 'application/octet-stream', BINARY_INIT);
60
+ }
61
+ this.#ended = true;
62
+ return HANDLER_RESULT;
63
+ }
64
+ redirect(url, status = 302) {
65
+ this.#status = status;
66
+ this.#setBuiltInHeader('location', url, responseInit({ location: url }));
67
+ this.#body = null;
68
+ this.#ended = true;
69
+ return HANDLER_RESULT;
70
+ }
71
+ /**
72
+ * Sends a streaming response body.
73
+ *
74
+ * Accepts a web-standard {@linkcode ReadableStream} so that a handler can flush
75
+ * bytes progressively over a long-lived connection instead of buffering a
76
+ * whole body before send.
77
+ *
78
+ * @param body - A `ReadableStream` of `Uint8Array` chunks
79
+ * @returns The handler result
80
+ */
81
+ stream(body) {
82
+ this.#body = body;
83
+ this.#streaming = true;
84
+ this.#ended = true;
85
+ return HANDLER_RESULT;
86
+ }
87
+ /**
88
+ * Returns a snapshot of the current response state.
89
+ *
90
+ * @returns The discriminated snapshot (status, headers, and either a buffered
91
+ * body or a live stream, keyed on `streaming`)
92
+ */
93
+ snapshot() {
94
+ if (this.#streaming) {
95
+ return new StreamingResponseSnapshot(this, this.#status, this.#body);
96
+ }
97
+ return new BufferedResponseSnapshot(this, this.#status, this.#body);
98
+ }
99
+ /** Whether a terminal method has been called (used to detect short-circuits). */
100
+ get ended() {
101
+ return this.#ended;
102
+ }
103
+ /**
104
+ * The response status as currently set, defaulted to 200 — the value a
105
+ * completed request's diagnostics record reports. Read-only by design:
106
+ * status is set through {@linkcode ResponseBuilder.status}.
107
+ * @since 0.8.0
108
+ */
109
+ get statusCode() {
110
+ return this.#status;
111
+ }
112
+ /** Returns the native Headers object, creating it only for mutable access. */
113
+ #materializeHeaders() {
114
+ if (this.#headers === undefined) {
115
+ this.#headers = new Headers(this.#responseInit?.headers);
116
+ this.#responseInit = undefined;
117
+ this.#initHeaderName = undefined;
118
+ }
119
+ return this.#headers;
120
+ }
121
+ /** Supplies the documented live headers view to an internal snapshot. */
122
+ headersForSnapshot() {
123
+ return this.#materializeHeaders();
124
+ }
125
+ /** Supplies the current fast-path init to an internal snapshot. */
126
+ responseInitForSnapshot() {
127
+ const init = this.#responseInit;
128
+ if (init === undefined)
129
+ return undefined;
130
+ // Native HTTP servers are allowed to add derived headers (for example,
131
+ // Content-Length) to their response initializer. Keep the shared internal
132
+ // source immutable, but give each snapshot consumer a mutable copy.
133
+ return { headers: { ...init.headers } };
134
+ }
135
+ /** Writes one built-in header without materializing native Headers where possible. */
136
+ #setBuiltInHeader(name, value, init) {
137
+ if (this.#headers !== undefined) {
138
+ this.#headers.set(name, value);
139
+ return;
140
+ }
141
+ if (this.#responseInit === undefined || this.#initHeaderName === name) {
142
+ this.#responseInit = init;
143
+ this.#initHeaderName = name;
144
+ return;
145
+ }
146
+ this.#materializeHeaders().set(name, value);
147
+ }
148
+ /** Returns whether the current response already holds a named header. */
149
+ #hasHeader(name) {
150
+ return this.#headers?.has(name) ?? this.#initHeaderName === name;
151
+ }
152
+ }
153
+ /** Snapshot object for buffered responses, with prototype-backed accessors. */
154
+ class BufferedResponseSnapshot {
155
+ streaming = false;
156
+ #response;
157
+ status;
158
+ body;
159
+ constructor(response, status, body) {
160
+ this.#response = response;
161
+ this.status = status;
162
+ this.body = body;
163
+ }
164
+ get responseInit() {
165
+ return this.#response.responseInitForSnapshot();
166
+ }
167
+ get headers() {
168
+ return this.#response.headersForSnapshot();
169
+ }
170
+ }
171
+ /** Snapshot object for streaming responses, with prototype-backed accessors. */
172
+ class StreamingResponseSnapshot {
173
+ streaming = true;
174
+ #response;
175
+ status;
176
+ body;
177
+ constructor(response, status, body) {
178
+ this.#response = response;
179
+ this.status = status;
180
+ this.body = body;
181
+ }
182
+ get responseInit() {
183
+ return this.#response.responseInitForSnapshot();
184
+ }
185
+ get headers() {
186
+ return this.#response.headersForSnapshot();
187
+ }
188
+ }
189
+ /** Freezes a terminal response's internal header source before it is copied to a snapshot. */
190
+ function responseInit(headers) {
191
+ return Object.freeze({ headers: Object.freeze(headers) });
192
+ }
193
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/context/response.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Diagnostics event buffer — a bounded, non-destructive ring of frozen
3
+ * execution records with dense per-instance sequence numbers.
4
+ *
5
+ * Two bounds are enforced here, both fixed v1 constants and deliberately not
6
+ * options: the ring holds 1,024 events, and one event may encode to at most
7
+ * 1,024 UTF-8 bytes. An oversized event is dropped WHOLE — truncating one
8
+ * would mint a record whose shape the contract never defined. Eviction is
9
+ * oldest-first and is reported to readers as `lost` sequence numbers rather
10
+ * than hidden.
11
+ *
12
+ * Readers never mutate the ring: `read` is a pure window over the retained
13
+ * range, so two readers polling at different cursors never steal from each
14
+ * other and a slow consumer cannot backpressure the application.
15
+ *
16
+ * @module
17
+ */
18
+ import type { DiagnosticsEvent } from '@setu-ts/common';
19
+ /** Fixed v1 limit: events retained by the ring. */
20
+ export declare const EVENT_CAPACITY = 1024;
21
+ /** Fixed v1 limit: maximum UTF-8 byte length of one compact-encoded event. */
22
+ export declare const MAX_EVENT_BYTES = 1024;
23
+ /** Fixed v1 limit: maximum events returned by one read. */
24
+ export declare const MAX_READ_LIMIT = 128;
25
+ /**
26
+ * The pre-serialization byte bound for one event — the M98a §3.6 rule that
27
+ * strings and counts are bounded BEFORE serialization, not measured after.
28
+ * With every field bounded at capture, the 1,024-byte cap holds
29
+ * structurally, so the request path never pays for a `JSON.stringify` it
30
+ * does not need.
31
+ *
32
+ * @param fieldLengths - Character lengths of the variable string fields
33
+ * @returns `true` when the encoded event is within {@linkcode MAX_EVENT_BYTES}
34
+ * @since 0.8.0
35
+ */
36
+ export declare function eventWithinByteCap(fieldLengths: {
37
+ readonly operationId: number;
38
+ readonly parentOperationId: number;
39
+ readonly nodeId: number;
40
+ readonly traceId: number;
41
+ readonly spanId: number;
42
+ readonly statusCode: boolean;
43
+ }): boolean;
44
+ /** The capture-time bound for one string field of an event. */
45
+ export declare const MAX_EVENT_FIELD_LENGTH: number;
46
+ /**
47
+ * The exact UTF-8 byte length of the event's compact JSON encoding — used by
48
+ * tests to prove the structural bound above is sound against a real
49
+ * encoding, never on the request path.
50
+ *
51
+ * @param event - The event to measure
52
+ * @returns Encoded byte length
53
+ * @since 0.8.0
54
+ */
55
+ export declare function encodedEventByteLength(event: DiagnosticsEvent): number;
56
+ /**
57
+ * Cursor validation for {@linkcode IDiagnosticsSource.read}-shaped reads.
58
+ * Exported pure so the exact refusal behavior is unit-testable without a
59
+ * collector.
60
+ *
61
+ * @param after - The requested sequence cursor
62
+ * @param limit - The requested page size (`undefined` for the 128 default)
63
+ * @param lastSequence - The ring's most recently assigned sequence
64
+ * @returns The normalized `{ after, limit }`
65
+ * @throws {RangeError} With a fixed, VALUE-FREE message when `after` is not a
66
+ * non-negative safe integer, when `limit` is not an integer from 1 to 128, or
67
+ * when `after` is beyond the current sequence
68
+ * @since 0.8.0
69
+ */
70
+ export declare function validateReadCursor(after: number, limit: number | undefined, lastSequence: number): {
71
+ after: number;
72
+ limit: number;
73
+ };
74
+ /**
75
+ * Bounded event ring. Sequences are allocated ONLY on a successful store, so
76
+ * the numbering is dense and a reader's `lost` count means exactly one thing:
77
+ * records between its cursor and the oldest retained record that are gone —
78
+ * evicted when the ring is full, or discarded by {@linkcode clear}, which
79
+ * never reuses a sequence number.
80
+ *
81
+ * @since 0.8.0
82
+ */
83
+ export declare class DiagnosticsEventRing {
84
+ #private;
85
+ /** Most recently assigned sequence number. */
86
+ get lastSequence(): number;
87
+ /**
88
+ * Oldest retained sequence number: 1 while nothing has been evicted or
89
+ * cleared, and `lastSequence + 1` (nothing retained) right after a clear.
90
+ */
91
+ get firstSequence(): number;
92
+ /** Whether the ring is closed to further writes (application stopped). */
93
+ get closed(): boolean;
94
+ /**
95
+ * Allocates the next sequence number, or `null` at saturation — the caller
96
+ * then stops event collection rather than letting identifiers wrap.
97
+ *
98
+ * @returns The next sequence number, or `null` when saturated
99
+ */
100
+ allocateSequence(): number | null;
101
+ /**
102
+ * Stores a pre-built, pre-measured frozen event under the given sequence.
103
+ * Evicts the oldest retained event when the ring is full.
104
+ *
105
+ * @param sequence - The sequence allocated by {@linkcode allocateSequence}
106
+ * @param event - The frozen event to retain
107
+ */
108
+ store(sequence: number, event: DiagnosticsEvent): void;
109
+ /**
110
+ * Returns the event stored under a sequence number, or `undefined` when that
111
+ * sequence was evicted or never allocated.
112
+ *
113
+ * @param sequence - The sequence number to read
114
+ * @returns The frozen event, or `undefined`
115
+ */
116
+ at(sequence: number): DiagnosticsEvent | undefined;
117
+ /**
118
+ * Closes the ring to further writes.
119
+ *
120
+ * Retained events are NOT discarded here — {@linkcode clear} does that —
121
+ * but the collector's terminal shutdown path calls both, and its `read()`
122
+ * short-circuits on a closed ring, so a consumer never drains a tail after
123
+ * shutdown: it gets an empty `closed` batch. Closing and clearing are kept
124
+ * separate so the ring has one reason to change at a time.
125
+ */
126
+ close(): void;
127
+ /**
128
+ * Discards every retained event. Used only by the teardown path: a failed
129
+ * startup or a final shutdown must not leave collected metadata recoverable
130
+ * through a read.
131
+ *
132
+ * The sequence counter is deliberately NOT reset: the discarded range is
133
+ * treated exactly like an eviction. A start that failed before any plugin
134
+ * registered can be corrected and retried on the same instance, and restarting at 1 would REUSE sequence
135
+ * numbers a reader already holds — a cursor past the new last sequence would
136
+ * then be refused as "beyond the current sequence", and one inside the new
137
+ * range would silently skip the retry's first events. Keeping the numbering
138
+ * monotonic means the retry's first event is `lastSequence + 1`, and a
139
+ * reader's `lost` counts the discarded records honestly.
140
+ */
141
+ clear(): void;
142
+ }
143
+ //# sourceMappingURL=buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/diagnostics/buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAIxD,mDAAmD;AACnD,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,8EAA8E;AAC9E,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC,2DAA2D;AAC3D,eAAO,MAAM,cAAc,MAAM,CAAC;AAsBlC;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE;IACZ,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B,GACA,OAAO,CAMT;AAED,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,EAAE,MAAyB,CAAC;AAE/D;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAEtE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,YAAY,EAAE,MAAM,GACnB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAmBlC;AAED;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;;IAM/B,8CAA8C;IAC9C,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,0EAA0E;IAC1E,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;;;OAKG;IACH,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAQjC;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAQtD;;;;;;OAMG;IACH,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAOlD;;;;;;;;OAQG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;;;;;;;;;OAaG;IACH,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,185 @@
1
+ import { saturatingNext } from "./projection.js";
2
+ /** Fixed v1 limit: events retained by the ring. */
3
+ export const EVENT_CAPACITY = 1024;
4
+ /** Fixed v1 limit: maximum UTF-8 byte length of one compact-encoded event. */
5
+ export const MAX_EVENT_BYTES = 1024;
6
+ /** Fixed v1 limit: maximum events returned by one read. */
7
+ export const MAX_READ_LIMIT = 128;
8
+ const encoder = new TextEncoder();
9
+ /**
10
+ * Bounded length of every string field an event may carry. Ids, stages, and
11
+ * outcomes are collector-owned fixed-vocabulary or bounded-identifier strings;
12
+ * the collector refuses any field over this bound at capture, dropping the
13
+ * event WHOLE.
14
+ */
15
+ const MAX_FIELD_LENGTH = 64;
16
+ /**
17
+ * Compact-JSON skeleton overhead of an event with empty variable fields:
18
+ * braces, keys, commas, the fixed kind/stage/outcome vocabulary, and maximal
19
+ * numeric fields. Every string field is collector-owned ASCII (ids, fixed
20
+ * vocabulary, validated hex identifiers), so the UTF-8 length equals the
21
+ * character count and the skeleton constant is an upper bound, not an
22
+ * estimate.
23
+ */
24
+ const EVENT_SKELETON_BYTES = 220;
25
+ /**
26
+ * The pre-serialization byte bound for one event — the M98a §3.6 rule that
27
+ * strings and counts are bounded BEFORE serialization, not measured after.
28
+ * With every field bounded at capture, the 1,024-byte cap holds
29
+ * structurally, so the request path never pays for a `JSON.stringify` it
30
+ * does not need.
31
+ *
32
+ * @param fieldLengths - Character lengths of the variable string fields
33
+ * @returns `true` when the encoded event is within {@linkcode MAX_EVENT_BYTES}
34
+ * @since 0.8.0
35
+ */
36
+ export function eventWithinByteCap(fieldLengths) {
37
+ const bounded = EVENT_SKELETON_BYTES +
38
+ fieldLengths.operationId + fieldLengths.parentOperationId + fieldLengths.nodeId +
39
+ fieldLengths.traceId + fieldLengths.spanId +
40
+ (fieldLengths.statusCode ? 21 : 0);
41
+ return bounded <= MAX_EVENT_BYTES;
42
+ }
43
+ /** The capture-time bound for one string field of an event. */
44
+ export const MAX_EVENT_FIELD_LENGTH = MAX_FIELD_LENGTH;
45
+ /**
46
+ * The exact UTF-8 byte length of the event's compact JSON encoding — used by
47
+ * tests to prove the structural bound above is sound against a real
48
+ * encoding, never on the request path.
49
+ *
50
+ * @param event - The event to measure
51
+ * @returns Encoded byte length
52
+ * @since 0.8.0
53
+ */
54
+ export function encodedEventByteLength(event) {
55
+ return encoder.encode(JSON.stringify(event)).length;
56
+ }
57
+ /**
58
+ * Cursor validation for {@linkcode IDiagnosticsSource.read}-shaped reads.
59
+ * Exported pure so the exact refusal behavior is unit-testable without a
60
+ * collector.
61
+ *
62
+ * @param after - The requested sequence cursor
63
+ * @param limit - The requested page size (`undefined` for the 128 default)
64
+ * @param lastSequence - The ring's most recently assigned sequence
65
+ * @returns The normalized `{ after, limit }`
66
+ * @throws {RangeError} With a fixed, VALUE-FREE message when `after` is not a
67
+ * non-negative safe integer, when `limit` is not an integer from 1 to 128, or
68
+ * when `after` is beyond the current sequence
69
+ * @since 0.8.0
70
+ */
71
+ export function validateReadCursor(after, limit, lastSequence) {
72
+ if (typeof after !== 'number' || !Number.isSafeInteger(after) || after < 0) {
73
+ throw new RangeError('Invalid diagnostics cursor: expected a non-negative safe integer.');
74
+ }
75
+ const effectiveLimit = limit ?? MAX_READ_LIMIT;
76
+ if (typeof effectiveLimit !== 'number' || !Number.isSafeInteger(effectiveLimit) ||
77
+ effectiveLimit < 1 || effectiveLimit > MAX_READ_LIMIT) {
78
+ throw new RangeError(`Invalid diagnostics read limit: expected an integer from 1 to ${MAX_READ_LIMIT}.`);
79
+ }
80
+ if (after > lastSequence) {
81
+ throw new RangeError('Invalid diagnostics cursor: beyond the current sequence.');
82
+ }
83
+ return { after, limit: effectiveLimit };
84
+ }
85
+ /**
86
+ * Bounded event ring. Sequences are allocated ONLY on a successful store, so
87
+ * the numbering is dense and a reader's `lost` count means exactly one thing:
88
+ * records between its cursor and the oldest retained record that are gone —
89
+ * evicted when the ring is full, or discarded by {@linkcode clear}, which
90
+ * never reuses a sequence number.
91
+ *
92
+ * @since 0.8.0
93
+ */
94
+ export class DiagnosticsEventRing {
95
+ #firstSequence = 1;
96
+ #lastSequence = 0;
97
+ #slots = new Array(EVENT_CAPACITY);
98
+ #closed = false;
99
+ /** Most recently assigned sequence number. */
100
+ get lastSequence() {
101
+ return this.#lastSequence;
102
+ }
103
+ /**
104
+ * Oldest retained sequence number: 1 while nothing has been evicted or
105
+ * cleared, and `lastSequence + 1` (nothing retained) right after a clear.
106
+ */
107
+ get firstSequence() {
108
+ return this.#firstSequence;
109
+ }
110
+ /** Whether the ring is closed to further writes (application stopped). */
111
+ get closed() {
112
+ return this.#closed;
113
+ }
114
+ /**
115
+ * Allocates the next sequence number, or `null` at saturation — the caller
116
+ * then stops event collection rather than letting identifiers wrap.
117
+ *
118
+ * @returns The next sequence number, or `null` when saturated
119
+ */
120
+ allocateSequence() {
121
+ const next = saturatingNext(this.#lastSequence);
122
+ if (next === null) {
123
+ return null;
124
+ }
125
+ return next;
126
+ }
127
+ /**
128
+ * Stores a pre-built, pre-measured frozen event under the given sequence.
129
+ * Evicts the oldest retained event when the ring is full.
130
+ *
131
+ * @param sequence - The sequence allocated by {@linkcode allocateSequence}
132
+ * @param event - The frozen event to retain
133
+ */
134
+ store(sequence, event) {
135
+ this.#slots[(sequence - 1) % EVENT_CAPACITY] = event;
136
+ this.#lastSequence = sequence;
137
+ if (sequence - this.#firstSequence >= EVENT_CAPACITY) {
138
+ this.#firstSequence = sequence - EVENT_CAPACITY + 1;
139
+ }
140
+ }
141
+ /**
142
+ * Returns the event stored under a sequence number, or `undefined` when that
143
+ * sequence was evicted or never allocated.
144
+ *
145
+ * @param sequence - The sequence number to read
146
+ * @returns The frozen event, or `undefined`
147
+ */
148
+ at(sequence) {
149
+ if (sequence < this.#firstSequence || sequence > this.#lastSequence) {
150
+ return undefined;
151
+ }
152
+ return this.#slots[(sequence - 1) % EVENT_CAPACITY];
153
+ }
154
+ /**
155
+ * Closes the ring to further writes.
156
+ *
157
+ * Retained events are NOT discarded here — {@linkcode clear} does that —
158
+ * but the collector's terminal shutdown path calls both, and its `read()`
159
+ * short-circuits on a closed ring, so a consumer never drains a tail after
160
+ * shutdown: it gets an empty `closed` batch. Closing and clearing are kept
161
+ * separate so the ring has one reason to change at a time.
162
+ */
163
+ close() {
164
+ this.#closed = true;
165
+ }
166
+ /**
167
+ * Discards every retained event. Used only by the teardown path: a failed
168
+ * startup or a final shutdown must not leave collected metadata recoverable
169
+ * through a read.
170
+ *
171
+ * The sequence counter is deliberately NOT reset: the discarded range is
172
+ * treated exactly like an eviction. A start that failed before any plugin
173
+ * registered can be corrected and retried on the same instance, and restarting at 1 would REUSE sequence
174
+ * numbers a reader already holds — a cursor past the new last sequence would
175
+ * then be refused as "beyond the current sequence", and one inside the new
176
+ * range would silently skip the retry's first events. Keeping the numbering
177
+ * monotonic means the retry's first event is `lastSequence + 1`, and a
178
+ * reader's `lost` counts the discarded records honestly.
179
+ */
180
+ clear() {
181
+ this.#slots.fill(undefined);
182
+ this.#firstSequence = this.#lastSequence + 1;
183
+ }
184
+ }
185
+ //# sourceMappingURL=buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.js","sourceRoot":"","sources":["../../src/diagnostics/buffer.ts"],"names":[],"mappings":"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"}