@telorun/kernel 0.62.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot-context-registry.js +1 -3
- package/dist/boot-context-registry.js.map +1 -1
- package/dist/bundle/module-artifact.js +15 -8
- package/dist/bundle/module-artifact.js.map +1 -1
- package/dist/controller-loader.d.ts +4 -0
- package/dist/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +5 -1
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-loaders/bundle-loader.d.ts +21 -0
- package/dist/controller-loaders/bundle-loader.d.ts.map +1 -1
- package/dist/controller-loaders/bundle-loader.js +98 -17
- package/dist/controller-loaders/bundle-loader.js.map +1 -1
- package/dist/controller-loaders/napi-loader.d.ts.map +1 -1
- package/dist/controller-loaders/napi-loader.js +24 -8
- package/dist/controller-loaders/napi-loader.js.map +1 -1
- package/dist/controller-loaders/npm-loader.js +37 -28
- package/dist/controller-loaders/npm-loader.js.map +1 -1
- package/dist/controller-loaders/source-bundle-builder.d.ts +30 -0
- package/dist/controller-loaders/source-bundle-builder.d.ts.map +1 -0
- package/dist/controller-loaders/source-bundle-builder.js +302 -0
- package/dist/controller-loaders/source-bundle-builder.js.map +1 -0
- package/dist/controller-registry.js +3 -5
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/resource-definition/abstract-controller.js +2 -1
- package/dist/controllers/resource-definition/abstract-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +11 -3
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/controllers/type/json-schema-controller.js +3 -0
- package/dist/controllers/type/json-schema-controller.js.map +1 -1
- package/dist/directory-lock.js +5 -5
- package/dist/directory-lock.js.map +1 -1
- package/dist/evaluation-context.js +60 -21
- package/dist/evaluation-context.js.map +1 -1
- package/dist/events.js +1 -3
- package/dist/events.js.map +1 -1
- package/dist/host-env.js +3 -2
- package/dist/host-env.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +9 -0
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +64 -26
- package/dist/kernel.js.map +1 -1
- package/dist/logging/console-sink.js +18 -29
- package/dist/logging/console-sink.js.map +1 -1
- package/dist/logging/debug-wire-sink.js +11 -23
- package/dist/logging/debug-wire-sink.js.map +1 -1
- package/dist/logging/drop-accounting.js +28 -41
- package/dist/logging/drop-accounting.js.map +1 -1
- package/dist/logging/file-sink.js +29 -39
- package/dist/logging/file-sink.js.map +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.d.ts.map +1 -1
- package/dist/logging/index.js +1 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/kernel-logging.js +30 -41
- package/dist/logging/kernel-logging.js.map +1 -1
- package/dist/logging/log-sink.d.ts +1 -1
- package/dist/logging/log-sink.d.ts.map +1 -1
- package/dist/logging/log-sink.js +1 -1
- package/dist/logging/log-sink.js.map +1 -1
- package/dist/logging/logging-pipeline.js +197 -197
- package/dist/logging/logging-pipeline.js.map +1 -1
- package/dist/logging/sampler.js +21 -34
- package/dist/logging/sampler.js.map +1 -1
- package/dist/manifest-sources/local-manifest-cache-source.js +2 -0
- package/dist/manifest-sources/local-manifest-cache-source.js.map +1 -1
- package/dist/manifest-sources/memory-source.js +1 -3
- package/dist/manifest-sources/memory-source.js.map +1 -1
- package/dist/module-context.js +62 -37
- package/dist/module-context.js.map +1 -1
- package/dist/registry.js +5 -7
- package/dist/registry.js.map +1 -1
- package/dist/resource-context.d.ts +11 -1
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +44 -23
- package/dist/resource-context.js.map +1 -1
- package/dist/resource-uri.js +4 -0
- package/dist/resource-uri.js.map +1 -1
- package/dist/runtime-seam.d.ts +19 -0
- package/dist/runtime-seam.d.ts.map +1 -0
- package/dist/runtime-seam.js +198 -0
- package/dist/runtime-seam.js.map +1 -0
- package/dist/schema-validator.js +20 -18
- package/dist/schema-validator.js.map +1 -1
- package/dist/tracing.js +4 -19
- package/dist/tracing.js.map +1 -1
- package/dist/transports/oci/oci-client.js +3 -1
- package/dist/transports/oci/oci-client.js.map +1 -1
- package/dist/transports/oci/oci-transport.js +17 -16
- package/dist/transports/oci/oci-transport.js.map +1 -1
- package/dist/transports/registry-transport.js +4 -0
- package/dist/transports/registry-transport.js.map +1 -1
- package/dist/transports/transport-registry.js +1 -0
- package/dist/transports/transport-registry.js.map +1 -1
- package/package.json +4 -4
- package/src/controller-loader.ts +5 -1
- package/src/controller-loaders/bundle-loader.ts +108 -23
- package/src/controller-loaders/napi-loader.ts +28 -9
- package/src/controller-loaders/source-bundle-builder.ts +337 -0
- package/src/controllers/resource-definition/resource-definition-controller.ts +23 -17
- package/src/index.ts +1 -0
- package/src/kernel.ts +13 -0
- package/src/logging/file-sink.ts +1 -1
- package/src/logging/index.ts +1 -1
- package/src/logging/log-sink.ts +1 -0
- package/src/resource-context.ts +23 -0
- package/src/runtime-seam.ts +230 -0
- package/dist/logging/record-buffer.d.ts +0 -24
- package/dist/logging/record-buffer.d.ts.map +0 -1
- package/dist/logging/record-buffer.js +0 -78
- package/dist/logging/record-buffer.js.map +0 -1
- package/src/logging/record-buffer.ts +0 -72
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Loader,
|
|
3
|
+
StaticAnalyzer,
|
|
4
|
+
flattenForAnalyzer,
|
|
5
|
+
type AnalysisDiagnostic,
|
|
6
|
+
type ManifestSource,
|
|
7
|
+
} from "@telorun/analyzer";
|
|
8
|
+
import {
|
|
9
|
+
Stream,
|
|
10
|
+
type RuntimeCheckOptions,
|
|
11
|
+
type RuntimeCheckResult,
|
|
12
|
+
type CheckDiagnostic,
|
|
13
|
+
type CheckDiagnosticSeverity,
|
|
14
|
+
type RuntimeRun,
|
|
15
|
+
type RuntimeRunOptions,
|
|
16
|
+
type RuntimeSeam,
|
|
17
|
+
} from "@telorun/sdk";
|
|
18
|
+
import { Writable } from "stream";
|
|
19
|
+
import { nodeCelHandlers } from "./cel-handlers.js";
|
|
20
|
+
import type { Kernel } from "./kernel.js";
|
|
21
|
+
import { defaultTransportRegistry } from "./transports/transport-registry.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* How many unconsumed chunks a channel holds before it stops acknowledging the
|
|
25
|
+
* child's writes.
|
|
26
|
+
*
|
|
27
|
+
* This is a courtesy, **not a memory bound**, and the distinction matters enough
|
|
28
|
+
* to state: withholding a `Writable`'s `_write` callback only moves subsequent
|
|
29
|
+
* chunks into the stream's own internal buffer, which is unbounded, and a
|
|
30
|
+
* controller writing to `ctx.stdout` does not await `drain`. So a child whose
|
|
31
|
+
* output nobody reads still grows the host's memory. What actually bounds it is
|
|
32
|
+
* `RuntimeRun.cancel()` — stopping the child, rather than hoping it stops
|
|
33
|
+
* producing. What the high-water mark does buy is that a *slow* consumer sees
|
|
34
|
+
* chunks handed over in order without the channel racing ahead of it.
|
|
35
|
+
*/
|
|
36
|
+
const CHANNEL_HIGH_WATER_MARK = 256;
|
|
37
|
+
|
|
38
|
+
interface PendingChunk {
|
|
39
|
+
text: string;
|
|
40
|
+
/** The child's `_write` callback, held back once the channel is over its
|
|
41
|
+
* high-water mark and released when a consumer takes the chunk. */
|
|
42
|
+
release?: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One direction of a child's output: a `Writable` the child kernel writes to,
|
|
47
|
+
* and the {@link Stream} the caller reads. Bridges Node's stream model to the
|
|
48
|
+
* SDK's without either side seeing the other's — `RuntimeSeam` has no
|
|
49
|
+
* `Writable` in it, and the child kernel has no `Stream` in it.
|
|
50
|
+
*/
|
|
51
|
+
class OutputChannel {
|
|
52
|
+
#queue: PendingChunk[] = [];
|
|
53
|
+
#waiters: Array<(result: IteratorResult<string>) => void> = [];
|
|
54
|
+
#ended = false;
|
|
55
|
+
|
|
56
|
+
readonly writable: Writable = new Writable({
|
|
57
|
+
write: (chunk: Buffer | string, _encoding, callback) => {
|
|
58
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
59
|
+
this.#push(text, callback);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
readonly stream: Stream<string> = new Stream<string>({
|
|
64
|
+
[Symbol.asyncIterator]: () => ({
|
|
65
|
+
next: () => this.#next(),
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** No more output is coming. Wakes every waiter with a terminal result, so a
|
|
70
|
+
* `for await` over the stream completes rather than hanging on a child that
|
|
71
|
+
* has already exited. */
|
|
72
|
+
end(): void {
|
|
73
|
+
this.#ended = true;
|
|
74
|
+
while (this.#waiters.length > 0) {
|
|
75
|
+
this.#waiters.shift()!({ value: undefined, done: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#push(text: string, callback: () => void): void {
|
|
80
|
+
const waiter = this.#waiters.shift();
|
|
81
|
+
if (waiter) {
|
|
82
|
+
waiter({ value: text, done: false });
|
|
83
|
+
callback();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (this.#queue.length >= CHANNEL_HIGH_WATER_MARK) {
|
|
87
|
+
this.#queue.push({ text, release: callback });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.#queue.push({ text });
|
|
91
|
+
callback();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async #next(): Promise<IteratorResult<string>> {
|
|
95
|
+
const pending = this.#queue.shift();
|
|
96
|
+
if (pending) {
|
|
97
|
+
pending.release?.();
|
|
98
|
+
return { value: pending.text, done: false };
|
|
99
|
+
}
|
|
100
|
+
if (this.#ended) return { value: undefined, done: true };
|
|
101
|
+
return new Promise<IteratorResult<string>>((resolve) => this.#waiters.push(resolve));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const SEVERITY_NAMES: Record<number, CheckDiagnosticSeverity> = {
|
|
106
|
+
1: "error",
|
|
107
|
+
2: "warning",
|
|
108
|
+
3: "info",
|
|
109
|
+
4: "hint",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Flatten an analyzer finding to the seam's plain-data shape. Severity becomes
|
|
113
|
+
* a name because the integer is an LSP protocol detail, and this contract is
|
|
114
|
+
* read by kernels that speak no LSP. An unlabelled severity is an error: the
|
|
115
|
+
* analyzer's own default, and the safe reading for a caller gating on it. */
|
|
116
|
+
function toCheckDiagnostic(diagnostic: AnalysisDiagnostic): CheckDiagnostic {
|
|
117
|
+
return {
|
|
118
|
+
code: String(diagnostic.code ?? ""),
|
|
119
|
+
message: diagnostic.message,
|
|
120
|
+
severity: SEVERITY_NAMES[diagnostic.severity ?? 1] ?? "error",
|
|
121
|
+
source: diagnostic.source,
|
|
122
|
+
line: diagnostic.range?.start?.line,
|
|
123
|
+
column: diagnostic.range?.start?.character,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The kernel's implementation of the SDK's {@link RuntimeSeam} — run a manifest,
|
|
129
|
+
* analyze a manifest — built over the `Kernel`, `Loader` and `StaticAnalyzer`
|
|
130
|
+
* this kernel already owns.
|
|
131
|
+
*
|
|
132
|
+
* Isolation is this class's choice, not the caller's: today a child `Kernel` in
|
|
133
|
+
* the same process, later a subprocess. That is what keeps process-global host
|
|
134
|
+
* state (the `process.env` guardrail is additive across in-process kernels) an
|
|
135
|
+
* implementation detail rather than something a module author has to know.
|
|
136
|
+
*/
|
|
137
|
+
export class KernelRuntimeSeam implements RuntimeSeam {
|
|
138
|
+
constructor(private readonly kernel: Kernel) {}
|
|
139
|
+
|
|
140
|
+
async run(source: string, options?: RuntimeRunOptions): Promise<RuntimeRun> {
|
|
141
|
+
// Import here rather than at module scope: `resource-context.ts` imports this
|
|
142
|
+
// file to construct the seam and `kernel.ts` imports *that*, so a top-level
|
|
143
|
+
// import of `Kernel` here would close the cycle. The dynamic import resolves
|
|
144
|
+
// after both modules are evaluated.
|
|
145
|
+
const { Kernel: ChildKernel } = await import("./kernel.js");
|
|
146
|
+
const stdout = new OutputChannel();
|
|
147
|
+
const stderr = new OutputChannel();
|
|
148
|
+
|
|
149
|
+
const child = new ChildKernel({
|
|
150
|
+
env: options?.env ?? this.kernel.env,
|
|
151
|
+
stdout: stdout.writable,
|
|
152
|
+
stderr: stderr.writable,
|
|
153
|
+
sources: [...this.kernel.injectedSources],
|
|
154
|
+
registryUrl: this.kernel.registryUrl,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// A child that fails to load is not an exception on this side: the caller
|
|
158
|
+
// asked to run a manifest and the answer is "it exited non-zero, here is
|
|
159
|
+
// why", which keeps both failure modes on one path. `start()` tears the
|
|
160
|
+
// child down in its own `finally`, so this only has to close the channels.
|
|
161
|
+
const exitCode = (async () => {
|
|
162
|
+
try {
|
|
163
|
+
await child.load(source);
|
|
164
|
+
await child.start();
|
|
165
|
+
return child.exitCode;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
stderr.writable.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
168
|
+
return child.exitCode === 0 ? 1 : child.exitCode;
|
|
169
|
+
} finally {
|
|
170
|
+
stdout.end();
|
|
171
|
+
stderr.end();
|
|
172
|
+
}
|
|
173
|
+
})();
|
|
174
|
+
|
|
175
|
+
let cancelled: Promise<void> | undefined;
|
|
176
|
+
const cancel = (reason = "cancelled"): Promise<void> => {
|
|
177
|
+
// Idempotent: repeated calls await the first teardown rather than starting
|
|
178
|
+
// another. `teardown()` is itself idempotent, but a second `forceIdle()`
|
|
179
|
+
// against a kernel already unwinding buys nothing.
|
|
180
|
+
cancelled ??= (async () => {
|
|
181
|
+
// Cancel the boot run first so an in-flight target observes it, then
|
|
182
|
+
// release `waitForIdle` so `start()`'s own `finally` performs the
|
|
183
|
+
// teardown — the same path a SIGINT takes. Awaiting `exitCode` is what
|
|
184
|
+
// makes "resolves when teardown has completed" true.
|
|
185
|
+
child.cancel(reason);
|
|
186
|
+
child.forceIdle();
|
|
187
|
+
await exitCode;
|
|
188
|
+
})();
|
|
189
|
+
return cancelled;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return { stdout: stdout.stream, stderr: stderr.stream, exitCode, cancel };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async check(source: string, options?: RuntimeCheckOptions): Promise<RuntimeCheckResult> {
|
|
196
|
+
// A fresh loader over this kernel's own resolution chain — the transports it
|
|
197
|
+
// was configured with plus whatever sources were injected — so `check()`
|
|
198
|
+
// resolves an import exactly as a run of the same manifest would. Fresh
|
|
199
|
+
// rather than the kernel's own loader because a checked manifest is often
|
|
200
|
+
// deliberately broken and has no business entering the running kernel's
|
|
201
|
+
// parse cache.
|
|
202
|
+
const loader = new Loader(defaultTransportRegistry(this.kernel.registryUrl).sources(), {
|
|
203
|
+
celHandlers: nodeCelHandlers,
|
|
204
|
+
});
|
|
205
|
+
for (const injected of this.kernel.injectedSources) {
|
|
206
|
+
loader.register(injected as ManifestSource);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let manifests;
|
|
210
|
+
try {
|
|
211
|
+
const graph = await loader.loadGraph(source, {
|
|
212
|
+
desugarImports: options?.desugarImports ?? true,
|
|
213
|
+
});
|
|
214
|
+
if (graph.errors.length > 0) throw graph.errors[0].error;
|
|
215
|
+
manifests = flattenForAnalyzer(graph);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
// A graph that would not load is an answer, not a failure of the call —
|
|
218
|
+
// "this manifest does not load, and here is the reason" is precisely what
|
|
219
|
+
// a caller asked for. Throwing would force every caller to re-express the
|
|
220
|
+
// distinction in a catch.
|
|
221
|
+
return {
|
|
222
|
+
diagnostics: [],
|
|
223
|
+
loadError: err instanceof Error ? err.message : String(err),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const diagnostics = new StaticAnalyzer({ celHandlers: nodeCelHandlers }).analyze(manifests);
|
|
228
|
+
return { diagnostics: diagnostics.map(toCheckDiagnostic) };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { LogRecord } from "@telorun/sdk";
|
|
2
|
-
import type { SinkBufferPolicy } from "./log-sink.js";
|
|
3
|
-
/**
|
|
4
|
-
* The bounded buffer every asynchronous sink composes — `kernel/specs/logging.md`
|
|
5
|
-
* §10.3.
|
|
6
|
-
*
|
|
7
|
-
* The ecosystems disagree by default: Rust's `tracing-appender` drops, zap
|
|
8
|
-
* buffers 256 kB / 30 s, and pino has no bound at all. Telo therefore makes the
|
|
9
|
-
* policy explicit and required, and the buffer is never unbounded.
|
|
10
|
-
*
|
|
11
|
-
* `drop_old` has no precedent among the surveyed libraries, so it is implemented
|
|
12
|
-
* as specified — true ring-buffer semantics, evicting the oldest record — rather
|
|
13
|
-
* than by analogy to something else.
|
|
14
|
-
*/
|
|
15
|
-
export declare class RecordBuffer {
|
|
16
|
-
#private;
|
|
17
|
-
constructor(policy: SinkBufferPolicy, onDrop: () => void);
|
|
18
|
-
get size(): number;
|
|
19
|
-
get isFull(): boolean;
|
|
20
|
-
push(record: LogRecord): void;
|
|
21
|
-
/** Take everything buffered, leaving the buffer empty. */
|
|
22
|
-
drain(): LogRecord[];
|
|
23
|
-
}
|
|
24
|
-
//# sourceMappingURL=record-buffer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"record-buffer.d.ts","sourceRoot":"","sources":["../../src/logging/record-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;;gBASX,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,IAAI;IAMxD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAiB7B,0DAA0D;IAC1D,KAAK,IAAI,SAAS,EAAE;CAerB"}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
-
};
|
|
7
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
-
};
|
|
12
|
-
var _RecordBuffer_instances, _RecordBuffer_capacity, _RecordBuffer_policy, _RecordBuffer_onDrop, _RecordBuffer_items, _RecordBuffer_head, _RecordBuffer_compact;
|
|
13
|
-
/**
|
|
14
|
-
* The bounded buffer every asynchronous sink composes — `kernel/specs/logging.md`
|
|
15
|
-
* §10.3.
|
|
16
|
-
*
|
|
17
|
-
* The ecosystems disagree by default: Rust's `tracing-appender` drops, zap
|
|
18
|
-
* buffers 256 kB / 30 s, and pino has no bound at all. Telo therefore makes the
|
|
19
|
-
* policy explicit and required, and the buffer is never unbounded.
|
|
20
|
-
*
|
|
21
|
-
* `drop_old` has no precedent among the surveyed libraries, so it is implemented
|
|
22
|
-
* as specified — true ring-buffer semantics, evicting the oldest record — rather
|
|
23
|
-
* than by analogy to something else.
|
|
24
|
-
*/
|
|
25
|
-
export class RecordBuffer {
|
|
26
|
-
constructor(policy, onDrop) {
|
|
27
|
-
_RecordBuffer_instances.add(this);
|
|
28
|
-
_RecordBuffer_capacity.set(this, void 0);
|
|
29
|
-
_RecordBuffer_policy.set(this, void 0);
|
|
30
|
-
_RecordBuffer_onDrop.set(this, void 0);
|
|
31
|
-
_RecordBuffer_items.set(this, []);
|
|
32
|
-
/** Index of the oldest record, so `drop_old` evicts in O(1) instead of
|
|
33
|
-
* shifting the whole array on every overflow. */
|
|
34
|
-
_RecordBuffer_head.set(this, 0);
|
|
35
|
-
__classPrivateFieldSet(this, _RecordBuffer_capacity, Math.max(1, policy.buffer), "f");
|
|
36
|
-
__classPrivateFieldSet(this, _RecordBuffer_policy, policy, "f");
|
|
37
|
-
__classPrivateFieldSet(this, _RecordBuffer_onDrop, onDrop, "f");
|
|
38
|
-
}
|
|
39
|
-
get size() {
|
|
40
|
-
return __classPrivateFieldGet(this, _RecordBuffer_items, "f").length - __classPrivateFieldGet(this, _RecordBuffer_head, "f");
|
|
41
|
-
}
|
|
42
|
-
get isFull() {
|
|
43
|
-
return this.size >= __classPrivateFieldGet(this, _RecordBuffer_capacity, "f");
|
|
44
|
-
}
|
|
45
|
-
push(record) {
|
|
46
|
-
if (this.isFull) {
|
|
47
|
-
if (__classPrivateFieldGet(this, _RecordBuffer_policy, "f").onFull === "drop_old") {
|
|
48
|
-
__classPrivateFieldSet(this, _RecordBuffer_head, __classPrivateFieldGet(this, _RecordBuffer_head, "f") + 1, "f");
|
|
49
|
-
__classPrivateFieldGet(this, _RecordBuffer_instances, "m", _RecordBuffer_compact).call(this);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
// `drop_new` — and `block`, which never reaches here because a runtime
|
|
53
|
-
// that cannot honour it rejects the manifest at load rather than
|
|
54
|
-
// silently degrading to a dropping policy.
|
|
55
|
-
__classPrivateFieldGet(this, _RecordBuffer_onDrop, "f").call(this);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
__classPrivateFieldGet(this, _RecordBuffer_onDrop, "f").call(this);
|
|
59
|
-
}
|
|
60
|
-
__classPrivateFieldGet(this, _RecordBuffer_items, "f").push(record);
|
|
61
|
-
}
|
|
62
|
-
/** Take everything buffered, leaving the buffer empty. */
|
|
63
|
-
drain() {
|
|
64
|
-
const drained = __classPrivateFieldGet(this, _RecordBuffer_head, "f") === 0 ? __classPrivateFieldGet(this, _RecordBuffer_items, "f") : __classPrivateFieldGet(this, _RecordBuffer_items, "f").slice(__classPrivateFieldGet(this, _RecordBuffer_head, "f"));
|
|
65
|
-
__classPrivateFieldSet(this, _RecordBuffer_items, [], "f");
|
|
66
|
-
__classPrivateFieldSet(this, _RecordBuffer_head, 0, "f");
|
|
67
|
-
return drained;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
_RecordBuffer_capacity = new WeakMap(), _RecordBuffer_policy = new WeakMap(), _RecordBuffer_onDrop = new WeakMap(), _RecordBuffer_items = new WeakMap(), _RecordBuffer_head = new WeakMap(), _RecordBuffer_instances = new WeakSet(), _RecordBuffer_compact = function _RecordBuffer_compact() {
|
|
71
|
-
// Reclaim the consumed prefix once it dominates the array, so a long-lived
|
|
72
|
-
// ring does not grow its backing store without bound.
|
|
73
|
-
if (__classPrivateFieldGet(this, _RecordBuffer_head, "f") > 32 && __classPrivateFieldGet(this, _RecordBuffer_head, "f") * 2 >= __classPrivateFieldGet(this, _RecordBuffer_items, "f").length) {
|
|
74
|
-
__classPrivateFieldSet(this, _RecordBuffer_items, __classPrivateFieldGet(this, _RecordBuffer_items, "f").slice(__classPrivateFieldGet(this, _RecordBuffer_head, "f")), "f");
|
|
75
|
-
__classPrivateFieldSet(this, _RecordBuffer_head, 0, "f");
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
//# sourceMappingURL=record-buffer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"record-buffer.js","sourceRoot":"","sources":["../../src/logging/record-buffer.ts"],"names":[],"mappings":";;;;;;;;;;;;AAGA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAY;IASvB,YAAY,MAAwB,EAAE,MAAkB;;QAR/C,yCAAkB;QAClB,uCAA0B;QAC1B,uCAAoB;QAC7B,8BAAsB,EAAE,EAAC;QACzB;0DACkD;QAClD,6BAAQ,CAAC,EAAC;QAGR,uBAAA,IAAI,0BAAa,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,MAAA,CAAC;QAC5C,uBAAA,IAAI,wBAAW,MAAM,MAAA,CAAC;QACtB,uBAAA,IAAI,wBAAW,MAAM,MAAA,CAAC;IACxB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,uBAAA,IAAI,2BAAO,CAAC,MAAM,GAAG,uBAAA,IAAI,0BAAM,CAAC;IACzC,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,IAAI,IAAI,uBAAA,IAAI,8BAAU,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,MAAiB;QACpB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,uBAAA,IAAI,4BAAQ,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACvC,yGAAc,CAAC,MAAA,CAAC;gBAChB,uBAAA,IAAI,sDAAS,MAAb,IAAI,CAAW,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,uEAAuE;gBACvE,iEAAiE;gBACjE,2CAA2C;gBAC3C,uBAAA,IAAI,4BAAQ,MAAZ,IAAI,CAAU,CAAC;gBACf,OAAO;YACT,CAAC;YACD,uBAAA,IAAI,4BAAQ,MAAZ,IAAI,CAAU,CAAC;QACjB,CAAC;QACD,uBAAA,IAAI,2BAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAED,0DAA0D;IAC1D,KAAK;QACH,MAAM,OAAO,GAAG,uBAAA,IAAI,0BAAM,KAAK,CAAC,CAAC,CAAC,CAAC,uBAAA,IAAI,2BAAO,CAAC,CAAC,CAAC,uBAAA,IAAI,2BAAO,CAAC,KAAK,CAAC,uBAAA,IAAI,0BAAM,CAAC,CAAC;QAC/E,uBAAA,IAAI,uBAAU,EAAE,MAAA,CAAC;QACjB,uBAAA,IAAI,sBAAS,CAAC,MAAA,CAAC;QACf,OAAO,OAAO,CAAC;IACjB,CAAC;CAUF;;IAPG,2EAA2E;IAC3E,sDAAsD;IACtD,IAAI,uBAAA,IAAI,0BAAM,GAAG,EAAE,IAAI,uBAAA,IAAI,0BAAM,GAAG,CAAC,IAAI,uBAAA,IAAI,2BAAO,CAAC,MAAM,EAAE,CAAC;QAC5D,uBAAA,IAAI,uBAAU,uBAAA,IAAI,2BAAO,CAAC,KAAK,CAAC,uBAAA,IAAI,0BAAM,CAAC,MAAA,CAAC;QAC5C,uBAAA,IAAI,sBAAS,CAAC,MAAA,CAAC;IACjB,CAAC;AACH,CAAC"}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { LogRecord } from "@telorun/sdk";
|
|
2
|
-
import type { SinkBufferPolicy } from "./log-sink.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The bounded buffer every asynchronous sink composes — `kernel/specs/logging.md`
|
|
6
|
-
* §10.3.
|
|
7
|
-
*
|
|
8
|
-
* The ecosystems disagree by default: Rust's `tracing-appender` drops, zap
|
|
9
|
-
* buffers 256 kB / 30 s, and pino has no bound at all. Telo therefore makes the
|
|
10
|
-
* policy explicit and required, and the buffer is never unbounded.
|
|
11
|
-
*
|
|
12
|
-
* `drop_old` has no precedent among the surveyed libraries, so it is implemented
|
|
13
|
-
* as specified — true ring-buffer semantics, evicting the oldest record — rather
|
|
14
|
-
* than by analogy to something else.
|
|
15
|
-
*/
|
|
16
|
-
export class RecordBuffer {
|
|
17
|
-
readonly #capacity: number;
|
|
18
|
-
readonly #policy: SinkBufferPolicy;
|
|
19
|
-
readonly #onDrop: () => void;
|
|
20
|
-
#items: LogRecord[] = [];
|
|
21
|
-
/** Index of the oldest record, so `drop_old` evicts in O(1) instead of
|
|
22
|
-
* shifting the whole array on every overflow. */
|
|
23
|
-
#head = 0;
|
|
24
|
-
|
|
25
|
-
constructor(policy: SinkBufferPolicy, onDrop: () => void) {
|
|
26
|
-
this.#capacity = Math.max(1, policy.buffer);
|
|
27
|
-
this.#policy = policy;
|
|
28
|
-
this.#onDrop = onDrop;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
get size(): number {
|
|
32
|
-
return this.#items.length - this.#head;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get isFull(): boolean {
|
|
36
|
-
return this.size >= this.#capacity;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
push(record: LogRecord): void {
|
|
40
|
-
if (this.isFull) {
|
|
41
|
-
if (this.#policy.onFull === "drop_old") {
|
|
42
|
-
this.#head += 1;
|
|
43
|
-
this.#compact();
|
|
44
|
-
} else {
|
|
45
|
-
// `drop_new` — and `block`, which never reaches here because a runtime
|
|
46
|
-
// that cannot honour it rejects the manifest at load rather than
|
|
47
|
-
// silently degrading to a dropping policy.
|
|
48
|
-
this.#onDrop();
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
this.#onDrop();
|
|
52
|
-
}
|
|
53
|
-
this.#items.push(record);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Take everything buffered, leaving the buffer empty. */
|
|
57
|
-
drain(): LogRecord[] {
|
|
58
|
-
const drained = this.#head === 0 ? this.#items : this.#items.slice(this.#head);
|
|
59
|
-
this.#items = [];
|
|
60
|
-
this.#head = 0;
|
|
61
|
-
return drained;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#compact(): void {
|
|
65
|
-
// Reclaim the consumed prefix once it dominates the array, so a long-lived
|
|
66
|
-
// ring does not grow its backing store without bound.
|
|
67
|
-
if (this.#head > 32 && this.#head * 2 >= this.#items.length) {
|
|
68
|
-
this.#items = this.#items.slice(this.#head);
|
|
69
|
-
this.#head = 0;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|