@btravstack/core 0.17.0 → 0.18.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.cjs CHANGED
@@ -5,6 +5,7 @@ let node_timers_promises = require("node:timers/promises");
5
5
  let _btravstack_config = require("@btravstack/config");
6
6
  let node_http = require("node:http");
7
7
  let node_async_hooks = require("node:async_hooks");
8
+ let node_crypto = require("node:crypto");
8
9
  //#region src/instrumentation.ts
9
10
  /**
10
11
  * The set port a package contributes its instrumentation to.
@@ -432,10 +433,22 @@ const installUncaughtHandlers = (onUncaught) => {
432
433
  const storage = new node_async_hooks.AsyncLocalStorage();
433
434
  const runWithUnit = (record, fn) => storage.run(record, fn);
434
435
  const currentUnit = () => storage.getStore();
435
- let counter = 0;
436
- const nextId = () => {
437
- counter += 1;
438
- return `u${counter}`;
436
+ const outcomes = /* @__PURE__ */ new WeakMap();
437
+ const settleUnit = (record, outcome) => {
438
+ outcomes.set(record, outcome);
439
+ };
440
+ /**
441
+ * How the current unit's work settled, for the code that runs AFTER it: a
442
+ * unit module's `onStop`, which tears down inside the record but is handed
443
+ * only its own service. `undefined` outside a unit and while the work is still
444
+ * running. Whether the unit was aborted is the record's own `signal`.
445
+ *
446
+ * It is the kernel's outcome, not the transport's: an HTTP unit that answered
447
+ * a `500` still settled `ok`, since the kernel never maps a status.
448
+ */
449
+ const unitOutcome = () => {
450
+ const unit = currentUnit();
451
+ return unit === void 0 ? void 0 : outcomes.get(unit);
439
452
  };
