@absolutejs/runtime 0.1.0 → 0.3.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/index.d.ts +48 -1
- package/dist/index.js +111 -1
- package/dist/index.js.map +5 -4
- package/package.json +5 -1
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
import type { Subprocess } from "bun";
|
|
47
|
+
import { type TracerProvider as TelemetryTracerProvider } from "@absolutejs/telemetry";
|
|
47
48
|
export type TenantSource = {
|
|
48
49
|
kind: "directory";
|
|
49
50
|
root: string;
|
|
@@ -212,6 +213,21 @@ export type RuntimeOptions = {
|
|
|
212
213
|
* Tests use this to point at a fixture script.
|
|
213
214
|
*/
|
|
214
215
|
command?: readonly string[];
|
|
216
|
+
/**
|
|
217
|
+
* Optional OpenTelemetry tracer provider. When set, each
|
|
218
|
+
* `ensure()` / `restart()` is wrapped in a `runtime.spawn` span
|
|
219
|
+
* with `abs.tenant`, `abs.runtime.pid`, `abs.runtime.port`,
|
|
220
|
+
* `abs.runtime.readiness_ms`, and (on exit) `abs.runtime.exit_reason`
|
|
221
|
+
* attributes. The span is the ROOT of the customer's trace —
|
|
222
|
+
* everything inside the spawned tenant inherits the active context
|
|
223
|
+
* if it propagates correctly (W3C trace context via env / headers).
|
|
224
|
+
* When omitted, all tracing is a zero-allocation noop. Added in 0.3.0.
|
|
225
|
+
*
|
|
226
|
+
* Pass any `@opentelemetry/api`-compatible `TracerProvider`. See
|
|
227
|
+
* `@absolutejs/telemetry` for the type shape — runtime re-uses its
|
|
228
|
+
* helpers but doesn't peer-dep `@opentelemetry/api` directly.
|
|
229
|
+
*/
|
|
230
|
+
tracerProvider?: TelemetryTracerProvider;
|
|
215
231
|
};
|
|
216
232
|
export type RuntimeStats = {
|
|
217
233
|
running: number;
|
|
@@ -221,6 +237,31 @@ export type RuntimeStats = {
|
|
|
221
237
|
/** Number of keys currently in the back-off window. */
|
|
222
238
|
backoff: number;
|
|
223
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* Operator-shaped metrics returned by {@link Runtime.metrics}. Combines
|
|
242
|
+
* the point-in-time {@link RuntimeStats} fields with cumulative counters
|
|
243
|
+
* since `createRuntime()`. Survives `dispose()` so post-shutdown
|
|
244
|
+
* introspection still reads the totals. Added in 0.2.0.
|
|
245
|
+
*
|
|
246
|
+
* - `totalSpawns` — successful `spawn()` calls (failed spawns hit
|
|
247
|
+
* `recordBackoff` instead and bump `totalBackoffEntries`).
|
|
248
|
+
* - `totalExits` — exits keyed by `ExitReason`. A climbing
|
|
249
|
+
* `crashed` means a tenant is unhealthy; `idle-killed` is the
|
|
250
|
+
* expected steady-state for hibernation; `lru-evicted` means the
|
|
251
|
+
* `maxRunning` cap is biting.
|
|
252
|
+
* - `totalBackoffEntries` — `recordBackoff` calls. Distinct from the
|
|
253
|
+
* point-in-time `backoff` (current keys in window): a single key
|
|
254
|
+
* that fails 5 times bumps `totalBackoffEntries` by 5 but only
|
|
255
|
+
* contributes 1 to `backoff`.
|
|
256
|
+
* - `lastSpawnMs` — wall-clock of the most recent spawn. A climb
|
|
257
|
+
* here is the operator's "is spawning getting slow" signal.
|
|
258
|
+
*/
|
|
259
|
+
export type RuntimeMetrics = RuntimeStats & {
|
|
260
|
+
totalSpawns: number;
|
|
261
|
+
totalExits: Record<ExitReason, number>;
|
|
262
|
+
totalBackoffEntries: number;
|
|
263
|
+
lastSpawnMs: number;
|
|
264
|
+
};
|
|
224
265
|
export type Runtime = {
|
|
225
266
|
/**
|
|
226
267
|
* Resolve `key` to a running tenant. Spawns if not running, waits
|
|
@@ -239,8 +280,14 @@ export type Runtime = {
|
|
|
239
280
|
* tenant.
|
|
240
281
|
*/
|
|
241
282
|
touch: (key: string) => void;
|
|
242
|
-
/** Synchronous snapshot. */
|
|
283
|
+
/** Synchronous point-in-time snapshot — back-compat alias of metrics() shape (subset). */
|
|
243
284
|
stats: () => RuntimeStats;
|
|
285
|
+
/**
|
|
286
|
+
* Operator-shaped point-in-time + cumulative metrics (since
|
|
287
|
+
* `createRuntime()`). Use this — `stats()` is kept for back-compat
|
|
288
|
+
* but doesn't carry the cumulative counters. Added in 0.2.0.
|
|
289
|
+
*/
|
|
290
|
+
metrics: () => RuntimeMetrics;
|
|
244
291
|
/** Force-kill `key`. No-op if not running. */
|
|
245
292
|
kill: (key: string) => Promise<void>;
|
|
246
293
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// node_modules/@absolutejs/telemetry/dist/index.js
|
|
3
|
+
var NOOP_SPAN_CONTEXT = {
|
|
4
|
+
spanId: "0000000000000000",
|
|
5
|
+
traceFlags: 0,
|
|
6
|
+
traceId: "00000000000000000000000000000000"
|
|
7
|
+
};
|
|
8
|
+
var noopSpan = {
|
|
9
|
+
addEvent: () => noopSpan,
|
|
10
|
+
end: () => {},
|
|
11
|
+
isRecording: () => false,
|
|
12
|
+
recordException: () => {},
|
|
13
|
+
setAttribute: () => noopSpan,
|
|
14
|
+
setAttributes: () => noopSpan,
|
|
15
|
+
setStatus: () => noopSpan,
|
|
16
|
+
spanContext: () => NOOP_SPAN_CONTEXT,
|
|
17
|
+
updateName: () => noopSpan
|
|
18
|
+
};
|
|
19
|
+
var startActiveSpanNoop = (_name, optionsOrFn, maybeFn) => {
|
|
20
|
+
const fn = typeof optionsOrFn === "function" ? optionsOrFn : maybeFn;
|
|
21
|
+
return fn(noopSpan);
|
|
22
|
+
};
|
|
23
|
+
var noopTracer = {
|
|
24
|
+
startActiveSpan: startActiveSpanNoop,
|
|
25
|
+
startSpan: () => noopSpan
|
|
26
|
+
};
|
|
27
|
+
var tracerOrNoop = (provider, name, version) => provider !== undefined ? provider.getTracer(name, version) : noopTracer;
|
|
28
|
+
var ABS_ATTRS = {
|
|
29
|
+
tenant: "abs.tenant",
|
|
30
|
+
shardId: "abs.shard.id",
|
|
31
|
+
engineId: "abs.engine.id",
|
|
32
|
+
collection: "abs.collection",
|
|
33
|
+
mutation: "abs.mutation",
|
|
34
|
+
mutationAttempt: "abs.mutation.attempt",
|
|
35
|
+
subscriptionId: "abs.subscription.id",
|
|
36
|
+
batchSize: "abs.batch.size",
|
|
37
|
+
clusterMessageOrigin: "abs.cluster.origin",
|
|
38
|
+
jobId: "abs.job.id",
|
|
39
|
+
jobKind: "abs.job.kind",
|
|
40
|
+
jobAttempt: "abs.job.attempt",
|
|
41
|
+
jobMaxAttempts: "abs.job.max_attempts",
|
|
42
|
+
workerId: "abs.worker.id",
|
|
43
|
+
runtimeKey: "abs.runtime.key",
|
|
44
|
+
runtimePid: "abs.runtime.pid",
|
|
45
|
+
runtimePort: "abs.runtime.port",
|
|
46
|
+
runtimeExitReason: "abs.runtime.exit_reason",
|
|
47
|
+
runtimeReadinessMs: "abs.runtime.readiness_ms",
|
|
48
|
+
routeShard: "abs.route.shard",
|
|
49
|
+
routeDecision: "abs.route.decision",
|
|
50
|
+
secretName: "abs.secret.name",
|
|
51
|
+
secretFingerprint: "abs.secret.fingerprint",
|
|
52
|
+
auditKind: "abs.audit.kind"
|
|
53
|
+
};
|
|
54
|
+
|
|
2
55
|
// src/index.ts
|
|
3
56
|
var defaultReadiness = async ({ port, startedAt }) => {
|
|
4
57
|
const deadline = startedAt + 30000;
|
|
@@ -129,6 +182,21 @@ var createRuntime = (options) => {
|
|
|
129
182
|
let lastObserveAt = 0;
|
|
130
183
|
let disposed = false;
|
|
131
184
|
let draining = false;
|
|
185
|
+
const tracer = tracerOrNoop(options.tracerProvider, "@absolutejs/runtime");
|
|
186
|
+
const openSpawnSpans = new Map;
|
|
187
|
+
let totalSpawns = 0;
|
|
188
|
+
const totalExits = {
|
|
189
|
+
crashed: 0,
|
|
190
|
+
"exited-clean": 0,
|
|
191
|
+
"idle-killed": 0,
|
|
192
|
+
"lru-evicted": 0,
|
|
193
|
+
killed: 0,
|
|
194
|
+
"readiness-timeout": 0,
|
|
195
|
+
disposed: 0,
|
|
196
|
+
restarted: 0
|
|
197
|
+
};
|
|
198
|
+
let totalBackoffEntries = 0;
|
|
199
|
+
let lastSpawnMs = 0;
|
|
132
200
|
const emitMetric = (event) => {
|
|
133
201
|
if (onMetrics === undefined)
|
|
134
202
|
return;
|
|
@@ -179,6 +247,7 @@ var createRuntime = (options) => {
|
|
|
179
247
|
const wait = Math.min(backoffOptions.maxMs, backoffOptions.baseMs * 2 ** (attempt - 1));
|
|
180
248
|
const message = error instanceof Error ? error.message : String(error);
|
|
181
249
|
backoffs.set(key, { attempt, lastError: message, retryAt: Date.now() + wait });
|
|
250
|
+
totalBackoffEntries += 1;
|
|
182
251
|
};
|
|
183
252
|
const observeRunning = async () => {
|
|
184
253
|
if (!isLinux || observeIntervalMs <= 0 || onMetrics === undefined)
|
|
@@ -275,6 +344,7 @@ var createRuntime = (options) => {
|
|
|
275
344
|
PORT: String(port)
|
|
276
345
|
};
|
|
277
346
|
let child;
|
|
347
|
+
const spawnStart = Date.now();
|
|
278
348
|
try {
|
|
279
349
|
child = await spawn({
|
|
280
350
|
cwd,
|
|
@@ -287,11 +357,34 @@ var createRuntime = (options) => {
|
|
|
287
357
|
recordBackoff(key, error);
|
|
288
358
|
throw error;
|
|
289
359
|
}
|
|
360
|
+
totalSpawns += 1;
|
|
361
|
+
lastSpawnMs = Date.now() - spawnStart;
|
|
362
|
+
const spawnSpan = tracer.startSpan("runtime.spawn", {
|
|
363
|
+
attributes: {
|
|
364
|
+
[ABS_ATTRS.runtimeKey]: key,
|
|
365
|
+
[ABS_ATTRS.runtimePid]: child.pid,
|
|
366
|
+
[ABS_ATTRS.runtimePort]: port,
|
|
367
|
+
[ABS_ATTRS.runtimeReadinessMs]: lastSpawnMs
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
openSpawnSpans.set(child.pid, spawnSpan);
|
|
290
371
|
emitTransition({ key, pid: child.pid, port, type: "spawn" });
|
|
291
372
|
child.exited.then((exitCode) => {
|
|
292
373
|
const stashed = exitReasons.get(child.pid);
|
|
293
374
|
const reason = stashed ?? (exitCode === 0 ? "exited-clean" : "crashed");
|
|
294
375
|
exitReasons.delete(child.pid);
|
|
376
|
+
totalExits[reason] += 1;
|
|
377
|
+
const exitSpan = openSpawnSpans.get(child.pid);
|
|
378
|
+
if (exitSpan !== undefined) {
|
|
379
|
+
openSpawnSpans.delete(child.pid);
|
|
380
|
+
exitSpan.setAttribute(ABS_ATTRS.runtimeExitReason, reason);
|
|
381
|
+
if (exitCode !== null && exitCode !== undefined) {
|
|
382
|
+
exitSpan.setAttribute("abs.runtime.exit_code", exitCode);
|
|
383
|
+
}
|
|
384
|
+
const ok = reason === "exited-clean" || reason === "idle-killed" || reason === "lru-evicted" || reason === "disposed" || reason === "restarted";
|
|
385
|
+
exitSpan.setStatus({ code: ok ? 1 : 2 });
|
|
386
|
+
exitSpan.end();
|
|
387
|
+
}
|
|
295
388
|
emitTransition({
|
|
296
389
|
exitCode: exitCode ?? null,
|
|
297
390
|
key,
|
|
@@ -411,6 +504,23 @@ var createRuntime = (options) => {
|
|
|
411
504
|
}
|
|
412
505
|
return { backoff: backoffs.size, draining, running, total: entries.size };
|
|
413
506
|
},
|
|
507
|
+
metrics() {
|
|
508
|
+
let running = 0;
|
|
509
|
+
for (const entry of entries.values()) {
|
|
510
|
+
if (entry.tenant !== null)
|
|
511
|
+
running += 1;
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
backoff: backoffs.size,
|
|
515
|
+
draining,
|
|
516
|
+
lastSpawnMs,
|
|
517
|
+
running,
|
|
518
|
+
total: entries.size,
|
|
519
|
+
totalBackoffEntries,
|
|
520
|
+
totalExits: { ...totalExits },
|
|
521
|
+
totalSpawns
|
|
522
|
+
};
|
|
523
|
+
},
|
|
414
524
|
async kill(key) {
|
|
415
525
|
const entry = entries.get(key);
|
|
416
526
|
if (entry === undefined)
|
|
@@ -463,5 +573,5 @@ export {
|
|
|
463
573
|
createRuntime
|
|
464
574
|
};
|
|
465
575
|
|
|
466
|
-
//# debugId=
|
|
576
|
+
//# debugId=FD61CCA67627CB9764756E2164756E21
|
|
467
577
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts"],
|
|
3
|
+
"sources": ["../node_modules/@absolutejs/telemetry/dist/index.js", "../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * `@absolutejs/runtime` — multi-tenant Bun runtime substrate.\n *\n * Wraps Bun's `spawn` so that \"run this tenant's `bun run start` inside\n * a hibernating, metric-emitting child process\" is one function call.\n * Built for PaaS providers that want to host many small Bun apps under\n * one host process.\n *\n * Architectural role: SB-6's `@absolutejs/runtime` library. Consumers\n * include the hosted `absolutejs.ai` PaaS (eventual) and anyone else\n * who needs the same shape. Stays decoupled from `@absolutejs/sync`\n * and `@absolutejs/isolated-jsc` — those libraries solve different\n * layers of the same stack.\n *\n * Hibernation strategy (per STRATEGY-CLOUD.md §9.5): idle-kill at the\n * process layer. Bun has no shipped process-level snapshot/resume\n * primitive as of 2026-05-29. When that primitive lands we'll add an\n * opt-in `hibernate: 'process-snapshot'` mode and keep idle-kill as\n * the default.\n *\n * @example\n * ```ts\n * import { createRuntime } from '@absolutejs/runtime';\n *\n * const runtime = createRuntime({\n * source: { kind: 'directory', root: '/srv/tenants' },\n * idleAfterMs: 5 * 60 * 1000, // 5 min\n * maxConcurrent: 100,\n * onMetrics: (event) => prometheus.observe(event),\n * onLog: (event) => loki.write(event),\n * observeIntervalMs: 30_000,\n * });\n *\n * // First call: spawns `bun run start` in /srv/tenants/tenant-42,\n * // injects PORT, waits for readiness, returns the bound port.\n * const tenant = await runtime.ensure('tenant-42');\n * fetch(`http://localhost:${tenant.port}/`);\n *\n * // Subsequent calls reuse the running process.\n * runtime.touch('tenant-42'); // bump idle clock\n *\n * runtime.stats(); // { running, total, draining }\n * await runtime.dispose();\n * ```\n */\n\nimport type { Subprocess } from \"bun\";\n\nexport type TenantSource =\n | { kind: \"directory\"; root: string };\n\n/** Identity of a single tenant process at a point in time. */\nexport type Tenant = {\n /** The key the consumer used to address this tenant. */\n key: string;\n /** The port the child process bound to, discovered after readiness. */\n port: number;\n /** OS process id. */\n pid: number;\n /** Wall-clock when the child was spawned. */\n startedAt: number;\n /** Last time the consumer marked this tenant active. */\n lastTouchedAt: number;\n};\n\nexport type RuntimeMetricEvent =\n | {\n type: \"spawn\";\n key: string;\n pid: number;\n port: number;\n durationMs: number;\n }\n | {\n /** Periodic observation emitted by the sweeper (Linux-only; see `observeIntervalMs`). */\n type: \"observation\";\n key: string;\n pid: number;\n /** Cumulative CPU ms used by the child since spawn, derived from `/proc/<pid>/stat`. */\n cpuMs: number;\n /** Resident set size in bytes, derived from `/proc/<pid>/status` VmRSS. */\n rssBytes: number;\n at: number;\n };\n\nexport type RuntimeLogEvent = {\n key: string;\n pid: number;\n stream: \"stdout\" | \"stderr\";\n /** A single line of output (newline-terminated lines are split client-side). */\n line: string;\n at: number;\n};\n\n/**\n * Why a tenant process ended. Used by `RuntimeTransitionEvent` of type\n * `'exit'` to give the consumer enough info to charge or restart correctly:\n * - `crashed` — the process exited on its own with a non-zero code\n * - `exited-clean` — the process exited 0 (probably a graceful self-stop)\n * - `idle-killed` — the sweeper killed it after `idleAfterMs` with no `touch()`\n * - `lru-evicted` — `ensure()` for a new tenant evicted this one\n * - `killed` — explicit `runtime.kill(key)` call\n * - `readiness-timeout` — readiness check failed; we killed during spawn\n * - `disposed` — `runtime.dispose()` killed it\n * - `restarted` — `runtime.restart(key)` killed it on purpose\n */\nexport type ExitReason =\n | \"crashed\"\n | \"exited-clean\"\n | \"idle-killed\"\n | \"lru-evicted\"\n | \"killed\"\n | \"readiness-timeout\"\n | \"disposed\"\n | \"restarted\";\n\nexport type RuntimeTransitionEvent =\n | { type: \"spawn\"; key: string; pid: number; port: number }\n | {\n type: \"ready\";\n key: string;\n pid: number;\n port: number;\n durationMs: number;\n }\n | {\n type: \"idle-kill\";\n key: string;\n pid: number;\n reason: \"idle-threshold\";\n idleMs: number;\n }\n | { type: \"lru-evict\"; key: string; pid: number; reason: \"max-concurrent\" }\n | {\n type: \"exit\";\n key: string;\n pid: number;\n exitCode: number | null;\n reason: ExitReason;\n }\n | {\n /** A spawn was deferred because the key is in the back-off window after a failure. */\n type: \"backoff\";\n key: string;\n attempt: number;\n retryAfterMs: number;\n }\n | { type: \"drain\"; reason: \"drain-requested\" };\n\nexport type ReadinessCheck = (args: {\n key: string;\n port: number;\n /** Wall-clock spawn time so the check can compute its own elapsed. */\n startedAt: number;\n}) => Promise<boolean>;\n\nexport type SpawnFn = (args: {\n cwd: string;\n env: Record<string, string>;\n onLogLine: (event: RuntimeLogEvent) => void;\n key: string;\n}) => Promise<Subprocess>;\n\nexport type SpawnBackoff = {\n /** First retry waits this long. Default 1000 ms. */\n baseMs?: number;\n /** Maximum back-off (the cap on the doubled wait). Default 60_000 ms. */\n maxMs?: number;\n /** After this many consecutive failures, `ensure()` throws immediately for this key until reset. Default 10. */\n maxFailures?: number;\n};\n\n/** Options for {@link createRuntime}. */\nexport type RuntimeOptions = {\n /** Where to find tenant project directories. */\n source: TenantSource;\n /**\n * Kill the child process after this many ms with no `touch()` call.\n * Default 5 minutes. Set to `0` to disable idle-kill (only LRU and\n * explicit `kill()` shed processes then).\n */\n idleAfterMs?: number;\n /**\n * Max concurrent tenant processes. When a fresh `ensure()` would\n * push past this, the least-recently-touched process is killed\n * first. Default 100.\n */\n maxConcurrent?: number;\n /**\n * Background sweep interval. Default 10_000 ms. The sweep runs only\n * when the runtime is non-empty and is unrefed so the process can\n * exit cleanly.\n */\n sweepIntervalMs?: number;\n /**\n * How often the sweeper observes CPU + RSS per running tenant. Default\n * 30_000 ms. Set to `0` to disable; observation only works on Linux\n * (`/proc/<pid>` derived) — the sweeper silently skips on other OSes.\n * Output goes to `onMetrics` as `{ type: 'observation', ... }`.\n */\n observeIntervalMs?: number;\n /**\n * Override the readiness check. Default: HTTP GET to\n * `http://127.0.0.1:${port}/` with a 100ms retry loop, give up after\n * 30s with a `Tenant readiness timed out` error.\n */\n readiness?: ReadinessCheck;\n /**\n * Override how a child process is spawned. Default: `Bun.spawn` with\n * `['bun', 'run', 'start']`, stdio piped through `onLogLine`, env\n * carrying `PORT=${allocatedPort}` and `NODE_ENV=production`. Tests\n * use this to inject a fixture without writing to disk.\n */\n spawn?: SpawnFn;\n /** Exponential-backoff policy for consecutive spawn failures. */\n backoff?: SpawnBackoff;\n /** Operational metrics — spawn/ready durations + periodic observations. */\n onMetrics?: (event: RuntimeMetricEvent) => void;\n /** stdout/stderr stream. Bounded internally; backpressure to the host. */\n onLog?: (event: RuntimeLogEvent) => void;\n /** Lifecycle events — spawn/ready/idle-kill/lru-evict/exit/backoff/drain. */\n onTransition?: (event: RuntimeTransitionEvent) => void;\n /**\n * Command to run when spawning. Default `['bun', 'run', 'start']`.\n * Tests use this to point at a fixture script.\n */\n command?: readonly string[];\n};\n\nexport type RuntimeStats = {\n running: number;\n total: number;\n /** True when the runtime is draining — refusing new ensure() calls. */\n draining: boolean;\n /** Number of keys currently in the back-off window. */\n backoff: number;\n};\n\nexport type Runtime = {\n /**\n * Resolve `key` to a running tenant. Spawns if not running, waits\n * for readiness, returns the live {@link Tenant} including the bound\n * `port`. Concurrent calls to the same key share a single-flight\n * spawn — N callers don't create N processes.\n *\n * If `key` is in the back-off window after a recent failure, throws\n * immediately (without spawning). Use `clearBackoff(key)` to retry early.\n */\n ensure: (key: string) => Promise<Tenant>;\n /**\n * Mark `key` as active right now. Bumps the idle clock; the next\n * sweep won't consider it for idle-kill until `idleAfterMs` again.\n * Cheap; safe to call before/after each request you route to this\n * tenant.\n */\n touch: (key: string) => void;\n /** Synchronous snapshot. */\n stats: () => RuntimeStats;\n /** Force-kill `key`. No-op if not running. */\n kill: (key: string) => Promise<void>;\n /**\n * Kill `key` and respawn it. Used by deploys to swap to a new release\n * after the `current` symlink has been updated. Concurrent restart\n * calls for the same key share a single-flight respawn.\n */\n restart: (key: string) => Promise<Tenant>;\n /** Forget any consecutive-failure state for `key`. Next `ensure()` retries immediately. */\n clearBackoff: (key: string) => void;\n /**\n * Begin draining: refuse new `ensure()` calls (they throw immediately).\n * In-flight spawns and existing tenants are untouched — wait for\n * `stats().running` to reach 0, or call `dispose()` for hard shutdown.\n * Useful for graceful shard shutdown before a host reboot.\n */\n drain: () => void;\n /** Dispose every running child + stop the sweep. Idempotent. */\n dispose: () => Promise<void>;\n};\n\n/* ─── internals ──────────────────────────────────────────────────────── */\n\ntype Entry = {\n key: string;\n /** Set while the spawn is in-flight; concurrent ensure() callers await it. */\n pending: Promise<Tenant> | null;\n tenant: Tenant | null;\n child: Subprocess | null;\n /** Set by code that's about to kill the child, read by the exit handler. */\n pendingExitReason: ExitReason | null;\n};\n\ntype BackoffState = {\n attempt: number;\n retryAt: number;\n lastError: string;\n};\n\nconst defaultReadiness: ReadinessCheck = async ({ port, startedAt }) => {\n const deadline = startedAt + 30_000;\n while (Date.now() < deadline) {\n try {\n const res = await fetch(`http://127.0.0.1:${port}/`, {\n signal: AbortSignal.timeout(2_000),\n });\n void res;\n return true;\n } catch {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n throw new Error(\"Tenant readiness timed out after 30s\");\n};\n\nconst allocateEphemeralPort = (): number => {\n const server = Bun.listen({\n hostname: \"127.0.0.1\",\n port: 0,\n socket: {\n data: () => {},\n open: () => {},\n },\n });\n const port = server.port;\n server.stop(true);\n return port;\n};\n\nconst splitLines = (() => {\n const remainders = new Map<string, string>();\n return (key: string, chunk: string): string[] => {\n const prior = remainders.get(key) ?? \"\";\n const combined = prior + chunk;\n const parts = combined.split(\"\\n\");\n remainders.set(key, parts.pop() ?? \"\");\n return parts;\n };\n})();\n\nconst isLinux = typeof process !== \"undefined\" && process.platform === \"linux\";\n\n/**\n * Read CPU + RSS for a pid from `/proc`. Returns `null` if the pid is gone\n * or we're not on Linux. The math: `utime + stime` from `/proc/<pid>/stat`\n * is in clock ticks; we divide by `Bun.clockTicksPerSecond` (or fall back\n * to 100 — the universal default for Linux kernels).\n */\nconst readProcStats = async (pid: number): Promise<{ cpuMs: number; rssBytes: number } | null> => {\n if (!isLinux) return null;\n try {\n const statText = await Bun.file(`/proc/${pid}/stat`).text();\n const statusText = await Bun.file(`/proc/${pid}/status`).text();\n // /proc/<pid>/stat: ... (comm) ... and utime/stime are fields 14 and 15\n // counting from 1; but `comm` can contain spaces, so we anchor on the\n // closing paren.\n const closeParen = statText.lastIndexOf(\")\");\n if (closeParen === -1) return null;\n const after = statText.slice(closeParen + 2).split(\" \");\n // After (comm), the fields are: state ppid pgrp session ... utime stime ...\n // utime = field 14 of the whole line = index (14 - 3 - 1) = 10 of `after`.\n const utime = Number(after[11]);\n const stime = Number(after[12]);\n if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;\n const ticksPerSec = (globalThis as { Bun?: { clockTicksPerSecond?: number } }).Bun?.clockTicksPerSecond ?? 100;\n const cpuMs = ((utime + stime) / ticksPerSec) * 1000;\n const match = statusText.match(/^VmRSS:\\s+(\\d+)\\s+kB/m);\n const rssBytes = match && match[1] ? Number(match[1]) * 1024 : 0;\n return { cpuMs, rssBytes };\n } catch {\n return null;\n }\n};\n\nconst defaultSpawn = (command: readonly string[]): SpawnFn => async ({\n cwd,\n env,\n onLogLine,\n key,\n}) => {\n const child = Bun.spawn({\n cmd: [...command],\n cwd,\n env,\n stderr: \"pipe\",\n stdout: \"pipe\",\n });\n\n const readStream = (\n stream: ReadableStream<Uint8Array> | undefined | null,\n label: \"stdout\" | \"stderr\",\n ): void => {\n if (stream === undefined || stream === null) return;\n void (async () => {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n const splitKey = `${child.pid}:${label}`;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n const text = decoder.decode(value, { stream: true });\n for (const line of splitLines(splitKey, text)) {\n onLogLine({\n at: Date.now(),\n key,\n line,\n pid: child.pid,\n stream: label,\n });\n }\n }\n } catch {\n /* stream errored on child exit; nothing to surface */\n }\n })();\n };\n readStream(child.stdout as ReadableStream<Uint8Array> | undefined, \"stdout\");\n readStream(child.stderr as ReadableStream<Uint8Array> | undefined, \"stderr\");\n\n return child;\n};\n\nexport const createRuntime = (options: RuntimeOptions): Runtime => {\n const source = options.source;\n const idleAfterMs = options.idleAfterMs ?? 5 * 60 * 1000;\n const maxConcurrent = options.maxConcurrent ?? 100;\n const sweepIntervalMs = options.sweepIntervalMs ?? 10_000;\n const observeIntervalMs = options.observeIntervalMs ?? 30_000;\n const readiness = options.readiness ?? defaultReadiness;\n const command = options.command ?? [\"bun\", \"run\", \"start\"];\n const spawn = options.spawn ?? defaultSpawn(command);\n const onMetrics = options.onMetrics;\n const onLog = options.onLog;\n const onTransition = options.onTransition;\n const backoffOptions: Required<SpawnBackoff> = {\n baseMs: options.backoff?.baseMs ?? 1_000,\n maxFailures: options.backoff?.maxFailures ?? 10,\n maxMs: options.backoff?.maxMs ?? 60_000,\n };\n\n const entries = new Map<string, Entry>();\n const backoffs = new Map<string, BackoffState>();\n /** Pending exit reasons keyed by child pid — read by the .then exit handler. */\n const exitReasons = new Map<number, ExitReason>();\n let sweepTimer: ReturnType<typeof setInterval> | undefined;\n let lastObserveAt = 0;\n let disposed = false;\n let draining = false;\n\n const emitMetric = (event: RuntimeMetricEvent): void => {\n if (onMetrics === undefined) return;\n try {\n onMetrics(event);\n } catch {\n /* observational only */\n }\n };\n const emitTransition = (event: RuntimeTransitionEvent): void => {\n if (onTransition === undefined) return;\n try {\n onTransition(event);\n } catch {\n /* observational only */\n }\n };\n const emitLog = (event: RuntimeLogEvent): void => {\n if (onLog === undefined) return;\n try {\n onLog(event);\n } catch {\n /* observational only */\n }\n };\n\n const tenantCwd = (key: string): string => {\n if (source.kind === \"directory\") {\n return `${source.root}/${key}`;\n }\n throw new Error(\n `Unsupported tenant source kind: ${(source as { kind: string }).kind}`,\n );\n };\n\n const killChildWithReason = async (entry: Entry, reason: ExitReason): Promise<void> => {\n const child = entry.child;\n if (child === null) return;\n entry.pendingExitReason = reason;\n exitReasons.set(child.pid, reason);\n try {\n child.kill();\n } catch {\n /* already dead */\n }\n try {\n await child.exited;\n } catch {\n /* ignore */\n }\n };\n\n const removeEntry = async (key: string, entry: Entry, reason: ExitReason): Promise<void> => {\n entries.delete(key);\n await killChildWithReason(entry, reason);\n };\n\n const recordBackoff = (key: string, error: unknown): void => {\n const prev = backoffs.get(key);\n const attempt = (prev?.attempt ?? 0) + 1;\n const wait = Math.min(backoffOptions.maxMs, backoffOptions.baseMs * 2 ** (attempt - 1));\n const message = error instanceof Error ? error.message : String(error);\n backoffs.set(key, { attempt, lastError: message, retryAt: Date.now() + wait });\n };\n\n const observeRunning = async (): Promise<void> => {\n if (!isLinux || observeIntervalMs <= 0 || onMetrics === undefined) return;\n const now = Date.now();\n if (now - lastObserveAt < observeIntervalMs) return;\n lastObserveAt = now;\n for (const [key, entry] of entries) {\n if (entry.tenant === null) continue;\n const stats = await readProcStats(entry.tenant.pid);\n if (stats === null) continue;\n emitMetric({\n at: now,\n cpuMs: stats.cpuMs,\n key,\n pid: entry.tenant.pid,\n rssBytes: stats.rssBytes,\n type: \"observation\",\n });\n }\n };\n\n const startSweepIfNeeded = (): void => {\n if (sweepTimer !== undefined || disposed) return;\n sweepTimer = setInterval(() => {\n if (disposed) return;\n const now = Date.now();\n for (const [key, entry] of entries) {\n if (entry.tenant === null) continue;\n if (idleAfterMs <= 0) continue;\n const idleMs = now - entry.tenant.lastTouchedAt;\n if (idleMs >= idleAfterMs) {\n emitTransition({\n idleMs,\n key,\n pid: entry.tenant.pid,\n reason: \"idle-threshold\",\n type: \"idle-kill\",\n });\n void removeEntry(key, entry, \"idle-killed\").catch(() => {});\n }\n }\n void observeRunning().catch(() => {});\n if (entries.size === 0 && sweepTimer !== undefined) {\n clearInterval(sweepTimer);\n sweepTimer = undefined;\n }\n }, sweepIntervalMs);\n if (typeof sweepTimer === \"object\" && sweepTimer !== null) {\n (sweepTimer as { unref?: () => void }).unref?.();\n }\n };\n\n const evictLruIfNeeded = (): void => {\n if (entries.size < maxConcurrent) return;\n let oldestKey: string | undefined;\n let oldestEntry: Entry | undefined;\n for (const [key, entry] of entries) {\n if (entry.tenant === null) continue;\n if (\n oldestEntry === undefined ||\n oldestEntry.tenant === null ||\n entry.tenant.lastTouchedAt < oldestEntry.tenant.lastTouchedAt\n ) {\n oldestKey = key;\n oldestEntry = entry;\n }\n }\n if (oldestKey !== undefined && oldestEntry !== undefined && oldestEntry.tenant !== null) {\n emitTransition({\n key: oldestKey,\n pid: oldestEntry.tenant.pid,\n reason: \"max-concurrent\",\n type: \"lru-evict\",\n });\n void removeEntry(oldestKey, oldestEntry, \"lru-evicted\").catch(() => {});\n }\n };\n\n const spawnFresh = async (key: string): Promise<Tenant> => {\n if (disposed) throw new Error(\"runtime has been disposed\");\n if (draining) throw new Error(\"runtime is draining; ensure() refused\");\n evictLruIfNeeded();\n\n const port = allocateEphemeralPort();\n const startedAt = Date.now();\n const cwd = tenantCwd(key);\n const env: Record<string, string> = {\n ...(process.env as Record<string, string>),\n NODE_ENV: \"production\",\n PORT: String(port),\n };\n\n let child: Subprocess;\n try {\n child = await spawn({\n cwd,\n env,\n key,\n onLogLine: emitLog,\n });\n } catch (error) {\n entries.delete(key);\n recordBackoff(key, error);\n throw error;\n }\n\n emitTransition({ key, pid: child.pid, port, type: \"spawn\" });\n\n // Reap the entry when the process exits. We capture the entry's\n // `pendingExitReason` if some code path set one; otherwise classify\n // by exit code.\n void child.exited\n .then((exitCode) => {\n const stashed = exitReasons.get(child.pid);\n const reason: ExitReason =\n stashed ?? (exitCode === 0 ? \"exited-clean\" : \"crashed\");\n exitReasons.delete(child.pid);\n emitTransition({\n exitCode: exitCode ?? null,\n key,\n pid: child.pid,\n reason,\n type: \"exit\",\n });\n const current = entries.get(key);\n if (current !== undefined && current.child === child) {\n entries.delete(key);\n }\n })\n .catch(() => {});\n\n try {\n await readiness({ key, port, startedAt });\n } catch (error) {\n const entry = entries.get(key);\n if (entry !== undefined) {\n entry.pendingExitReason = \"readiness-timeout\";\n }\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n entries.delete(key);\n recordBackoff(key, error);\n throw error;\n }\n\n const tenant: Tenant = {\n key,\n lastTouchedAt: Date.now(),\n pid: child.pid,\n port,\n startedAt,\n };\n const entry = entries.get(key);\n if (entry !== undefined) {\n entry.tenant = tenant;\n entry.child = child;\n entry.pending = null;\n }\n backoffs.delete(key);\n const durationMs = Date.now() - startedAt;\n emitMetric({\n durationMs,\n key,\n pid: child.pid,\n port,\n type: \"spawn\",\n });\n emitTransition({\n durationMs,\n key,\n pid: child.pid,\n port,\n type: \"ready\",\n });\n startSweepIfNeeded();\n return tenant;\n };\n\n const checkBackoff = (key: string): void => {\n const state = backoffs.get(key);\n if (state === undefined) return;\n if (state.attempt >= backoffOptions.maxFailures) {\n throw new Error(\n `Tenant \"${key}\" exceeded ${backoffOptions.maxFailures} consecutive spawn failures; clearBackoff() to retry. Last error: ${state.lastError}`,\n );\n }\n const remaining = state.retryAt - Date.now();\n if (remaining > 0) {\n emitTransition({\n attempt: state.attempt,\n key,\n retryAfterMs: remaining,\n type: \"backoff\",\n });\n throw new Error(\n `Tenant \"${key}\" is backing off after ${state.attempt} failure(s); retry in ${remaining}ms. Last error: ${state.lastError}`,\n );\n }\n };\n\n return {\n async ensure(key) {\n if (disposed) throw new Error(\"runtime has been disposed\");\n const existing = entries.get(key);\n if (existing !== undefined) {\n if (existing.tenant !== null) {\n existing.tenant.lastTouchedAt = Date.now();\n return existing.tenant;\n }\n if (existing.pending !== null) {\n return existing.pending;\n }\n }\n // From here we'd spawn a fresh process — drain only refuses NEW spawns.\n if (draining) throw new Error(\"runtime is draining; ensure() refused\");\n checkBackoff(key);\n const fresh: Entry = {\n child: null,\n key,\n pending: null,\n pendingExitReason: null,\n tenant: null,\n };\n const promise = spawnFresh(key);\n fresh.pending = promise;\n entries.set(key, fresh);\n return promise;\n },\n\n touch(key) {\n const entry = entries.get(key);\n if (entry === undefined || entry.tenant === null) return;\n entry.tenant.lastTouchedAt = Date.now();\n },\n\n stats() {\n let running = 0;\n for (const entry of entries.values()) {\n if (entry.tenant !== null) running += 1;\n }\n return { backoff: backoffs.size, draining, running, total: entries.size };\n },\n\n async kill(key) {\n const entry = entries.get(key);\n if (entry === undefined) return;\n await removeEntry(key, entry, \"killed\");\n },\n\n async restart(key) {\n if (disposed) throw new Error(\"runtime has been disposed\");\n const entry = entries.get(key);\n if (entry !== undefined) {\n await removeEntry(key, entry, \"restarted\");\n }\n // Same single-flight contract as ensure().\n const fresh: Entry = {\n child: null,\n key,\n pending: null,\n pendingExitReason: null,\n tenant: null,\n };\n const promise = spawnFresh(key);\n fresh.pending = promise;\n entries.set(key, fresh);\n return promise;\n },\n\n clearBackoff(key) {\n backoffs.delete(key);\n },\n\n drain() {\n if (draining) return;\n draining = true;\n emitTransition({ reason: \"drain-requested\", type: \"drain\" });\n },\n\n async dispose() {\n if (disposed) return;\n disposed = true;\n if (sweepTimer !== undefined) {\n clearInterval(sweepTimer);\n sweepTimer = undefined;\n }\n const snapshot = [...entries.entries()];\n entries.clear();\n await Promise.all(snapshot.map(([_key, entry]) => killChildWithReason(entry, \"disposed\")));\n },\n };\n};\n"
|
|
5
|
+
"// @bun\n// src/index.ts\nvar SpanKind = {\n INTERNAL: 0,\n SERVER: 1,\n CLIENT: 2,\n PRODUCER: 3,\n CONSUMER: 4\n};\nvar SpanStatusCode = {\n UNSET: 0,\n OK: 1,\n ERROR: 2\n};\nvar NOOP_SPAN_CONTEXT = {\n spanId: \"0000000000000000\",\n traceFlags: 0,\n traceId: \"00000000000000000000000000000000\"\n};\nvar noopSpan = {\n addEvent: () => noopSpan,\n end: () => {},\n isRecording: () => false,\n recordException: () => {},\n setAttribute: () => noopSpan,\n setAttributes: () => noopSpan,\n setStatus: () => noopSpan,\n spanContext: () => NOOP_SPAN_CONTEXT,\n updateName: () => noopSpan\n};\nvar createNoopSpan = () => noopSpan;\nvar startActiveSpanNoop = (_name, optionsOrFn, maybeFn) => {\n const fn = typeof optionsOrFn === \"function\" ? optionsOrFn : maybeFn;\n return fn(noopSpan);\n};\nvar noopTracer = {\n startActiveSpan: startActiveSpanNoop,\n startSpan: () => noopSpan\n};\nvar createNoopTracer = () => noopTracer;\nvar createNoopTracerProvider = () => ({\n getTracer: () => noopTracer\n});\nvar tracerOrNoop = (provider, name, version) => provider !== undefined ? provider.getTracer(name, version) : noopTracer;\nvar ABS_ATTRS = {\n tenant: \"abs.tenant\",\n shardId: \"abs.shard.id\",\n engineId: \"abs.engine.id\",\n collection: \"abs.collection\",\n mutation: \"abs.mutation\",\n mutationAttempt: \"abs.mutation.attempt\",\n subscriptionId: \"abs.subscription.id\",\n batchSize: \"abs.batch.size\",\n clusterMessageOrigin: \"abs.cluster.origin\",\n jobId: \"abs.job.id\",\n jobKind: \"abs.job.kind\",\n jobAttempt: \"abs.job.attempt\",\n jobMaxAttempts: \"abs.job.max_attempts\",\n workerId: \"abs.worker.id\",\n runtimeKey: \"abs.runtime.key\",\n runtimePid: \"abs.runtime.pid\",\n runtimePort: \"abs.runtime.port\",\n runtimeExitReason: \"abs.runtime.exit_reason\",\n runtimeReadinessMs: \"abs.runtime.readiness_ms\",\n routeShard: \"abs.route.shard\",\n routeDecision: \"abs.route.decision\",\n secretName: \"abs.secret.name\",\n secretFingerprint: \"abs.secret.fingerprint\",\n auditKind: \"abs.audit.kind\"\n};\nvar withSpan = async (tracer, name, options, fn) => tracer.startActiveSpan(name, options, async (span) => {\n try {\n const result = await fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error)\n });\n span.recordException(error);\n throw error;\n } finally {\n span.end();\n }\n});\nvar withSpanSync = (tracer, name, options, fn) => tracer.startActiveSpan(name, options, (span) => {\n try {\n const result = fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error)\n });\n span.recordException(error);\n throw error;\n } finally {\n span.end();\n }\n});\nexport {\n withSpanSync,\n withSpan,\n tracerOrNoop,\n createNoopTracerProvider,\n createNoopTracer,\n createNoopSpan,\n SpanStatusCode,\n SpanKind,\n ABS_ATTRS\n};\n\n//# debugId=3038092490E875A764756E2164756E21\n//# sourceMappingURL=index.js.map\n",
|
|
6
|
+
"/**\n * `@absolutejs/runtime` — multi-tenant Bun runtime substrate.\n *\n * Wraps Bun's `spawn` so that \"run this tenant's `bun run start` inside\n * a hibernating, metric-emitting child process\" is one function call.\n * Built for PaaS providers that want to host many small Bun apps under\n * one host process.\n *\n * Architectural role: SB-6's `@absolutejs/runtime` library. Consumers\n * include the hosted `absolutejs.ai` PaaS (eventual) and anyone else\n * who needs the same shape. Stays decoupled from `@absolutejs/sync`\n * and `@absolutejs/isolated-jsc` — those libraries solve different\n * layers of the same stack.\n *\n * Hibernation strategy (per STRATEGY-CLOUD.md §9.5): idle-kill at the\n * process layer. Bun has no shipped process-level snapshot/resume\n * primitive as of 2026-05-29. When that primitive lands we'll add an\n * opt-in `hibernate: 'process-snapshot'` mode and keep idle-kill as\n * the default.\n *\n * @example\n * ```ts\n * import { createRuntime } from '@absolutejs/runtime';\n *\n * const runtime = createRuntime({\n * source: { kind: 'directory', root: '/srv/tenants' },\n * idleAfterMs: 5 * 60 * 1000, // 5 min\n * maxConcurrent: 100,\n * onMetrics: (event) => prometheus.observe(event),\n * onLog: (event) => loki.write(event),\n * observeIntervalMs: 30_000,\n * });\n *\n * // First call: spawns `bun run start` in /srv/tenants/tenant-42,\n * // injects PORT, waits for readiness, returns the bound port.\n * const tenant = await runtime.ensure('tenant-42');\n * fetch(`http://localhost:${tenant.port}/`);\n *\n * // Subsequent calls reuse the running process.\n * runtime.touch('tenant-42'); // bump idle clock\n *\n * runtime.stats(); // { running, total, draining }\n * await runtime.dispose();\n * ```\n */\n\nimport type { Subprocess } from \"bun\";\nimport {\n ABS_ATTRS,\n tracerOrNoop,\n type TracerProvider as TelemetryTracerProvider,\n type Span as TelemetrySpan\n} from \"@absolutejs/telemetry\";\n\nexport type TenantSource =\n | { kind: \"directory\"; root: string };\n\n/** Identity of a single tenant process at a point in time. */\nexport type Tenant = {\n /** The key the consumer used to address this tenant. */\n key: string;\n /** The port the child process bound to, discovered after readiness. */\n port: number;\n /** OS process id. */\n pid: number;\n /** Wall-clock when the child was spawned. */\n startedAt: number;\n /** Last time the consumer marked this tenant active. */\n lastTouchedAt: number;\n};\n\nexport type RuntimeMetricEvent =\n | {\n type: \"spawn\";\n key: string;\n pid: number;\n port: number;\n durationMs: number;\n }\n | {\n /** Periodic observation emitted by the sweeper (Linux-only; see `observeIntervalMs`). */\n type: \"observation\";\n key: string;\n pid: number;\n /** Cumulative CPU ms used by the child since spawn, derived from `/proc/<pid>/stat`. */\n cpuMs: number;\n /** Resident set size in bytes, derived from `/proc/<pid>/status` VmRSS. */\n rssBytes: number;\n at: number;\n };\n\nexport type RuntimeLogEvent = {\n key: string;\n pid: number;\n stream: \"stdout\" | \"stderr\";\n /** A single line of output (newline-terminated lines are split client-side). */\n line: string;\n at: number;\n};\n\n/**\n * Why a tenant process ended. Used by `RuntimeTransitionEvent` of type\n * `'exit'` to give the consumer enough info to charge or restart correctly:\n * - `crashed` — the process exited on its own with a non-zero code\n * - `exited-clean` — the process exited 0 (probably a graceful self-stop)\n * - `idle-killed` — the sweeper killed it after `idleAfterMs` with no `touch()`\n * - `lru-evicted` — `ensure()` for a new tenant evicted this one\n * - `killed` — explicit `runtime.kill(key)` call\n * - `readiness-timeout` — readiness check failed; we killed during spawn\n * - `disposed` — `runtime.dispose()` killed it\n * - `restarted` — `runtime.restart(key)` killed it on purpose\n */\nexport type ExitReason =\n | \"crashed\"\n | \"exited-clean\"\n | \"idle-killed\"\n | \"lru-evicted\"\n | \"killed\"\n | \"readiness-timeout\"\n | \"disposed\"\n | \"restarted\";\n\nexport type RuntimeTransitionEvent =\n | { type: \"spawn\"; key: string; pid: number; port: number }\n | {\n type: \"ready\";\n key: string;\n pid: number;\n port: number;\n durationMs: number;\n }\n | {\n type: \"idle-kill\";\n key: string;\n pid: number;\n reason: \"idle-threshold\";\n idleMs: number;\n }\n | { type: \"lru-evict\"; key: string; pid: number; reason: \"max-concurrent\" }\n | {\n type: \"exit\";\n key: string;\n pid: number;\n exitCode: number | null;\n reason: ExitReason;\n }\n | {\n /** A spawn was deferred because the key is in the back-off window after a failure. */\n type: \"backoff\";\n key: string;\n attempt: number;\n retryAfterMs: number;\n }\n | { type: \"drain\"; reason: \"drain-requested\" };\n\nexport type ReadinessCheck = (args: {\n key: string;\n port: number;\n /** Wall-clock spawn time so the check can compute its own elapsed. */\n startedAt: number;\n}) => Promise<boolean>;\n\nexport type SpawnFn = (args: {\n cwd: string;\n env: Record<string, string>;\n onLogLine: (event: RuntimeLogEvent) => void;\n key: string;\n}) => Promise<Subprocess>;\n\nexport type SpawnBackoff = {\n /** First retry waits this long. Default 1000 ms. */\n baseMs?: number;\n /** Maximum back-off (the cap on the doubled wait). Default 60_000 ms. */\n maxMs?: number;\n /** After this many consecutive failures, `ensure()` throws immediately for this key until reset. Default 10. */\n maxFailures?: number;\n};\n\n/** Options for {@link createRuntime}. */\nexport type RuntimeOptions = {\n /** Where to find tenant project directories. */\n source: TenantSource;\n /**\n * Kill the child process after this many ms with no `touch()` call.\n * Default 5 minutes. Set to `0` to disable idle-kill (only LRU and\n * explicit `kill()` shed processes then).\n */\n idleAfterMs?: number;\n /**\n * Max concurrent tenant processes. When a fresh `ensure()` would\n * push past this, the least-recently-touched process is killed\n * first. Default 100.\n */\n maxConcurrent?: number;\n /**\n * Background sweep interval. Default 10_000 ms. The sweep runs only\n * when the runtime is non-empty and is unrefed so the process can\n * exit cleanly.\n */\n sweepIntervalMs?: number;\n /**\n * How often the sweeper observes CPU + RSS per running tenant. Default\n * 30_000 ms. Set to `0` to disable; observation only works on Linux\n * (`/proc/<pid>` derived) — the sweeper silently skips on other OSes.\n * Output goes to `onMetrics` as `{ type: 'observation', ... }`.\n */\n observeIntervalMs?: number;\n /**\n * Override the readiness check. Default: HTTP GET to\n * `http://127.0.0.1:${port}/` with a 100ms retry loop, give up after\n * 30s with a `Tenant readiness timed out` error.\n */\n readiness?: ReadinessCheck;\n /**\n * Override how a child process is spawned. Default: `Bun.spawn` with\n * `['bun', 'run', 'start']`, stdio piped through `onLogLine`, env\n * carrying `PORT=${allocatedPort}` and `NODE_ENV=production`. Tests\n * use this to inject a fixture without writing to disk.\n */\n spawn?: SpawnFn;\n /** Exponential-backoff policy for consecutive spawn failures. */\n backoff?: SpawnBackoff;\n /** Operational metrics — spawn/ready durations + periodic observations. */\n onMetrics?: (event: RuntimeMetricEvent) => void;\n /** stdout/stderr stream. Bounded internally; backpressure to the host. */\n onLog?: (event: RuntimeLogEvent) => void;\n /** Lifecycle events — spawn/ready/idle-kill/lru-evict/exit/backoff/drain. */\n onTransition?: (event: RuntimeTransitionEvent) => void;\n /**\n * Command to run when spawning. Default `['bun', 'run', 'start']`.\n * Tests use this to point at a fixture script.\n */\n command?: readonly string[];\n /**\n * Optional OpenTelemetry tracer provider. When set, each\n * `ensure()` / `restart()` is wrapped in a `runtime.spawn` span\n * with `abs.tenant`, `abs.runtime.pid`, `abs.runtime.port`,\n * `abs.runtime.readiness_ms`, and (on exit) `abs.runtime.exit_reason`\n * attributes. The span is the ROOT of the customer's trace —\n * everything inside the spawned tenant inherits the active context\n * if it propagates correctly (W3C trace context via env / headers).\n * When omitted, all tracing is a zero-allocation noop. Added in 0.3.0.\n *\n * Pass any `@opentelemetry/api`-compatible `TracerProvider`. See\n * `@absolutejs/telemetry` for the type shape — runtime re-uses its\n * helpers but doesn't peer-dep `@opentelemetry/api` directly.\n */\n tracerProvider?: TelemetryTracerProvider;\n};\n\nexport type RuntimeStats = {\n running: number;\n total: number;\n /** True when the runtime is draining — refusing new ensure() calls. */\n draining: boolean;\n /** Number of keys currently in the back-off window. */\n backoff: number;\n};\n\n/**\n * Operator-shaped metrics returned by {@link Runtime.metrics}. Combines\n * the point-in-time {@link RuntimeStats} fields with cumulative counters\n * since `createRuntime()`. Survives `dispose()` so post-shutdown\n * introspection still reads the totals. Added in 0.2.0.\n *\n * - `totalSpawns` — successful `spawn()` calls (failed spawns hit\n * `recordBackoff` instead and bump `totalBackoffEntries`).\n * - `totalExits` — exits keyed by `ExitReason`. A climbing\n * `crashed` means a tenant is unhealthy; `idle-killed` is the\n * expected steady-state for hibernation; `lru-evicted` means the\n * `maxRunning` cap is biting.\n * - `totalBackoffEntries` — `recordBackoff` calls. Distinct from the\n * point-in-time `backoff` (current keys in window): a single key\n * that fails 5 times bumps `totalBackoffEntries` by 5 but only\n * contributes 1 to `backoff`.\n * - `lastSpawnMs` — wall-clock of the most recent spawn. A climb\n * here is the operator's \"is spawning getting slow\" signal.\n */\nexport type RuntimeMetrics = RuntimeStats & {\n totalSpawns: number;\n totalExits: Record<ExitReason, number>;\n totalBackoffEntries: number;\n lastSpawnMs: number;\n};\n\nexport type Runtime = {\n /**\n * Resolve `key` to a running tenant. Spawns if not running, waits\n * for readiness, returns the live {@link Tenant} including the bound\n * `port`. Concurrent calls to the same key share a single-flight\n * spawn — N callers don't create N processes.\n *\n * If `key` is in the back-off window after a recent failure, throws\n * immediately (without spawning). Use `clearBackoff(key)` to retry early.\n */\n ensure: (key: string) => Promise<Tenant>;\n /**\n * Mark `key` as active right now. Bumps the idle clock; the next\n * sweep won't consider it for idle-kill until `idleAfterMs` again.\n * Cheap; safe to call before/after each request you route to this\n * tenant.\n */\n touch: (key: string) => void;\n /** Synchronous point-in-time snapshot — back-compat alias of metrics() shape (subset). */\n stats: () => RuntimeStats;\n /**\n * Operator-shaped point-in-time + cumulative metrics (since\n * `createRuntime()`). Use this — `stats()` is kept for back-compat\n * but doesn't carry the cumulative counters. Added in 0.2.0.\n */\n metrics: () => RuntimeMetrics;\n /** Force-kill `key`. No-op if not running. */\n kill: (key: string) => Promise<void>;\n /**\n * Kill `key` and respawn it. Used by deploys to swap to a new release\n * after the `current` symlink has been updated. Concurrent restart\n * calls for the same key share a single-flight respawn.\n */\n restart: (key: string) => Promise<Tenant>;\n /** Forget any consecutive-failure state for `key`. Next `ensure()` retries immediately. */\n clearBackoff: (key: string) => void;\n /**\n * Begin draining: refuse new `ensure()` calls (they throw immediately).\n * In-flight spawns and existing tenants are untouched — wait for\n * `stats().running` to reach 0, or call `dispose()` for hard shutdown.\n * Useful for graceful shard shutdown before a host reboot.\n */\n drain: () => void;\n /** Dispose every running child + stop the sweep. Idempotent. */\n dispose: () => Promise<void>;\n};\n\n/* ─── internals ──────────────────────────────────────────────────────── */\n\ntype Entry = {\n key: string;\n /** Set while the spawn is in-flight; concurrent ensure() callers await it. */\n pending: Promise<Tenant> | null;\n tenant: Tenant | null;\n child: Subprocess | null;\n /** Set by code that's about to kill the child, read by the exit handler. */\n pendingExitReason: ExitReason | null;\n};\n\ntype BackoffState = {\n attempt: number;\n retryAt: number;\n lastError: string;\n};\n\nconst defaultReadiness: ReadinessCheck = async ({ port, startedAt }) => {\n const deadline = startedAt + 30_000;\n while (Date.now() < deadline) {\n try {\n const res = await fetch(`http://127.0.0.1:${port}/`, {\n signal: AbortSignal.timeout(2_000),\n });\n void res;\n return true;\n } catch {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n throw new Error(\"Tenant readiness timed out after 30s\");\n};\n\nconst allocateEphemeralPort = (): number => {\n const server = Bun.listen({\n hostname: \"127.0.0.1\",\n port: 0,\n socket: {\n data: () => {},\n open: () => {},\n },\n });\n const port = server.port;\n server.stop(true);\n return port;\n};\n\nconst splitLines = (() => {\n const remainders = new Map<string, string>();\n return (key: string, chunk: string): string[] => {\n const prior = remainders.get(key) ?? \"\";\n const combined = prior + chunk;\n const parts = combined.split(\"\\n\");\n remainders.set(key, parts.pop() ?? \"\");\n return parts;\n };\n})();\n\nconst isLinux = typeof process !== \"undefined\" && process.platform === \"linux\";\n\n/**\n * Read CPU + RSS for a pid from `/proc`. Returns `null` if the pid is gone\n * or we're not on Linux. The math: `utime + stime` from `/proc/<pid>/stat`\n * is in clock ticks; we divide by `Bun.clockTicksPerSecond` (or fall back\n * to 100 — the universal default for Linux kernels).\n */\nconst readProcStats = async (pid: number): Promise<{ cpuMs: number; rssBytes: number } | null> => {\n if (!isLinux) return null;\n try {\n const statText = await Bun.file(`/proc/${pid}/stat`).text();\n const statusText = await Bun.file(`/proc/${pid}/status`).text();\n // /proc/<pid>/stat: ... (comm) ... and utime/stime are fields 14 and 15\n // counting from 1; but `comm` can contain spaces, so we anchor on the\n // closing paren.\n const closeParen = statText.lastIndexOf(\")\");\n if (closeParen === -1) return null;\n const after = statText.slice(closeParen + 2).split(\" \");\n // After (comm), the fields are: state ppid pgrp session ... utime stime ...\n // utime = field 14 of the whole line = index (14 - 3 - 1) = 10 of `after`.\n const utime = Number(after[11]);\n const stime = Number(after[12]);\n if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;\n const ticksPerSec = (globalThis as { Bun?: { clockTicksPerSecond?: number } }).Bun?.clockTicksPerSecond ?? 100;\n const cpuMs = ((utime + stime) / ticksPerSec) * 1000;\n const match = statusText.match(/^VmRSS:\\s+(\\d+)\\s+kB/m);\n const rssBytes = match && match[1] ? Number(match[1]) * 1024 : 0;\n return { cpuMs, rssBytes };\n } catch {\n return null;\n }\n};\n\nconst defaultSpawn = (command: readonly string[]): SpawnFn => async ({\n cwd,\n env,\n onLogLine,\n key,\n}) => {\n const child = Bun.spawn({\n cmd: [...command],\n cwd,\n env,\n stderr: \"pipe\",\n stdout: \"pipe\",\n });\n\n const readStream = (\n stream: ReadableStream<Uint8Array> | undefined | null,\n label: \"stdout\" | \"stderr\",\n ): void => {\n if (stream === undefined || stream === null) return;\n void (async () => {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n const splitKey = `${child.pid}:${label}`;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n const text = decoder.decode(value, { stream: true });\n for (const line of splitLines(splitKey, text)) {\n onLogLine({\n at: Date.now(),\n key,\n line,\n pid: child.pid,\n stream: label,\n });\n }\n }\n } catch {\n /* stream errored on child exit; nothing to surface */\n }\n })();\n };\n readStream(child.stdout as ReadableStream<Uint8Array> | undefined, \"stdout\");\n readStream(child.stderr as ReadableStream<Uint8Array> | undefined, \"stderr\");\n\n return child;\n};\n\nexport const createRuntime = (options: RuntimeOptions): Runtime => {\n const source = options.source;\n const idleAfterMs = options.idleAfterMs ?? 5 * 60 * 1000;\n const maxConcurrent = options.maxConcurrent ?? 100;\n const sweepIntervalMs = options.sweepIntervalMs ?? 10_000;\n const observeIntervalMs = options.observeIntervalMs ?? 30_000;\n const readiness = options.readiness ?? defaultReadiness;\n const command = options.command ?? [\"bun\", \"run\", \"start\"];\n const spawn = options.spawn ?? defaultSpawn(command);\n const onMetrics = options.onMetrics;\n const onLog = options.onLog;\n const onTransition = options.onTransition;\n const backoffOptions: Required<SpawnBackoff> = {\n baseMs: options.backoff?.baseMs ?? 1_000,\n maxFailures: options.backoff?.maxFailures ?? 10,\n maxMs: options.backoff?.maxMs ?? 60_000,\n };\n\n const entries = new Map<string, Entry>();\n const backoffs = new Map<string, BackoffState>();\n /** Pending exit reasons keyed by child pid — read by the .then exit handler. */\n const exitReasons = new Map<number, ExitReason>();\n let sweepTimer: ReturnType<typeof setInterval> | undefined;\n let lastObserveAt = 0;\n let disposed = false;\n let draining = false;\n // 0.3.0: OTel tracer + per-pid open spans. spawnFresh opens a\n // `runtime.spawn` span; the exit handler reads it back to close\n // with abs.runtime.exit_reason. Noop when tracerProvider unset.\n const tracer = tracerOrNoop(options.tracerProvider, \"@absolutejs/runtime\");\n const openSpawnSpans = new Map<number, TelemetrySpan>();\n // 0.2.0: cumulative operator counters surfaced via metrics(). Survive\n // dispose() so post-shutdown introspection still reads totals.\n let totalSpawns = 0;\n const totalExits: Record<ExitReason, number> = {\n 'crashed': 0,\n 'exited-clean': 0,\n 'idle-killed': 0,\n 'lru-evicted': 0,\n 'killed': 0,\n 'readiness-timeout': 0,\n 'disposed': 0,\n 'restarted': 0\n };\n let totalBackoffEntries = 0;\n let lastSpawnMs = 0;\n\n const emitMetric = (event: RuntimeMetricEvent): void => {\n if (onMetrics === undefined) return;\n try {\n onMetrics(event);\n } catch {\n /* observational only */\n }\n };\n const emitTransition = (event: RuntimeTransitionEvent): void => {\n if (onTransition === undefined) return;\n try {\n onTransition(event);\n } catch {\n /* observational only */\n }\n };\n const emitLog = (event: RuntimeLogEvent): void => {\n if (onLog === undefined) return;\n try {\n onLog(event);\n } catch {\n /* observational only */\n }\n };\n\n const tenantCwd = (key: string): string => {\n if (source.kind === \"directory\") {\n return `${source.root}/${key}`;\n }\n throw new Error(\n `Unsupported tenant source kind: ${(source as { kind: string }).kind}`,\n );\n };\n\n const killChildWithReason = async (entry: Entry, reason: ExitReason): Promise<void> => {\n const child = entry.child;\n if (child === null) return;\n entry.pendingExitReason = reason;\n exitReasons.set(child.pid, reason);\n try {\n child.kill();\n } catch {\n /* already dead */\n }\n try {\n await child.exited;\n } catch {\n /* ignore */\n }\n };\n\n const removeEntry = async (key: string, entry: Entry, reason: ExitReason): Promise<void> => {\n entries.delete(key);\n await killChildWithReason(entry, reason);\n };\n\n const recordBackoff = (key: string, error: unknown): void => {\n const prev = backoffs.get(key);\n const attempt = (prev?.attempt ?? 0) + 1;\n const wait = Math.min(backoffOptions.maxMs, backoffOptions.baseMs * 2 ** (attempt - 1));\n const message = error instanceof Error ? error.message : String(error);\n backoffs.set(key, { attempt, lastError: message, retryAt: Date.now() + wait });\n totalBackoffEntries += 1;\n };\n\n const observeRunning = async (): Promise<void> => {\n if (!isLinux || observeIntervalMs <= 0 || onMetrics === undefined) return;\n const now = Date.now();\n if (now - lastObserveAt < observeIntervalMs) return;\n lastObserveAt = now;\n for (const [key, entry] of entries) {\n if (entry.tenant === null) continue;\n const stats = await readProcStats(entry.tenant.pid);\n if (stats === null) continue;\n emitMetric({\n at: now,\n cpuMs: stats.cpuMs,\n key,\n pid: entry.tenant.pid,\n rssBytes: stats.rssBytes,\n type: \"observation\",\n });\n }\n };\n\n const startSweepIfNeeded = (): void => {\n if (sweepTimer !== undefined || disposed) return;\n sweepTimer = setInterval(() => {\n if (disposed) return;\n const now = Date.now();\n for (const [key, entry] of entries) {\n if (entry.tenant === null) continue;\n if (idleAfterMs <= 0) continue;\n const idleMs = now - entry.tenant.lastTouchedAt;\n if (idleMs >= idleAfterMs) {\n emitTransition({\n idleMs,\n key,\n pid: entry.tenant.pid,\n reason: \"idle-threshold\",\n type: \"idle-kill\",\n });\n void removeEntry(key, entry, \"idle-killed\").catch(() => {});\n }\n }\n void observeRunning().catch(() => {});\n if (entries.size === 0 && sweepTimer !== undefined) {\n clearInterval(sweepTimer);\n sweepTimer = undefined;\n }\n }, sweepIntervalMs);\n if (typeof sweepTimer === \"object\" && sweepTimer !== null) {\n (sweepTimer as { unref?: () => void }).unref?.();\n }\n };\n\n const evictLruIfNeeded = (): void => {\n if (entries.size < maxConcurrent) return;\n let oldestKey: string | undefined;\n let oldestEntry: Entry | undefined;\n for (const [key, entry] of entries) {\n if (entry.tenant === null) continue;\n if (\n oldestEntry === undefined ||\n oldestEntry.tenant === null ||\n entry.tenant.lastTouchedAt < oldestEntry.tenant.lastTouchedAt\n ) {\n oldestKey = key;\n oldestEntry = entry;\n }\n }\n if (oldestKey !== undefined && oldestEntry !== undefined && oldestEntry.tenant !== null) {\n emitTransition({\n key: oldestKey,\n pid: oldestEntry.tenant.pid,\n reason: \"max-concurrent\",\n type: \"lru-evict\",\n });\n void removeEntry(oldestKey, oldestEntry, \"lru-evicted\").catch(() => {});\n }\n };\n\n const spawnFresh = async (key: string): Promise<Tenant> => {\n if (disposed) throw new Error(\"runtime has been disposed\");\n if (draining) throw new Error(\"runtime is draining; ensure() refused\");\n evictLruIfNeeded();\n\n const port = allocateEphemeralPort();\n const startedAt = Date.now();\n const cwd = tenantCwd(key);\n const env: Record<string, string> = {\n ...(process.env as Record<string, string>),\n NODE_ENV: \"production\",\n PORT: String(port),\n };\n\n let child: Subprocess;\n const spawnStart = Date.now();\n try {\n child = await spawn({\n cwd,\n env,\n key,\n onLogLine: emitLog,\n });\n } catch (error) {\n entries.delete(key);\n recordBackoff(key, error);\n throw error;\n }\n\n totalSpawns += 1;\n lastSpawnMs = Date.now() - spawnStart;\n\n // 0.3.0: open the per-tenant span. Lifetime = from spawn-success\n // through process exit (closed by the exit handler below). The\n // span is the ROOT of the customer's trace for this tenant\n // session — sync mutations / queue jobs / etc. nest under it if\n // OTel context propagates correctly across the process boundary.\n const spawnSpan = tracer.startSpan(\"runtime.spawn\", {\n attributes: {\n [ABS_ATTRS.runtimeKey]: key,\n [ABS_ATTRS.runtimePid]: child.pid,\n [ABS_ATTRS.runtimePort]: port,\n [ABS_ATTRS.runtimeReadinessMs]: lastSpawnMs,\n },\n });\n openSpawnSpans.set(child.pid, spawnSpan);\n\n emitTransition({ key, pid: child.pid, port, type: \"spawn\" });\n\n // Reap the entry when the process exits. We capture the entry's\n // `pendingExitReason` if some code path set one; otherwise classify\n // by exit code.\n void child.exited\n .then((exitCode) => {\n const stashed = exitReasons.get(child.pid);\n const reason: ExitReason =\n stashed ?? (exitCode === 0 ? \"exited-clean\" : \"crashed\");\n exitReasons.delete(child.pid);\n totalExits[reason] += 1;\n // 0.3.0: close the tenant's spawn span on exit. Status maps\n // the reason: exited-clean / idle-killed / lru-evicted /\n // disposed / restarted are OK (planned); crashed /\n // readiness-timeout / killed are ERROR.\n const exitSpan = openSpawnSpans.get(child.pid);\n if (exitSpan !== undefined) {\n openSpawnSpans.delete(child.pid);\n exitSpan.setAttribute(ABS_ATTRS.runtimeExitReason, reason);\n if (exitCode !== null && exitCode !== undefined) {\n exitSpan.setAttribute(\"abs.runtime.exit_code\", exitCode);\n }\n const ok =\n reason === \"exited-clean\" ||\n reason === \"idle-killed\" ||\n reason === \"lru-evicted\" ||\n reason === \"disposed\" ||\n reason === \"restarted\";\n exitSpan.setStatus({ code: ok ? 1 /* OK */ : 2 /* ERROR */ });\n exitSpan.end();\n }\n emitTransition({\n exitCode: exitCode ?? null,\n key,\n pid: child.pid,\n reason,\n type: \"exit\",\n });\n const current = entries.get(key);\n if (current !== undefined && current.child === child) {\n entries.delete(key);\n }\n })\n .catch(() => {});\n\n try {\n await readiness({ key, port, startedAt });\n } catch (error) {\n const entry = entries.get(key);\n if (entry !== undefined) {\n entry.pendingExitReason = \"readiness-timeout\";\n }\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n entries.delete(key);\n recordBackoff(key, error);\n throw error;\n }\n\n const tenant: Tenant = {\n key,\n lastTouchedAt: Date.now(),\n pid: child.pid,\n port,\n startedAt,\n };\n const entry = entries.get(key);\n if (entry !== undefined) {\n entry.tenant = tenant;\n entry.child = child;\n entry.pending = null;\n }\n backoffs.delete(key);\n const durationMs = Date.now() - startedAt;\n emitMetric({\n durationMs,\n key,\n pid: child.pid,\n port,\n type: \"spawn\",\n });\n emitTransition({\n durationMs,\n key,\n pid: child.pid,\n port,\n type: \"ready\",\n });\n startSweepIfNeeded();\n return tenant;\n };\n\n const checkBackoff = (key: string): void => {\n const state = backoffs.get(key);\n if (state === undefined) return;\n if (state.attempt >= backoffOptions.maxFailures) {\n throw new Error(\n `Tenant \"${key}\" exceeded ${backoffOptions.maxFailures} consecutive spawn failures; clearBackoff() to retry. Last error: ${state.lastError}`,\n );\n }\n const remaining = state.retryAt - Date.now();\n if (remaining > 0) {\n emitTransition({\n attempt: state.attempt,\n key,\n retryAfterMs: remaining,\n type: \"backoff\",\n });\n throw new Error(\n `Tenant \"${key}\" is backing off after ${state.attempt} failure(s); retry in ${remaining}ms. Last error: ${state.lastError}`,\n );\n }\n };\n\n return {\n async ensure(key) {\n if (disposed) throw new Error(\"runtime has been disposed\");\n const existing = entries.get(key);\n if (existing !== undefined) {\n if (existing.tenant !== null) {\n existing.tenant.lastTouchedAt = Date.now();\n return existing.tenant;\n }\n if (existing.pending !== null) {\n return existing.pending;\n }\n }\n // From here we'd spawn a fresh process — drain only refuses NEW spawns.\n if (draining) throw new Error(\"runtime is draining; ensure() refused\");\n checkBackoff(key);\n const fresh: Entry = {\n child: null,\n key,\n pending: null,\n pendingExitReason: null,\n tenant: null,\n };\n const promise = spawnFresh(key);\n fresh.pending = promise;\n entries.set(key, fresh);\n return promise;\n },\n\n touch(key) {\n const entry = entries.get(key);\n if (entry === undefined || entry.tenant === null) return;\n entry.tenant.lastTouchedAt = Date.now();\n },\n\n stats() {\n let running = 0;\n for (const entry of entries.values()) {\n if (entry.tenant !== null) running += 1;\n }\n return { backoff: backoffs.size, draining, running, total: entries.size };\n },\n\n metrics() {\n let running = 0;\n for (const entry of entries.values()) {\n if (entry.tenant !== null) running += 1;\n }\n return {\n backoff: backoffs.size,\n draining,\n lastSpawnMs,\n running,\n total: entries.size,\n totalBackoffEntries,\n totalExits: { ...totalExits },\n totalSpawns\n };\n },\n\n async kill(key) {\n const entry = entries.get(key);\n if (entry === undefined) return;\n await removeEntry(key, entry, \"killed\");\n },\n\n async restart(key) {\n if (disposed) throw new Error(\"runtime has been disposed\");\n const entry = entries.get(key);\n if (entry !== undefined) {\n await removeEntry(key, entry, \"restarted\");\n }\n // Same single-flight contract as ensure().\n const fresh: Entry = {\n child: null,\n key,\n pending: null,\n pendingExitReason: null,\n tenant: null,\n };\n const promise = spawnFresh(key);\n fresh.pending = promise;\n entries.set(key, fresh);\n return promise;\n },\n\n clearBackoff(key) {\n backoffs.delete(key);\n },\n\n drain() {\n if (draining) return;\n draining = true;\n emitTransition({ reason: \"drain-requested\", type: \"drain\" });\n },\n\n async dispose() {\n if (disposed) return;\n disposed = true;\n if (sweepTimer !== undefined) {\n clearInterval(sweepTimer);\n sweepTimer = undefined;\n }\n const snapshot = [...entries.entries()];\n entries.clear();\n await Promise.all(snapshot.map(([_key, entry]) => killChildWithReason(entry, \"disposed\")));\n },\n };\n};\n"
|
|
6
7
|
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "FD61CCA67627CB9764756E2164756E21",
|
|
9
10
|
"names": []
|
|
10
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Multi-tenant Bun runtime substrate for PaaS providers — spawn isolated child Bun processes per tenant, idle-kill, metric emit. The library SB-6 surfaces between isolated-jsc + sync and the hosted product downstream.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,10 +32,14 @@
|
|
|
32
32
|
"check:package": "bun run typecheck && bun run build && bun run test",
|
|
33
33
|
"release": "bun run format && bun run check:package && bun publish"
|
|
34
34
|
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@absolutejs/telemetry": "^0.0.2"
|
|
37
|
+
},
|
|
35
38
|
"peerDependencies": {
|
|
36
39
|
"bun-types": ">= 1.3.0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
42
|
+
"@absolutejs/telemetry": "^0.0.2",
|
|
39
43
|
"@types/bun": "1.3.14",
|
|
40
44
|
"prettier": "3.5.3",
|
|
41
45
|
"typescript": "5.8.3"
|