@btravstack/core 0.28.0 → 0.30.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
@@ -124,14 +124,18 @@ const noObserver = () => () => {};
124
124
  * matters because the settling call sites are error paths, where a `tap` and a
125
125
  * `tapFailure` on the same chain, or a retry, is exactly the shape that fires
126
126
  * twice and doubles a failure count.
127
+ *
128
+ * A broken observer is dropped independently, on startup and settlement. No
129
+ * recursive reporting is attempted: telemetry must never alter the operation
130
+ * or prevent another observer from settling.
127
131
  */
128
132
  const observe = (observers, operation) => {
129
- const settlers = observers.map((observer) => observer(operation));
133
+ const settlers = observers.map((observer) => (0, unthrown.fromSafeThrowable)(observer)(operation).recoverDefect(() => (0, unthrown.Ok)(noObserver())).get());
130
134
  let done = false;
131
135
  return (settled) => {
132
136
  if (done) return;
133
137
  done = true;
134
- for (const settle of settlers) settle(settled);
138
+ for (const settle of settlers) (0, unthrown.fromSafeThrowable)(settle)(settled).recoverDefect(() => (0, unthrown.Ok)()).get();
135
139
  };
136
140
  };
137
141
  /**
@@ -143,14 +147,21 @@ const observe = (observers, operation) => {
143
147
  *
144
148
  * `settled` replaces either default where a starter has more to say: `ok`
145
149
  * receives the value (a cache reports a hit or a miss), `failure` the
146
- * `FailureView` (a store reports a missing object as an ordinary answer).
150
+ * `FailureView` (a store reports a missing object as an ordinary answer). A
151
+ * throwing shaping hook is dropped and the ordinary settlement is used.
147
152
  */
148
153
  const observed = (observers, operation, call, settled = {}) => {
149
154
  const settle = observe(observers, operation);
150
- return call().tap((value) => settle(settled.ok?.(value) ?? { outcome: "ok" })).tapFailure((failure) => settle(settled.failure?.(failure) ?? {
151
- outcome: "error",
152
- cause: failure.tag === "Err" ? failure.error : failure.cause
153
- }));
155
+ return (0, unthrown.OkAsync)().flatMap(call).tap((value) => {
156
+ const rendered = (0, unthrown.fromSafeThrowable)(() => settled.ok?.(value))().recoverDefect(() => (0, unthrown.Ok)(void 0)).get();
157
+ settle(rendered ?? { outcome: "ok" });
158
+ }).tapFailure((failure) => {
159
+ const rendered = (0, unthrown.fromSafeThrowable)(() => settled.failure?.(failure))().recoverDefect(() => (0, unthrown.Ok)(void 0)).get();
160
+ settle(rendered ?? {
161
+ outcome: "error",
162
+ cause: failure.tag === "Err" ? failure.error : failure.cause
163
+ });
164
+ });
154
165
  };
155
166
  //#endregion
156
167
  //#region src/clock.ts
@@ -520,6 +531,7 @@ const createUnitRegistry = () => {
520
531
  } finally {
521
532
  open.delete(controller);
522
533
  closedCount += 1;
534
+ controller.abort(new DOMException("The unit has settled", "AbortError"));
523
535
  settleIfIdle();
524
536
  }
525
537
  })).flatMap((result) => result);
package/dist/index.d.cts CHANGED
@@ -309,6 +309,10 @@ export declare const noObserver: () => Settle;
309
309
  * matters because the settling call sites are error paths, where a `tap` and a
310
310
  * `tapFailure` on the same chain, or a retry, is exactly the shape that fires
311
311
  * twice and doubles a failure count.
312
+ *
313
+ * A broken observer is dropped independently, on startup and settlement. No
314
+ * recursive reporting is attempted: telemetry must never alter the operation
315
+ * or prevent another observer from settling.
312
316
  */
313
317
  export declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
314
318
  /**
@@ -320,7 +324,8 @@ export declare const observe: (observers: readonly ((operation: Operation) => Se
320
324
  *
321
325
  * `settled` replaces either default where a starter has more to say: `ok`
322
326
  * receives the value (a cache reports a hit or a miss), `failure` the
323
- * `FailureView` (a store reports a missing object as an ordinary answer).
327
+ * `FailureView` (a store reports a missing object as an ordinary answer). A
328
+ * throwing shaping hook is dropped and the ordinary settlement is used.
324
329
  */