440
453
  const createUnitRegistry = () => {
441
454
  const open = /* @__PURE__ */ new Set();
@@ -451,7 +464,7 @@ const createUnitRegistry = () => {
451
464
  const controller = new AbortController();
452
465
  open.add(controller);
453
466
  const record = {
454
- unitId: nextId(),
467
+ unitId: (0, node_crypto.randomUUID)(),
455
468
  traceId: meta.traceId ?? meta.id,
456
469
  tenantId: meta.tenantId,
457
470
  signal: controller.signal
@@ -741,13 +754,18 @@ const start = (module, options = {}) => {
741
754
  }).get();
742
755
  return (0, unthrown.fromSafePromise)(ready.promise);
743
756
  };
757
+ const record = currentUnit();
744
758
  const outcome = (async () => {
759
+ let settledAs = "error";
745
760
  try {
746
- return await work({
761
+ const result = await work({
747
762
  ctx: runtimeCtx,
748
763
  fork
749
764
  }, signal);
765
+ if (result.isOk()) settledAs = "ok";
766
+ return result;
750
767
  } finally {
768
+ if (record !== void 0) settleUnit(record, settledAs);
751
769
  hasSettled = true;
752
770
  settled.resolve();
753
771
  if (closing !== void 0) await closing;
@@ -976,4 +994,5 @@ exports.start = start;
976
994
  exports.stderrSink = stderrSink;
977
995
  exports.systemClock = systemClock;
978
996
  exports.traceIdOfTraceparent = traceIdOfTraceparent;
997
+ exports.unitOutcome = unitOutcome;
979
998
  exports.withUnitRecord = withUnitRecord;
package/dist/index.d.cts CHANGED
@@ -352,6 +352,18 @@ type UnitRecord = {
352
352
  readonly signal: AbortSignal;
353
353
  };
354
354
  export declare const currentUnit: () => UnitRecord | undefined;
355
+ /** How a unit's work settled: `ok`, or `error` for an `Err`, a `Defect` and a throw alike. */
356
+ type UnitOutcome = "ok" | "error";
357
+ /**
358
+ * How the current unit's work settled, for the code that runs AFTER it: a
359
+ * unit module's `onStop`, which tears down inside the record but is handed
360
+ * only its own service. `undefined` outside a unit and while the work is still
361
+ * running. Whether the unit was aborted is the record's own `signal`.
362
+ *
363
+ * It is the kernel's outcome, not the transport's: an HTTP unit that answered
364
+ * a `500` still settled `ok`, since the kernel never maps a status.
365
+ */
366
+ export declare const unitOutcome: () => UnitOutcome | undefined;
355
367
  /**
356
368
  * What a runtime says about one piece of work as it submits it. `kind` is the
357
369
  * category (`"http"`, `"tick"`, `"job"`); `id` identifies **this** unit.
@@ -363,7 +375,8 @@ export declare const currentUnit: () => UnitRecord | undefined;
363
375
  * silently defeats the ambient record. A route template is a `kind`.
364
376
  *
365
377
  * The kernel cannot check this, so uniqueness is the runtime's to guarantee.
366
- * What it does guarantee is {@link UnitRecord}'s `unitId`, minted per unit;
378
+ * What it does guarantee is {@link UnitRecord}'s `unitId`, a UUID minted per
379
+ * unit, so no two units share one across replicas either;
367
380
  * `traceId` is the CORRELATION id, which is why it is the one a runtime may
368
381
  * supply — it carries an id from outside the process.
369
382
  */
@@ -942,4 +955,4 @@ export declare const noObserverMember: Provider<Observers, never, never> & {
942
955
  readonly port: typeof Observers;
943
956
  };
944
957
  //#endregion
945
- export type { AnyUnitModule, Attributes, Clock, ComponentHealth, Counter, DrainReport, EventSink, ExitReport, HealthCheck, HealthReport, Histogram, InstrumentationLoader, KernelEvent, Level, LoggerService, MeterService, Operation, Phase, Refuse, RunUnit, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, Serving, Settle, Settled, Span, SpanStatusCode, StartGate, StartOptions, TeardownError, TracerService, UnitExportsOf, UnitGate, UnitHost, UnitMeta, UnitNeedsOf, UnitRecord, UnitRecordOf, UnitRegistry, UnitWork };
958
+ export type { AnyUnitModule, Attributes, Clock, ComponentHealth, Counter, DrainReport, EventSink, ExitReport, HealthCheck, HealthReport, Histogram, InstrumentationLoader, KernelEvent, Level, LoggerService, MeterService, Operation, Phase, Refuse, RunUnit, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, Serving, Settle, Settled, Span, SpanStatusCode, StartGate, StartOptions, TeardownError, TracerService, UnitExportsOf, UnitGate, UnitHost, UnitMeta, UnitNeedsOf, UnitOutcome, UnitRecord, UnitRecordOf, UnitRegistry, UnitWork };
package/dist/index.d.mts CHANGED
@@ -352,6 +352,18 @@ type UnitRecord = {
352
352
  readonly signal: AbortSignal;
353
353
  };
354
354
  export declare const currentUnit: () => UnitRecord | undefined;
355
+ /** How a unit's work settled: `ok`, or `error` for an `Err`, a `Defect` and a throw alike. */
356
+ type UnitOutcome = "ok" | "error";
357
+ /**
358
+ * How the current unit's work settled, for the code that runs AFTER it: a
359
+ * unit module's `onStop`, which tears down inside the record but is handed
360
+ * only its own service. `undefined` outside a unit and while the work is still
361
+ * running. Whether the unit was aborted is the record's own `signal`.
362
+ *
363
+ * It is the kernel's outcome, not the transport's: an HTTP unit that answered
364
+ * a `500` still settled `ok`, since the kernel never maps a status.
365
+ */
366
+ export declare const unitOutcome: () => UnitOutcome | undefined;
355
367
  /**
356
368
  * What a runtime says about one piece of work as it submits it. `kind` is the
357
369
  * category (`"http"`, `"tick"`, `"job"`); `id` identifies **this** unit.
@@ -363,7 +375,8 @@ export declare const currentUnit: () => UnitRecord | undefined;
363
375
  * silently defeats the ambient record. A route template is a `kind`.
364
376
  *
365
377
  * The kernel cannot check this, so uniqueness is the runtime's to guarantee.
366
- * What it does guarantee is {@link UnitRecord}'s `unitId`, minted per unit;
378
+ * What it does guarantee is {@link UnitRecord}'s `unitId`, a UUID minted per
379
+ * unit, so no two units share one across replicas either;
367
380
  * `traceId` is the CORRELATION id, which is why it is the one a runtime may
368
381
  * supply — it carries an id from outside the process.
369
382
  */
@@ -942,4 +955,4 @@ export declare const noObserverMember: Provider<Observers, never, never> & {
942
955
  readonly port: typeof Observers;
943
956
  };
944
957
  //#endregion
945
- export type { AnyUnitModule, Attributes, Clock, ComponentHealth, Counter, DrainReport, EventSink, ExitReport, HealthCheck, HealthReport, Histogram, InstrumentationLoader, KernelEvent, Level, LoggerService, MeterService, Operation, Phase, Refuse, RunUnit, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, Serving, Settle, Settled, Span, SpanStatusCode, StartGate, StartOptions, TeardownError, TracerService, UnitExportsOf, UnitGate, UnitHost, UnitMeta, UnitNeedsOf, UnitRecord, UnitRecordOf, UnitRegistry, UnitWork };
958
+ export type { AnyUnitModule, Attributes, Clock, ComponentHealth, Counter, DrainReport, EventSink, ExitReport, HealthCheck, HealthReport, Histogram, InstrumentationLoader, KernelEvent, Level, LoggerService, MeterService, Operation, Phase, Refuse, RunUnit, RunningApp, Runtime, RuntimeHost, RuntimeInfoOf, Serving, Settle, Settled, Span, SpanStatusCode, StartGate, StartOptions, TeardownError, TracerService, UnitExportsOf, UnitGate, UnitHost, UnitMeta, UnitNeedsOf, UnitOutcome, UnitRecord, UnitRecordOf, UnitRegistry, UnitWork };
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import { setTimeout as setTimeout$1 } from "node:timers/promises";
4
4
  import { Config, ConfigInvalid, Env } from "@btravstack/config";
5
5
  import { createServer } from "node:http";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
+ import { randomUUID } from "node:crypto";
7
8
  //#region src/instrumentation.ts
8
9
  /**
9
10
  * The set port a package contributes its instrumentation to.
@@ -431,10 +432,22 @@ const installUncaughtHandlers = (onUncaught) => {
431
432
  const storage = new AsyncLocalStorage();
432
433
  const runWithUnit = (record, fn) => storage.run(record, fn);
433
434
  const currentUnit = () => storage.getStore();
434
- let counter = 0;
435
- const nextId = () => {
436
- counter += 1;
437
- return `u${counter}`;
435
+ const outcomes = /* @__PURE__ */ new WeakMap();
436
+ const settleUnit = (record, outcome) => {
437
+ outcomes.set(record, outcome);
438
+ };
439
+ /**
440
+ * How the current unit's work settled, for the code that runs AFTER it: a
441
+ * unit module's `onStop`, which tears down inside the record but is handed
442
+ * only its own service. `undefined` outside a unit and while the work is still
443
+ * running. Whether the unit was aborted is the record's own `signal`.
444
+ *
445
+ * It is the kernel's outcome, not the transport's: an HTTP unit that answered
446
+ * a `500` still settled `ok`, since the kernel never maps a status.
447
+ */
448
+ const unitOutcome = () => {
449
+ const unit = currentUnit();
450
+ return unit === void 0 ? void 0 : outcomes.get(unit);
438
451
  };
439
452
  const createUnitRegistry = () => {
440
453
  const open = /* @__PURE__ */ new Set();
@@ -450,7 +463,7 @@ const createUnitRegistry = () => {
450
463
  const controller = new AbortController();
451
464
  open.add(controller);
452
465
  const record = {
453
- unitId: nextId(),
466
+ unitId: randomUUID(),
454
467
  traceId: meta.traceId ?? meta.id,
455
468
  tenantId: meta.tenantId,
456
469
  signal: controller.signal
@@ -740,13 +753,18 @@ const start = (module, options = {}) => {
740
753
  }).get();
741
754
  return fromSafePromise(ready.promise);
742
755
  };
756
+ const record = currentUnit();
743
757
  const outcome = (async () => {
758
+ let settledAs = "error";
744
759
  try {
745
- return await work({
760
+ const result = await work({
746
761
  ctx: runtimeCtx,
747
762
  fork
748
763
  }, signal);
764
+ if (result.isOk()) settledAs = "ok";
765
+ return result;
749
766
  } finally {
767
+ if (record !== void 0) settleUnit(record, settledAs);
750
768
  hasSettled = true;
751
769
  settled.resolve();
752
770
  if (closing !== void 0) await closing;
@@ -949,4 +967,4 @@ const noObserverMember = Provider.member(Observers)({
949
967
  value: noObserver
950
968
  });
951
969
  //#endregion
952
- export { HealthCheckFailed, HealthChecks, Instrumentations, LEVELS, Logger, Meter, Observers, RuntimePort, RuntimeStartFailed, SPAN_STATUS, Tracer, composeByPrefix, currentUnit, dispatchUnit, mintPiece, noObserver, noObserverMember, observe, observed, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock, traceIdOfTraceparent, withUnitRecord };
970
+ export { HealthCheckFailed, HealthChecks, Instrumentations, LEVELS, Logger, Meter, Observers, RuntimePort, RuntimeStartFailed, SPAN_STATUS, Tracer, composeByPrefix, currentUnit, dispatchUnit, mintPiece, noObserver, noObserverMember, observe, observed, releasedBy, runHealthChecks, runMain, start, stderrSink, systemClock, traceIdOfTraceparent, unitOutcome, withUnitRecord };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/core",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "The application kernel: boot a di module into a running process, and stop it without losing work",
5
5
  "keywords": [
6
6
  "application",
@@ -45,20 +45,21 @@
45
45
  "./package.json": "./package.json"
46
46
  },
47
47
  "devDependencies": {
48
- "@btravstack/config": "0.17.0",
49
- "@btravstack/di": "0.17.0",
48
+ "@btravstack/config": "0.18.0",
49
+ "@btravstack/di": "0.18.0",
50
50
  "@btravstack/tsconfig": "0.4.0",
51
51
  "@types/node": "26.6.4",
52
52
  "@unthrown/vitest": "5.12.0",
53
53
  "@vitest/coverage-v8": "5.0.3",
54
54
  "tsdown": "0.23.0",
55
55
  "typescript": "7.0.2",
56
+ "typescript-consumer": "npm:typescript@5.9.3",
56
57
  "unthrown": "5.12.0",
57
58
  "vitest": "5.0.3"
58
59
  },
59
60
  "peerDependencies": {
60
- "@btravstack/config": "^0.17.0",
61
- "@btravstack/di": "^0.17.0",
61
+ "@btravstack/config": "^0.18.0",
62
+ "@btravstack/di": "^0.18.0",
62
63
  "unthrown": "^5.0.0"
63
64
  },
64
65
  "engines": {
@@ -69,6 +70,6 @@
69
70
  "dev": "tsdown src/index.ts --format cjs,esm --dts --watch --tsconfig tsconfig.build.json",
70
71
  "generate": "node ../../docs/scripts/extract-doc-samples.ts --group core --out src/generated/doc-samples",
71
72
  "test": "vitest run --coverage",
72
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test-d.json"
73
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test-d.json && node ./node_modules/typescript-consumer/bin/tsc --noEmit -p tsconfig.test-d.json"
73
74
  }
74
75
  }