@ultimat3/core 29.4.0 → 30.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CLAUDE.md +45 -81
  2. package/README.md +137 -10
  3. package/package.json +19 -3
  4. package/src/address-class.ts +53 -12
  5. package/src/aws-sigv4.ts +104 -8
  6. package/src/breaker.ts +177 -0
  7. package/src/client-dispatch.ts +76 -17
  8. package/src/client-flight.ts +4 -4
  9. package/src/client-paths-pins.ts +92 -0
  10. package/src/client-problem.ts +25 -1
  11. package/src/client-scope.ts +3 -1
  12. package/src/client-stream.ts +106 -0
  13. package/src/client-transport.ts +16 -2
  14. package/src/config-assets.ts +64 -0
  15. package/src/config-client-errors.ts +132 -0
  16. package/src/config-defaults.ts +18 -4
  17. package/src/config-failure-capture.ts +70 -0
  18. package/src/config-health.ts +8 -0
  19. package/src/config-islands.ts +1 -1
  20. package/src/config-keys.ts +2 -0
  21. package/src/config-site.ts +16 -0
  22. package/src/config-time.ts +40 -0
  23. package/src/config.ts +37 -26
  24. package/src/context.ts +92 -10
  25. package/src/core-error-codes.ts +19 -0
  26. package/src/cursor.ts +7 -3
  27. package/src/deadline-signal.ts +57 -0
  28. package/src/declared-errors-openapi.ts +70 -0
  29. package/src/declared-errors-scan.ts +79 -0
  30. package/src/declared-errors.ts +185 -0
  31. package/src/define-error.ts +200 -0
  32. package/src/deprecation.ts +14 -3
  33. package/src/drain-deadline.ts +22 -0
  34. package/src/env.ts +8 -3
  35. package/src/error-fingerprint.ts +95 -0
  36. package/src/error-reporter-chain.ts +45 -0
  37. package/src/error-reporter-sentry.ts +4 -0
  38. package/src/error-reporter.ts +14 -1
  39. package/src/error-status-declared.ts +82 -0
  40. package/src/exercise-errors.ts +41 -0
  41. package/src/exercise-run.ts +176 -0
  42. package/src/exports/error-contract.ts +7 -0
  43. package/src/exports/observability.ts +26 -0
  44. package/src/failure-capture-flush.ts +93 -0
  45. package/src/failure-capture-mask.ts +54 -0
  46. package/src/failure-capture.ts +205 -0
  47. package/src/fill-wire.ts +30 -0
  48. package/src/finite-option.ts +7 -0
  49. package/src/health-disclosure.ts +2 -2
  50. package/src/hmac.ts +47 -0
  51. package/src/index.ts +172 -9
  52. package/src/json-storable.ts +21 -0
  53. package/src/keyed-fingerprint.ts +3 -7
  54. package/src/lifecycle.ts +5 -1
  55. package/src/logger.ts +1 -1
  56. package/src/otlp-metric-exporter.ts +7 -1
  57. package/src/otlp-post.ts +147 -0
  58. package/src/otlp-span-exporter.ts +7 -1
  59. package/src/otlp.ts +4 -37
  60. package/src/outbound-attempt.ts +235 -0
  61. package/src/outbound-dial.ts +103 -0
  62. package/src/outbound-errors.ts +193 -0
  63. package/src/outbound-hops.ts +135 -0
  64. package/src/outbound-read.ts +103 -0
  65. package/src/outbound-subpath.ts +26 -0
  66. package/src/outbound-types.ts +108 -0
  67. package/src/outbound.ts +223 -0
  68. package/src/page-meta.ts +8 -0
  69. package/src/page.ts +9 -0
  70. package/src/record-envelope-openapi.ts +6 -0
  71. package/src/record-envelope.ts +33 -9
  72. package/src/record-sink.ts +13 -0
  73. package/src/redis-client.ts +78 -0
  74. package/src/registrar.ts +11 -0
  75. package/src/render-node.ts +13 -0
  76. package/src/render-nodes.ts +137 -0
  77. package/src/retry-loop.ts +197 -0
  78. package/src/retry.ts +47 -145
  79. package/src/roles.ts +2 -2
  80. package/src/route-path-merge.ts +55 -0
  81. package/src/route-path-template.ts +124 -0
  82. package/src/runtime-metrics.ts +16 -1
  83. package/src/same-origin.ts +18 -2
  84. package/src/session-lost.ts +115 -0
  85. package/src/single-flight.ts +75 -40
  86. package/src/source-mask.ts +36 -2
  87. package/src/tool-description.ts +29 -0
  88. package/src/visible-interval.ts +153 -0
  89. package/src/webhook-signature.ts +9 -11