325
330
  export declare const observed: <T, E>(observers: readonly ((operation: Operation) => Settle)[], operation: Operation, call: () => AsyncResult<T, E>, settled?: {
326
331
  readonly ok?: (value: T) => Settled;
@@ -340,7 +345,11 @@ export declare const systemClock: Clock;
340
345
  * record of **data about this unit**, and never a service.
341
346
  *
342
347
  * `signal` is the same `AbortSignal` the work callback receives — aborted at
343
- * the drain deadline, or at once on a path that skips the drain. It is here
348
+ * the drain deadline, at once on a path that skips the drain, and once the
349
+ * unit has settled, so work that outlives its unit — an HTTP handler whose
350
+ * client disconnected — is told to stop. The settle abort's `reason` is an
351
+ * `AbortError` `DOMException` whose message is `"The unit has settled"`; the
352
+ * kernel's own aborts keep the default reason. It is here
344
353
  * because the callback is not always where the work is: a middleware-shaped
345
354
  * runtime opens the unit around a call whose arguments it does not own. A
346
355
  * transport's own cancellation is a different clock, not this one.
@@ -481,6 +490,11 @@ export declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
481
490
  * moment it holds the unit's input; the kernel closes that scope when the
482
491
  * unit settles, after the response is flushed. Work that subscribes to an
483
492
  * event after a `fork` must first check whether it already fired.
493
+ *
494
+ * `signal` aborts at the drain deadline, at once when the drain is skipped,
495
+ * and once the unit settles — after its scope has closed — so work the
496
+ * runtime left running past the unit, such as an HTTP handler whose client
497
+ * disconnected, is told to stop. Only the first two count as `abandoned`.
484
498
  */
485
499
  type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (unit: UnitHost<Resolves>, signal: AbortSignal) => ReturnType<UnitWork<T, E>>) => AsyncResult<T, E>;
486
500
  /**
package/dist/index.d.mts CHANGED
@@ -309,6 +309,10 @@ export declare const noObserver: () => Settle;
309
309
  * matters because the settling call sites are error paths, where a `tap` and a
310
310
  * `tapFailure` on the same chain, or a retry, is exactly the shape that fires
311
311
  * twice and doubles a failure count.
312
+ *
313
+ * A broken observer is dropped independently, on startup and settlement. No
314
+ * recursive reporting is attempted: telemetry must never alter the operation
315
+ * or prevent another observer from settling.
312
316
  */
313
317
  export declare const observe: (observers: readonly ((operation: Operation) => Settle)[], operation: Operation) => Settle;
314
318
  /**
@@ -320,7 +324,8 @@ export declare const observe: (observers: readonly ((operation: Operation) => Se
320
324
  *
321
325
  * `settled` replaces either default where a starter has more to say: `ok`
322
326
  * receives the value (a cache reports a hit or a miss), `failure` the
323
- * `FailureView` (a store reports a missing object as an ordinary answer).
327
+ * `FailureView` (a store reports a missing object as an ordinary answer). A
328
+ * throwing shaping hook is dropped and the ordinary settlement is used.
324
329
  */
325
330
  export declare const observed: <T, E>(observers: readonly ((operation: Operation) => Settle)[], operation: Operation, call: () => AsyncResult<T, E>, settled?: {
326
331
  readonly ok?: (value: T) => Settled;
@@ -340,7 +345,11 @@ export declare const systemClock: Clock;
340
345
  * record of **data about this unit**, and never a service.
341
346
  *
342
347
  * `signal` is the same `AbortSignal` the work callback receives — aborted at
343
- * the drain deadline, or at once on a path that skips the drain. It is here
348
+ * the drain deadline, at once on a path that skips the drain, and once the
349
+ * unit has settled, so work that outlives its unit — an HTTP handler whose
350
+ * client disconnected — is told to stop. The settle abort's `reason` is an
351
+ * `AbortError` `DOMException` whose message is `"The unit has settled"`; the
352
+ * kernel's own aborts keep the default reason. It is here
344
353
  * because the callback is not always where the work is: a middleware-shaped
345
354
  * runtime opens the unit around a call whose arguments it does not own. A
346
355
  * transport's own cancellation is a different clock, not this one.
@@ -481,6 +490,11 @@ export declare class RuntimeStartFailed extends RuntimeStartFailed_base<{
481
490
  * moment it holds the unit's input; the kernel closes that scope when the
482
491
  * unit settles, after the response is flushed. Work that subscribes to an
483
492
  * event after a `fork` must first check whether it already fired.
493
+ *
494
+ * `signal` aborts at the drain deadline, at once when the drain is skipped,
495
+ * and once the unit settles — after its scope has closed — so work the
496
+ * runtime left running past the unit, such as an HTTP handler whose client
497
+ * disconnected, is told to stop. Only the first two count as `abandoned`.
484
498
  */
485
499
  type RunUnit<Resolves extends AnyPort> = <T, E>(meta: UnitMeta, work: (unit: UnitHost<Resolves>, signal: AbortSignal) => ReturnType<UnitWork<T, E>>) => AsyncResult<T, E>;
486
500
  /**
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Module, Port, Provider } from "@btravstack/di";
2
- import { Err, Ok, OkAsync, P, TaggedError, allAsync, fromSafePromise } from "unthrown";
2
+ import { Err, Ok, OkAsync, P, TaggedError, allAsync, fromSafePromise, fromSafeThrowable } from "unthrown";
3
3
  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";
@@ -123,14 +123,18 @@ const noObserver = () => () => {};
123
123
  * matters because the settling call sites are error paths, where a `tap` and a
124
124
  * `tapFailure` on the same chain, or a retry, is exactly the shape that fires
125
125
  * twice and doubles a failure count.
126
+ *
127
+ * A broken observer is dropped independently, on startup and settlement. No
128
+ * recursive reporting is attempted: telemetry must never alter the operation
129
+ * or prevent another observer from settling.
126
130
  */
127
131
  const observe = (observers, operation) => {
128
- const settlers = observers.map((observer) => observer(operation));
132
+ const settlers = observers.map((observer) => fromSafeThrowable(observer)(operation).recoverDefect(() => Ok(noObserver())).get());
129
133
  let done = false;
130
134
  return (settled) => {
131
135
  if (done) return;
132
136
  done = true;
133
- for (const settle of settlers) settle(settled);
137
+ for (const settle of settlers) fromSafeThrowable(settle)(settled).recoverDefect(() => Ok()).get();
134
138
  };
135
139
  };
136
140
  /**
@@ -142,14 +146,21 @@ const observe = (observers, operation) => {
142
146
  *
143
147
  * `settled` replaces either default where a starter has more to say: `ok`
144
148
  * receives the value (a cache reports a hit or a miss), `failure` the
145
- * `FailureView` (a store reports a missing object as an ordinary answer).
149
+ * `FailureView` (a store reports a missing object as an ordinary answer). A
150
+ * throwing shaping hook is dropped and the ordinary settlement is used.
146
151
  */
147
152
  const observed = (observers, operation, call, settled = {}) => {
148
153
  const settle = observe(observers, operation);
149
- return call().tap((value) => settle(settled.ok?.(value) ?? { outcome: "ok" })).tapFailure((failure) => settle(settled.failure?.(failure) ?? {
150
- outcome: "error",
151
- cause: failure.tag === "Err" ? failure.error : failure.cause
152
- }));
154
+ return OkAsync().flatMap(call).tap((value) => {
155
+ const rendered = fromSafeThrowable(() => settled.ok?.(value))().recoverDefect(() => Ok(void 0)).get();
156
+ settle(rendered ?? { outcome: "ok" });
157
+ }).tapFailure((failure) => {
158
+ const rendered = fromSafeThrowable(() => settled.failure?.(failure))().recoverDefect(() => Ok(void 0)).get();
159
+ settle(rendered ?? {
160
+ outcome: "error",
161
+ cause: failure.tag === "Err" ? failure.error : failure.cause
162
+ });
163
+ });
153
164
  };
154
165
  //#endregion
155
166
  //#region src/clock.ts
@@ -519,6 +530,7 @@ const createUnitRegistry = () => {
519
530
  } finally {
520
531
  open.delete(controller);
521
532
  closedCount += 1;
533
+ controller.abort(new DOMException("The unit has settled", "AbortError"));
522
534
  settleIfIdle();
523
535
  }
524
536
  })).flatMap((result) => result);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/core",
3
- "version": "0.28.0",
3
+ "version": "0.30.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,8 +45,8 @@
45
45
  "./package.json": "./package.json"
46
46
  },
47
47
  "devDependencies": {
48
- "@btravstack/config": "0.28.0",
49
- "@btravstack/di": "0.28.0",
48
+ "@btravstack/config": "0.30.0",
49
+ "@btravstack/di": "0.30.0",
50
50
  "@btravstack/tsconfig": "0.4.0",
51
51
  "@stryker-mutator/core": "10.0.0",
52
52
  "@stryker-mutator/vitest-runner": "10.0.0",
@@ -60,8 +60,8 @@
60
60
  "vitest": "5.0.3"
61
61
  },
62
62
  "peerDependencies": {
63
- "@btravstack/config": "^0.28.0",
64
- "@btravstack/di": "^0.28.0",
63
+ "@btravstack/config": "^0.30.0",
64
+ "@btravstack/di": "^0.30.0",
65
65
  "unthrown": "^5.0.0"
66
66
  },
67
67
  "engines": {