@@ -26,6 +26,19 @@ export interface SingleFlightOptions {
26
26
  readonly deadlineMs?: number | undefined;
27
27
  /** Injected so a deadline is provable without waiting for one. */
28
28
  readonly schedule?: Scheduler | undefined;
29
+ /**
30
+ * The lifetime of whoever is calling, read at each `run`. Set, a joiner whose leader was aborted
31
+ * and whose own signal is live re-leads with its own `work`: the leader's closed tab is not a
32
+ * failure of the read it joined. Any other rejection is shared, ordinary failures included.
33
+ * Absent means every joiner shares every rejection. Injected, so this file reads no context.
34
+ */
35
+ readonly signal?: (() => AbortSignal | undefined) | undefined;
36
+ /**
37
+ * Told each time `key` stops being held: settle, deadline or `evict`, and not when a load already
38
+ * evicted settles. So a caller keeping state per held key mirrors the map exactly, with no gap
39
+ * before the settle and no entry left behind by a load that never does.
40
+ */
41
+ readonly released?: ((key: string) => void) | undefined;
29
42
  }
30
43
 
31
44
  export interface SingleFlight {
@@ -51,9 +64,10 @@ export interface SingleFlight {
51
64
  evict(key: string): boolean;
52
65
  }
53
66
 
54
- /** The leader's promise, the box its merged context lives in, and its deadline's canceller. */
67
+ /** The leader's promise, its signal, the box its merged context lives in, its deadline's canceller. */
55
68
  interface Flight {
56
69
  readonly running: Promise<unknown>;
70
+ readonly signal: AbortSignal | undefined;
57
71
  readonly shared: { context: unknown };
58
72
  cancelDeadline: () => void;
59
73
  }
@@ -69,12 +83,68 @@ export function singleFlight(options?: SingleFlightOptions): SingleFlight {
69
83
  const inflight = new Map<string, Flight>();
70
84
  const schedule = options?.schedule ?? defaultScheduler;
71
85
  const deadlineMs = options?.deadlineMs;
86
+ const readSignal = options?.signal;
87
+ const released = options?.released;
72
88
 
73
89
  // Identity, never key presence. `inflight.delete(key)` from a settling load drops whatever holds
74
90
  // the key NOW — which, once a deadline can evict, is a different load that has not settled: its
75
91
  // joiners would then be sharing a promise nothing in the map answers for.
76
92
  const evict = (key: string, entry: Flight): void => {
77
- if (inflight.get(key) === entry) inflight.delete(key);
93
+ if (inflight.get(key) !== entry) return;
94
+ inflight.delete(key);
95
+ released?.(key);
96
+ };
97
+
98
+ const run = <T, C = undefined>(
99
+ key: string,
100
+ work: (shared: () => C | undefined) => Promise<T>,
101
+ join?: FlightJoin<C>,
102
+ ): Promise<T> => {
103
+ const joined = inflight.get(key);
104
+ // Two readers of one key asking for two different `T` is a caller bug this cannot see; the
105
+ // value they share is the same object either way, so the cast is the honest one.
106
+ if (joined !== undefined) {
107
+ if (join !== undefined) {
108
+ joined.shared.context = join.merge(joined.shared.context as C, join.context);
109
+ }
110
+ if (readSignal === undefined) return joined.running as Promise<T>;
111
+ const mine = readSignal();
112
+ // Asked at the rejection, not now: either side may yet abort. `settled` was attached at lead,
113
+ // ahead of this, so the key is already free; the first re-lead leads and the rest join it. No
114
+ // cap is needed, as each further round spends one more aborted leader. `catch` runs under the
115
+ // context it was attached in, so the re-lead's `work` is this joiner's, as this joiner.
116
+ return (joined.running as Promise<T>).catch((error: unknown) => {
117
+ if (joined.signal?.aborted !== true || mine?.aborted === true) throw error;
118
+ return run(key, work, join);
119
+ });
120
+ }
121
+
122
+ const shared: { context: unknown } = { context: join?.context };
123
+ // Wrapped so a `work()` that throws SYNCHRONOUSLY still rejects the joiners rather than
124
+ // escaping past the map and leaving no entry to clear.
125
+ const running: Promise<T> = (async () => await work(() => shared.context as C | undefined))();
126
+ const entry: Flight = {
127
+ running,
128
+ signal: readSignal?.(),
129
+ shared,
130
+ cancelDeadline: (): void => undefined,
131
+ };
132
+ inflight.set(key, entry);
133
+
134
+ if (deadlineMs !== undefined) {
135
+ entry.cancelDeadline = schedule(() => {
136
+ evict(key, entry);
137
+ }, deadlineMs);
138
+ }
139
+
140
+ const settled = (): void => {
141
+ entry.cancelDeadline();
142
+ evict(key, entry);
143
+ };
144
+ // A rejected load MUST clear too, or one failure is cached as a permanent rejection.
145
+ void running.then(settled, settled);
146
+
147
+ return running;
78
148
  };
79
149
 
80
150
  return {
@@ -88,45 +158,10 @@ export function singleFlight(options?: SingleFlightOptions): SingleFlight {
88
158
  const held = inflight.get(key);
89
159
  if (held === undefined) return false;
90
160
  held.cancelDeadline();
91
- return inflight.delete(key);
161
+ evict(key, held);
162
+ return true;
92
163
  },
93
164
 
94
- run<T, C = undefined>(
95
- key: string,
96
- work: (shared: () => C | undefined) => Promise<T>,
97
- join?: FlightJoin<C>,
98
- ): Promise<T> {
99
- const joined = inflight.get(key);
100
- // Two readers of one key asking for two different `T` is a caller bug this cannot see; the
101
- // value they share is the same object either way, so the cast is the honest one.
102
- if (joined !== undefined) {
103
- if (join !== undefined) {
104
- joined.shared.context = join.merge(joined.shared.context as C, join.context);
105
- }
106
- return joined.running as Promise<T>;
107
- }
108
-
109
- const shared: { context: unknown } = { context: join?.context };
110
- // Wrapped so a `work()` that throws SYNCHRONOUSLY still rejects the joiners rather than
111
- // escaping past the map and leaving no entry to clear.
112
- const running: Promise<T> = (async () => await work(() => shared.context as C | undefined))();
113
- const entry: Flight = { running, shared, cancelDeadline: (): void => undefined };
114
- inflight.set(key, entry);
115
-
116
- if (deadlineMs !== undefined) {
117
- entry.cancelDeadline = schedule(() => {
118
- evict(key, entry);
119
- }, deadlineMs);
120
- }
121
-
122
- const settled = (): void => {
123
- entry.cancelDeadline();
124
- evict(key, entry);
125
- };
126
- // A rejected load MUST clear too, or one failure is cached as a permanent rejection.
127
- void running.then(settled, settled);
128
-
129
- return running;
130
- },
165
+ run,
131
166
  };
132
167
  }
@@ -147,8 +147,42 @@ function blankRegions(text: string, strings: boolean): string {
147
147
  return out.join('');
148
148
  }
149
149
 
150
+ /**
151
+ * Held units per memo — key plus answer, twice the input — past which it is dropped whole: about
152
+ * 190 MB of RSS. Measured 2026-11-03 on the `errors` step, user CPU / RSS: no memo 32.2 s /
153
+ * 0.7 GB; 32M 21.9 s, too small for the step's working set; 64M 16.5-16.9 s / 0.89-1.15 GB;
154
+ * 128M 15.8 s / 1.14 GB.
155
+ */
156
+ export const MASK_MEMO_CAP = 64 * 1024 * 1024;
157
+
158
+ /**
159
+ * `mask` memoised on its input text. Every guard of the `errors` step re-masked every file: 74,031
160
+ * masks for 11,298 distinct inputs, 42% of its CPU. Cleared whole and not LRU, because a guard
161
+ * walks every file in order and an LRU smaller than the corpus misses on every call.
162
+ */
163
+ export function memoText(
164
+ mask: (text: string) => string,
165
+ cap = MASK_MEMO_CAP,
166
+ ): (text: string) => string {
167
+ const memo = new Map<string, string>();
168
+ let held = 0;
169
+ return (text) => {
170
+ const known = memo.get(text);
171
+ if (known !== undefined) return known;
172
+ const out = mask(text);
173
+ // Key and answer are both held: twice the input, about 2.9 B of RSS per unit measured.
174
+ if (held + 2 * text.length > cap) {
175
+ memo.clear();
176
+ held = 0;
177
+ }
178
+ memo.set(text, out);
179
+ held += 2 * text.length;
180
+ return out;
181
+ };
182
+ }
183
+
150
184
  /** Comments gone, string literals intact — what a scan for declared codes or `t()` keys reads. */
151
- export const stripComments = (text: string): string => blankRegions(text, false);
185
+ export const stripComments = memoText((text) => blankRegions(text, false));
152
186
 
153
187
  /** Comments and string contents gone, delimiters kept — what a scan for code structure reads. */
154
- export const maskLiterals = (text: string): string => blankRegions(text, true);
188
+ export const maskLiterals = memoText((text) => blankRegions(text, true));
@@ -0,0 +1,29 @@
1
+ // The ONE description a projected tool carries: `@ultimat3/mcp`'s, `@ultimat3/ai`'s and
2
+ // `@ultimat3/admin`'s, which the tier table keeps apart, so the rule lives here, never copied.
3
+
4
+ import { describeDeclaredErrors } from './declared-errors';
5
+
6
+ /** What a tool's description is read from — an action's, a query's, or a hand-built primitive's. */
7
+ export interface ToolDescriptionSource {
8
+ readonly name: string;
9
+ readonly description?: string;
10
+ readonly mcp?: { readonly description?: string };
11
+ /** `false` for a read (a query). Absent counts as a write, as `toolFrom` meters it. */
12
+ readonly mutates?: boolean;
13
+ }
14
+
15
+ /**
16
+ * `mcp.description`, else the primitive's own `description`, else a default naming it — a read
17
+ * says it reads, so an agent never plans a write around one — then the declared `errors:`
18
+ * appended (`describeDeclaredErrors`), so an agent plans around a refusal.
19
+ */
20
+ export function toolDescription(source: ToolDescriptionSource, errors?: readonly string[]): string {
21
+ const lead = source.mcp?.description ?? source.description ?? defaultLead(source);
22
+ return describeDeclaredErrors(lead, errors);
23
+ }
24
+
25
+ function defaultLead(source: ToolDescriptionSource): string {
26
+ return source.mutates === false
27
+ ? `Read with the "${source.name}" query.`
28
+ : `Run the "${source.name}" action.`;
29
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * A poll that a hidden tab and a lost session both stop: the ONE timer for what has no changefeed,
3
+ * and for the safety re-read behind an at-most-once channel. Here, not in `@ultimat3/realtime`,
4
+ * because reaching realtime ships the whole page runtime to an island that only wanted a timer.
5
+ */
6
+
7
+ import { backoffDelay, type Random } from './backoff';
8
+ import { finiteOption, MAX_TIMER_MS } from './finite-option';
9
+ import { whileSessionHeld } from './session-lost';
10
+
11
+ export interface VisibleIntervalOptions {
12
+ /**
13
+ * Back off on failure: each failed run (a throw or a rejection) doubles the wait from `ms` up to
14
+ * `maxMs`, with equal jitter so a fleet of tabs does not ask in lockstep; a success resets it to
15
+ * `ms`. Without it every wait is `ms`, failed or not.
16
+ */
17
+ readonly maxMs?: number | undefined;
18
+ }
19
+
20
+ /** The tab, injected: timers and visibility. Internal; `visibleInterval` reads the real one. */
21
+ export interface IntervalHost {
22
+ setTimer(fn: () => void, ms: number): unknown;
23
+ clearTimer(id: unknown): void;
24
+ isHidden(): boolean;
25
+ /** Subscribe to visibility changes; returns the unsubscribe. */
26
+ onVisibility(fn: () => void): () => void;
27
+ readonly random: Random;
28
+ }
29
+
30
+ const SUBJECT = 'visibleInterval';
31
+
32
+ /**
33
+ * Call `run` every `ms` while the tab is shown. Hidden: no call; one make-up call when shown. An
34
+ * async `run` is never overlapped — the next wait starts when it settles; a throw or rejection is
35
+ * re-raised for the page's error hook. With `maxMs`, failures back off and a success resets.
36
+ * Stops on a session loss; starts afresh when a principal next signs in. Returns an idempotent
37
+ * disposer.
38
+ */
39
+ export function visibleInterval(
40
+ run: () => unknown,
41
+ ms: number,
42
+ options: VisibleIntervalOptions = {},
43
+ ): () => void {
44
+ return visibleIntervalOn(browserHost(), run, ms, options);
45
+ }
46
+
47
+ /** `visibleInterval` over an injected tab. Internal: tests and `visibleInterval` call it. */
48
+ export function visibleIntervalOn(
49
+ host: IntervalHost,
50
+ run: () => unknown,
51
+ ms: number,
52
+ options: VisibleIntervalOptions = {},
53
+ ): () => void {
54
+ const base = clampTimer(finiteOption(SUBJECT, 'ms', ms));
55
+ const max =
56
+ options.maxMs === undefined ? base : clampTimer(finiteOption(SUBJECT, 'maxMs', options.maxMs));
57
+ // Stopped by a session loss, started afresh — attempt 1 — by the next signed-in principal.
58
+ return whileSessionHeld(() => runLoop(host, run, base, max));
59
+ }
60
+
61
+ /** One session's loop: armed now, stopped by the returned function. */
62
+ function runLoop(host: IntervalHost, run: () => unknown, base: number, max: number): () => void {
63
+ /** 1 + the failures since the last success: what `backoffDelay` grows the wait by. */
64
+ let attempt = 1;
65
+ let timer: unknown;
66
+ let owed = false;
67
+ let running = false;
68
+ let stopped = false;
69
+
70
+ const arm = (): void => {
71
+ if (stopped || running) return;
72
+ // Jitter spreads a fleet's RETRIES; a healthy poll's period is exactly what was asked for.
73
+ const wait =
74
+ attempt > 1 && max > base
75
+ ? backoffDelay({ attempt, base, max, jitter: 'equal', random: host.random })
76
+ : base;
77
+ timer = host.setTimer(tick, wait);
78
+ };
79
+ const call = (): void => {
80
+ running = true;
81
+ const settled = (failed: boolean): void => {
82
+ running = false;
83
+ attempt = failed ? attempt + 1 : 1;
84
+ arm();
85
+ };
86
+ // A failure is timed (it backs off) and then re-raised ONCE, off the loop's stack, so the loop
87
+ // survives and the page's error hook still sees what `run` did not catch. Never swallowed.
88
+ const failed = (error: unknown): void => {
89
+ settled(true);
90
+ queueMicrotask(() => {
91
+ throw error;
92
+ });
93
+ };
94
+ let result: unknown;
95
+ try {
96
+ result = run();
97
+ } catch (error) {
98
+ failed(error);
99
+ return;
100
+ }
101
+ if (result instanceof Promise) result.then(() => settled(false), failed);
102
+ else settled(false);
103
+ };
104
+ const tick = (): void => {
105
+ timer = undefined;
106
+ if (host.isHidden()) {
107
+ owed = true;
108
+ return;
109
+ }
110
+ call();
111
+ };
112
+ const visible = (): void => {
113
+ if (host.isHidden() || !owed) return;
114
+ owed = false;
115
+ call();
116
+ };
117
+
118
+ const offVisibility = host.onVisibility(visible);
119
+ const stop = (): void => {
120
+ if (stopped) return;
121
+ stopped = true;
122
+ if (timer !== undefined) host.clearTimer(timer);
123
+ timer = undefined;
124
+ offVisibility();
125
+ };
126
+ arm();
127
+ return stop;
128
+ }
129
+
130
+ /** A timer past 2^31-1 ms fires at once, and one under 1 ms is a spin. */
131
+ function clampTimer(ms: number): number {
132
+ return Math.min(Math.max(1, ms), MAX_TIMER_MS);
133
+ }
134
+
135
+ type VisibilityDoc = {
136
+ readonly visibilityState?: string;
137
+ addEventListener?(type: string, fn: () => void): void;
138
+ removeEventListener?(type: string, fn: () => void): void;
139
+ };
140
+
141
+ function browserHost(): IntervalHost {
142
+ const doc: VisibilityDoc | undefined = Reflect.get(globalThis, 'document');
143
+ return {
144
+ setTimer: (fn, ms) => setTimeout(fn, ms),
145
+ clearTimer: (id) => clearTimeout(id as ReturnType<typeof setTimeout>),
146
+ isHidden: () => doc?.visibilityState === 'hidden',
147
+ onVisibility(fn) {
148
+ doc?.addEventListener?.('visibilitychange', fn);
149
+ return (): void => doc?.removeEventListener?.('visibilitychange', fn);
150
+ },
151
+ random: Math.random,
152
+ };
153
+ }
@@ -2,15 +2,17 @@
2
2
  // three headers a delivery carries, and the parse of the one header a receiver reads back.
3
3
  //
4
4
  // It lives HERE for the reason `timing-safe-equal.ts` does, in that file's own words: two packages
5
- // need the identical guarantee and cannot share it any other way. `@ultimat3/jobs` (tier 3) signs
6
- // a delivery and its boundary forbids `@ultimat3/http`; `@ultimat3/http` (tier 2) verifies one and
7
- // may not reach tier 3. Neither is the other's dependency, so the one copy lives at the tier both
8
- // can reach. Before this module the spelling was stated twice and held together by a hex literal
9
- // asserted in two test files — which works and is not a single source of truth.
5
+ // need the identical guarantee. `@ultimat3/jobs` (tier 3) signs a delivery; `@ultimat3/http`
6
+ // (tier 2) verifies one and may not reach tier 3. `jobs` may import `http` (it does, for
7
+ // `webhookReceiver()`'s route), but a sender's format is not the receiver's policy, so the format
8
+ // sits below both and neither re-declares it. Before this module the spelling was stated twice and
9
+ // held together by a hex literal asserted in two test files — not a single source of truth.
10
10
  //
11
11
  // FORMAT and never POLICY. What counts as fresh, how large a body may be, and which status a
12
12
  // refusal answers with are the receiver's questions and stay in `@ultimat3/http`.
13
13
 
14
+ import { hmac } from './hmac';
15
+
14
16
  /** The format's version: the first field of the canonical string, and the signature's key. */
15
17
  export const WEBHOOK_SIGNATURE_VERSION = 'v1';
16
18
 
@@ -86,12 +88,8 @@ export interface WebhookMacInput {
86
88
 
87
89
  /** The hex hmac-sha256 over the canonical string. The one place either side computes one. */
88
90
  export function webhookMac(input: WebhookMacInput): string {
89
- const hasher = new Bun.CryptoHasher('sha256', input.secret);
90
- hasher.update(
91
- `${WEBHOOK_SIGNATURE_VERSION}:${input.timestampText}:${input.eventId}:${input.topic}:`,
92
- );
93
- hasher.update(input.body);
94
- return hasher.digest('hex');
91
+ const prefix = `${WEBHOOK_SIGNATURE_VERSION}:${input.timestampText}:${input.eventId}:${input.topic}:`;
92
+ return hmac('sha256', input.secret, [prefix, input.body], 'hex');
95
93
  }
96
94
 
97
95
  /**