@ultimat3/core 23.0.0 → 25.0.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 (98) hide show
  1. package/CLAUDE.md +29 -26
  2. package/README.md +98 -30
  3. package/package.json +4 -7
  4. package/src/actor.ts +9 -0
  5. package/src/address-class.ts +40 -4
  6. package/src/assert.ts +9 -5
  7. package/src/audit.ts +144 -0
  8. package/src/aws-sigv4.ts +275 -0
  9. package/src/backoff.ts +16 -0
  10. package/src/bunfs.ts +17 -0
  11. package/src/client-dispatch.ts +24 -3
  12. package/src/client-flight.ts +68 -13
  13. package/src/client-problem.ts +62 -6
  14. package/src/client-retry-after.ts +47 -0
  15. package/src/client-transport.ts +3 -1
  16. package/src/client-wire.ts +27 -3
  17. package/src/config-ai.ts +32 -0
  18. package/src/config-defaults.ts +53 -0
  19. package/src/config-fixes.ts +0 -10
  20. package/src/config-health.ts +9 -2
  21. package/src/config-jobs.ts +51 -0
  22. package/src/config-keys.ts +170 -0
  23. package/src/config-mail.ts +73 -0
  24. package/src/config-merge.ts +9 -1
  25. package/src/config-navigation.ts +1 -25
  26. package/src/config-pwa.ts +42 -5
  27. package/src/config-removed.ts +131 -0
  28. package/src/config-shape.ts +79 -0
  29. package/src/config-site.ts +14 -3
  30. package/src/config.ts +141 -173
  31. package/src/context.ts +29 -13
  32. package/src/cookie.ts +299 -0
  33. package/src/core-error-codes.ts +2 -0
  34. package/src/cursor-page.ts +41 -0
  35. package/src/cursor.ts +26 -5
  36. package/src/decimal-order.ts +5 -4
  37. package/src/deprecation.ts +77 -0
  38. package/src/dev-secrets.ts +18 -7
  39. package/src/drain-deadline.ts +43 -0
  40. package/src/env-example.ts +9 -29
  41. package/src/error-reporter-sentry.ts +7 -3
  42. package/src/errors.ts +18 -9
  43. package/src/exports/error-contract.ts +0 -1
  44. package/src/exports/observability.ts +1 -1
  45. package/src/exports/secrets.ts +3 -0
  46. package/src/finite-option.ts +1 -1
  47. package/src/flight-gate.ts +43 -16
  48. package/src/fnv1a.ts +19 -0
  49. package/src/generation-fence.ts +1 -1
  50. package/src/health-disclosure.ts +43 -0
  51. package/src/host-rules.ts +28 -1
  52. package/src/html-escape.ts +24 -0
  53. package/src/ids.ts +7 -7
  54. package/src/image/canvas.ts +76 -5
  55. package/src/image/errors.ts +3 -1
  56. package/src/image/pipeline.ts +17 -5
  57. package/src/image/png-pixels.ts +29 -6
  58. package/src/image/probe.ts +7 -2
  59. package/src/image/raster.ts +27 -2
  60. package/src/index.ts +99 -33
  61. package/src/iso-date.ts +1 -1
  62. package/src/lifecycle-errors.ts +1 -1
  63. package/src/lifecycle-readiness.ts +60 -2
  64. package/src/lifecycle-signals.ts +27 -2
  65. package/src/lifecycle-types.ts +96 -0
  66. package/src/lifecycle.ts +44 -133
  67. package/src/locale-direction.ts +1 -1
  68. package/src/logger.ts +92 -16
  69. package/src/mcp-exposure.ts +70 -8
  70. package/src/measurement-actor.ts +16 -1
  71. package/src/metric-errors.ts +32 -0
  72. package/src/metric-registry.ts +151 -0
  73. package/src/metric-series.ts +94 -0
  74. package/src/metrics.ts +9 -255
  75. package/src/nearest-name.ts +11 -2
  76. package/src/otlp-metric-exporter.ts +1 -1
  77. package/src/otlp-span-exporter.ts +1 -1
  78. package/src/otlp.ts +44 -13
  79. package/src/page.ts +4 -2
  80. package/src/pg-executor.ts +15 -0
  81. package/src/public-cause.ts +37 -0
  82. package/src/registrar.ts +22 -4
  83. package/src/retry.ts +40 -7
  84. package/src/route-rank.ts +36 -0
  85. package/src/same-origin.ts +1 -1
  86. package/src/sampler.ts +6 -2
  87. package/src/secrets-errors.ts +14 -3
  88. package/src/secrets-key-file.ts +139 -0
  89. package/src/secrets-store.ts +32 -16
  90. package/src/service.ts +5 -5
  91. package/src/single-flight.ts +1 -1
  92. package/src/source-mask.ts +14 -8
  93. package/src/store-mode.ts +23 -0
  94. package/src/telemetry.ts +1 -1
  95. package/src/theme-storage.ts +12 -0
  96. package/src/type-pins.ts +51 -1
  97. package/src/image/fixtures.ts +0 -263
  98. package/src/time-zone-name.ts +0 -14
@@ -3,6 +3,14 @@
3
3
  // safe zone `@ultimat3/pwa` promises is a composite. So this file exists for exactly that one hop:
4
4
  // Bun re-encodes to PNG, this reads the pixels back, `canvas.ts` blits, this writes them again.
5
5
 
6
+ // why: `Bun.inflateSync` takes no output bound — measured, it ignores `maxOutputLength` and
7
+ // returns the whole stream — and `DecompressionStream` is async where this seam is synchronous.
8
+ // `node:zlib` is the one inflate here that can be told when to stop. A NAMESPACE import, never a
9
+ // named one: the browser polyfill of this module has no `inflateRawSync`, and a named import of a
10
+ // missing export fails the BUNDLE of every browser graph that reaches the barrel
11
+ // (`async-context.test.ts` builds one) — for a function no browser ever calls.
12
+ import * as zlib from 'node:zlib';
13
+ import { stringField } from '../error-render';
6
14
  import { imageDecodeFailed, imageUnsupported } from './errors';
7
15
  import {
8
16
  adler32,
@@ -14,7 +22,7 @@ import {
14
22
  unshared,
15
23
  writeU32,
16
24
  } from './png-bytes';
17
- import { type Raster, rasterFrom } from './raster';
25
+ import { assertPixelBudget, type Raster, rasterFrom } from './raster';
18
26
 
19
27
  /** Truecolour with alpha, 8 bits per channel — the ONE shape `Raster` is. */
20
28
  const RGBA_COLOR_TYPE = 8 << 4;
@@ -105,8 +113,13 @@ function readHeader(bytes: Uint8Array): PngHeader {
105
113
  return { width: readU32(bytes, 16), height: readU32(bytes, 20) };
106
114
  }
107
115
 
108
- /** Every IDAT concatenated: a PNG may split its stream across any number of them. */
109
- function idatStream(bytes: Uint8Array): Uint8Array {
116
+ /**
117
+ * Every IDAT concatenated — a PNG may split its stream across any number of them — and inflated
118
+ * to AT MOST `limit` bytes, which is what the header says the pixels need. Inflating first and
119
+ * measuring after is the decompression bomb: deflate packs zeros ~1000:1, so a file of a few
120
+ * hundred kilobytes declaring 1x1 allocated hundreds of megabytes before anything compared it to 5.
121
+ */
122
+ function idatStream(bytes: Uint8Array, limit: number): Uint8Array {
110
123
  const parts: Uint8Array[] = [];
111
124
  let at = 8;
112
125
  while (at + 12 <= bytes.length) {
@@ -123,8 +136,16 @@ function idatStream(bytes: Uint8Array): Uint8Array {
123
136
  try {
124
137
  // The 2-byte zlib header and the 4-byte Adler-32 trailer are PNG's envelope, stripped here
125
138
  // so the payload inflates as RAW deflate — see the encoder above for the mirror image.
126
- return Bun.inflateSync(unshared(stream.subarray(2, stream.length - 4)), { windowBits: -15 });
127
- } catch {
139
+ return zlib.inflateRawSync(unshared(stream.subarray(2, stream.length - 4)), {
140
+ maxOutputLength: limit,
141
+ });
142
+ } catch (error) {
143
+ if (stringField(error, 'code') === 'ERR_BUFFER_TOO_LARGE') {
144
+ throw imageDecodeFailed(
145
+ `the PNG IDAT stream inflates to more than the ${limit} bytes its header's size needs`,
146
+ { length: stream.length, limit },
147
+ );
148
+ }
128
149
  throw imageDecodeFailed(`the PNG IDAT stream (${stream.length} bytes) could not be inflated`, {
129
150
  length: stream.length,
130
151
  });
@@ -171,8 +192,10 @@ function unfilter(raw: Uint8Array, width: number, height: number): Uint8ClampedA
171
192
  /** PNG bytes to RGBA pixels. Refuses anything but 8-bit RGBA, naming the pipeline that reads it. */
172
193
  export function decodeImage(bytes: Uint8Array): Raster {
173
194
  const { width, height } = readHeader(bytes);
174
- const raw = idatStream(bytes);
195
+ // From the HEADER, before the stream is touched: it bounds `expected`, which bounds the inflate.
196
+ assertPixelBudget(width, height, 'PNG');
175
197
  const expected = (width * BYTES_PER_PIXEL + 1) * height;
198
+ const raw = idatStream(bytes, expected);
176
199
  if (raw.length !== expected) {
177
200
  throw imageDecodeFailed(
178
201
  `the PNG inflates to ${raw.length} bytes but ${width}x${height} RGBA needs ${expected}`,
@@ -66,8 +66,13 @@ function requireBytes(bytes: Uint8Array, needed: number, format: string, missing
66
66
 
67
67
  const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] as const;
68
68
 
69
- /** `mif1` is the generic HEIF brand AVIF files carry; `avis` is an image sequence. */
70
- const AVIF_BRANDS: ReadonlySet<string> = new Set(['avif', 'avis', 'mif1']);
69
+ /**
70
+ * `avif` is a still, `avis` an image sequence. NOT `mif1`: that is the generic HEIF brand, which
71
+ * an AVIF file lists as a COMPATIBLE brand and a HEIC file lists too — counting it sniffed every
72
+ * iPhone photo as AVIF, and the probe then read (or failed to read) it as one. An AVIF whose major
73
+ * brand is `mif1` still names `avif` among its compatible brands, which the scan below reads.
74
+ */
75
+ const AVIF_BRANDS: ReadonlySet<string> = new Set(['avif', 'avis']);
71
76
 
72
77
  function isAvif(bytes: Uint8Array): boolean {
73
78
  if (!ascii(bytes, 4, 'ftyp')) return false;
@@ -25,7 +25,9 @@ export const MAX_IMAGE_PIXELS = 64_000_000;
25
25
  /** Checked from the header before a single byte is allocated. */
26
26
  export function assertPixelBudget(width: number, height: number, source: string): void {
27
27
  if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
28
- throw imageTooLarge(`${source} declares a ${width}x${height} image, which is not a size`, {
28
+ // Decode-failed, not too-large: a header declaring zero, a fraction or `NaN` pixels is
29
+ // inconsistent bytes, and the too-large fix — downscale it — has nothing to act on.
30
+ throw imageDecodeFailed(`${source} declares a ${width}x${height} image, which is not a size`, {
29
31
  width,
30
32
  height,
31
33
  source,
@@ -41,7 +43,7 @@ export function assertPixelBudget(width: number, height: number, source: string)
41
43
  }
42
44
 
43
45
  /** A transparent canvas of the given size, budget already checked. */
44
- export function createRaster(width: number, height: number, source = 'raster'): Raster {
46
+ export function blankRaster(width: number, height: number, source = 'raster'): Raster {
45
47
  assertPixelBudget(width, height, source);
46
48
  return { width, height, pixels: new Uint8ClampedArray(width * height * 4) };
47
49
  }
@@ -61,6 +63,29 @@ export function rasterFrom(width: number, height: number, pixels: Uint8ClampedAr
61
63
  return { width, height, pixels };
62
64
  }
63
65
 
66
+ /** A rectangle of a raster, in its pixels. */
67
+ export interface ImageRegion extends ImageSize {
68
+ readonly x: number;
69
+ readonly y: number;
70
+ }
71
+
72
+ /** The pixels of `region`, copied row by row. The region must lie inside the raster. */
73
+ export function cropRaster(raster: Raster, region: ImageRegion): Raster {
74
+ const { x, y, width, height } = region;
75
+ if (x < 0 || y < 0 || x + width > raster.width || y + height > raster.height) {
76
+ throw imageDecodeFailed(
77
+ `crop ${width}x${height}+${x}+${y} lies outside the ${raster.width}x${raster.height} raster`,
78
+ { x, y, width, height, rasterWidth: raster.width, rasterHeight: raster.height },
79
+ );
80
+ }
81
+ const out = blankRaster(width, height, 'crop');
82
+ for (let row = 0; row < height; row += 1) {
83
+ const from = ((y + row) * raster.width + x) * 4;
84
+ out.pixels.set(raster.pixels.subarray(from, from + width * 4), row * width * 4);
85
+ }
86
+ return out;
87
+ }
88
+
64
89
  /** Whether any pixel is not fully opaque — decides PNG vs JPEG when nobody asked. */
65
90
  export function hasAlpha(raster: Raster): boolean {
66
91
  const { pixels } = raster;
package/src/index.ts CHANGED
@@ -5,15 +5,17 @@
5
5
  // secrets — because each is one subject spread over a dozen modules. Every name they carry is
6
6
  // still written out below: `export *` would make the contract something a reader has to resolve.
7
7
 
8
- // Anchored on purpose, and NOT by the `sideEffects` array: Bun reads any array as `false` and drops
9
- // the module regardless (oven-sh/bun#40650). This module registers @ultimat3/schema's error TITLES,
10
- // because schema is tier 0 and cannot register its own — and what reads them is `UltimateError`'s
11
- // constructor, which never imports this file. Shaken out, every X_VALIDATION_FAILED renders
12
- // untitled in the browser with nothing to say why. `SIDE_EFFECTS_ANCHORS` carries the argument and
13
- // `bun run side-effects` enforces it. `context.ts`, `lifecycle-errors.ts` and `secrets-errors.ts`
14
- // are declared side-effecting too and are deliberately NOT anchored: each is reached by whatever
15
- // uses it, and anchoring `context.ts` alone measured +3,485 B on a browser chunk for a provider a
16
- // browser can never fire.
8
+ // Anchored on purpose, and not by the `sideEffects` array alone: Bun before 1.4.1 read any array as
9
+ // `false` and dropped the module regardless (oven-sh/bun#40650), and a bare import holds on every
10
+ // bundler — the array still lists both, because one that honours it drops a bare import of a module
11
+ // it does not list. This module registers @ultimat3/schema's error TITLES, because schema is tier 0
12
+ // and cannot register its own — and what reads them is `UltimateError`'s constructor, which never
13
+ // imports this file. Shaken out, every X_VALIDATION_FAILED renders untitled in the browser with
14
+ // nothing to say why. `SIDE_EFFECTS_ANCHORS` carries the argument and `bun run side-effects`
15
+ // enforces it. `context.ts`, `lifecycle-errors.ts` and `secrets-errors.ts` also run something at
16
+ // import and are neither anchored NOR listed: only their own bindings need the effect, so they ride
17
+ // along exactly where they are used. Listed, Bun 1.4.2 kept all three in every chunk reaching this
18
+ // barrel, ~5.4 kB an island (`SIDE_EFFECTS_BY_USE` in `scripts/side-effects.ts`).
17
19
  import './core-error-codes';
18
20
  import './schema-error-codes';
19
21
 
@@ -30,6 +32,7 @@ export {
30
32
  ACTOR_KINDS,
31
33
  actorFact,
32
34
  actorLabel,
35
+ actorOf,
33
36
  actorOrigin,
34
37
  agentActor,
35
38
  anonymousActor,
@@ -42,22 +45,45 @@ export {
42
45
  withFacts,
43
46
  } from './actor';
44
47
  export type { AddressClass } from './address-class';
45
- export { classifyAddress, isPublicAddress } from './address-class';
48
+ export { addressNetwork, classifyAddress, isPublicAddress } from './address-class';
46
49
  export { APP_VERSION_KEY, appVersion, DEFAULT_APP_VERSION } from './app-version';
47
- export { assert, assertNever, type InvariantOptions, invariant } from './assert';
50
+ export { type AssertCodedOptions, assert, assertCoded, assertNever } from './assert';
48
51
  export { type AsyncContext, asyncContext } from './async-context';
49
52
  /** The four shapes an async region can be in — produced by `realtime`, rendered by `ui`. */
50
53
  export type { AsyncState } from './async-state';
54
+ /** The audit seam `action` and `query` share: one record shape, one installed sink. */
55
+ export type {
56
+ AuditFailure,
57
+ AuditOutcome,
58
+ AuditPrimitive,
59
+ AuditRecord,
60
+ AuditSink,
61
+ AuditSurface,
62
+ } from './audit';
63
+ export {
64
+ AUDIT_RECORD_FIELDS,
65
+ getAuditSink,
66
+ resetAuditSink,
67
+ setAuditSink,
68
+ } from './audit';
69
+ export type {
70
+ AwsCredentials,
71
+ AwsPayload,
72
+ SignAwsRequestInput,
73
+ SignedAwsRequest,
74
+ } from './aws-sigv4';
75
+ export { signAwsRequest, UNSIGNED_PAYLOAD } from './aws-sigv4';
51
76
  export type { BackoffCurve, BackoffOptions, JitterMode, Random } from './backoff';
52
- export { backoffDelay } from './backoff';
77
+ export { backoffDelay, jitterStatedDelay } from './backoff';
78
+ export { isCompiledBundle } from './bunfs';
53
79
  export { CACHE_TIERS, type CacheTierName } from './cache-vocabulary';
54
80
  export { canonicalJson, fingerprint } from './canonical-json';
55
81
  export type { FetchLike, TransportRequest } from './client-dispatch';
56
- export { IDEMPOTENCY_HEADER } from './client-dispatch';
82
+ export { BUILD_ID_HEADER, IDEMPOTENCY_HEADER } from './client-dispatch';
57
83
  /**
58
84
  * Flight control for a typed client, and OPT-IN by construction: `@ultimat3/action`'s and
59
85
  * `@ultimat3/query`'s `client.ts` each name `ClientFlight` as a TYPE only, so a caller that never
60
- * mentions `createClientFlight` pays nothing for the fence, the dedup map or the retry loop.
86
+ * mentions `clientFlight` pays nothing for the fence, the dedup map or the retry loop.
61
87
  * Both packages re-export these names unchanged; this is the one copy.
62
88
  */
63
89
  export type {
@@ -67,7 +93,7 @@ export type {
67
93
  FlightKeyOptions,
68
94
  FlightPlan,
69
95
  } from './client-flight';
70
- export { createClientFlight, DEFAULT_CLIENT_RETRY, isTransientFailure } from './client-flight';
96
+ export { clientFlight, DEFAULT_CLIENT_RETRY, isTransientFailure } from './client-flight';
71
97
  export type { ActionPathStyle, ActionRoute } from './client-paths';
72
98
  export {
73
99
  ACTION_PATH_PREFIX,
@@ -80,6 +106,9 @@ export {
80
106
  splitWords,
81
107
  } from './client-paths';
82
108
  export type { TransportFailure } from './client-problem';
109
+ export { MAX_REMOTE_TITLE_LENGTH, remoteTitleOf, withStatedDelay } from './client-problem';
110
+ /** What a decoder reads off a refusal: the stated `Retry-After`, and the body's display title. */
111
+ export { MAX_RETRY_AFTER_SECONDS, retryAfterSecondsOf } from './client-retry-after';
83
112
  /**
84
113
  * The browser seam (plan 101): ONE HTTP function, the records envelope it decodes, the per-tab
85
114
  * page handle records land in, and the principal fence every client layer subscribes to.
@@ -93,8 +122,6 @@ export { FRAMEWORK_CODE, problemOf, retryForStatus, traceHeaders } from './clien
93
122
  export { notifyClientWrite, onClientWrite } from './client-writes';
94
123
  export { type Clock, type FrozenClock, frozenClock, systemClock } from './clock';
95
124
  export type {
96
- AiConfig,
97
- AiConfigInput,
98
125
  AppConfig,
99
126
  AppConfigInput,
100
127
  AppConfigOverlay,
@@ -102,7 +129,6 @@ export type {
102
129
  CacheConfig,
103
130
  DatabaseConfig,
104
131
  JobsConfig,
105
- McpConfig,
106
132
  NotifyConfig,
107
133
  PwaConfigInput,
108
134
  RealtimeConfig,
@@ -111,9 +137,17 @@ export type {
111
137
  ThemeMode,
112
138
  } from './config';
113
139
  export { defineConfig, INBOX_RETENTION_KEYS } from './config';
140
+ export type { AiConfig, AiConfigInput, McpConfig } from './config-ai';
114
141
  export type { DrainConfig, HealthConfig, ReadinessMode } from './config-health';
115
142
  export { READINESS_MODES } from './config-health';
116
143
  export type { IslandsConfig, IslandsSection, IslandsSectionInput } from './config-islands';
144
+ export { JOBS_CONCURRENCY_DEFAULT, type JobsConcurrency } from './config-jobs';
145
+ export type {
146
+ MailConfig,
147
+ MailRetainMimeConfig,
148
+ MailSection,
149
+ MailSectionInput,
150
+ } from './config-mail';
117
151
  export type {
118
152
  NavigationConfig,
119
153
  NavigationSection,
@@ -125,7 +159,6 @@ export type {
125
159
  export {
126
160
  DEFAULT_SPECULATION,
127
161
  NAVIGATION_SURFACES,
128
- resolveSpeculation,
129
162
  SPECULATION_EAGERNESS,
130
163
  } from './config-navigation';
131
164
  export type {
@@ -138,7 +171,7 @@ export type {
138
171
  PwaShortcut,
139
172
  PwaText,
140
173
  } from './config-pwa';
141
- export { PWA_COLOR_KEYS, PWA_SCHEMES } from './config-pwa';
174
+ export { isSameOriginPath, PWA_COLOR_KEYS, PWA_SCHEMES } from './config-pwa';
142
175
  export type {
143
176
  SeoConfig,
144
177
  SeoConfigInput,
@@ -154,7 +187,7 @@ export type { ConflictPolicy, ResolveConflictOptions, Row } from './conflict-pol
154
187
  export { resolveConflict } from './conflict-policy';
155
188
  export type { Ctx, CtxFacts, CtxInit, CtxPatch, CtxServices, ServiceBag } from './context';
156
189
  export {
157
- createContext,
190
+ ctxOf,
158
191
  DEFAULT_LOCALE,
159
192
  DEFAULT_TIME_ZONE,
160
193
  hasContext,
@@ -165,8 +198,13 @@ export {
165
198
  useService,
166
199
  withChildContext,
167
200
  } from './context';
201
+ /** The one cookie codec — auth, http and i18n each parsed `Cookie:` and spelled `Set-Cookie`. */
202
+ export type { CookiePriority, CookieSameSite, SetCookieOptions } from './cookie';
203
+ export { CookieInvalidError, readCookie, serializeSetCookie } from './cookie';
168
204
  export type { CursorPayload } from './cursor';
169
205
  export {
206
+ CURSOR_SECRET_FIX,
207
+ CURSOR_SECRET_KEY,
170
208
  CursorInvalidError,
171
209
  configureCursorSigning,
172
210
  decodeCursor,
@@ -174,9 +212,19 @@ export {
174
212
  resetCursorSigning,
175
213
  usesDevCursorSecret,
176
214
  } from './cursor';
215
+ /** The ONE page shape — `nextCursor` is `null` exactly when `hasMore` is false (25.0.0). */
216
+ export type { Page } from './cursor-page';
217
+ export { pageOf } from './cursor-page';
177
218
  export { compareDecimalText } from './decimal-order';
219
+ export type { Deprecation, DeprecationField, DeprecationRender } from './deprecation';
220
+ export { recordDeprecatedCall, renderDeprecation } from './deprecation';
178
221
  export type { DevSecretsOptions } from './dev-secrets';
179
- export { assertNoDevSecretsOutsideLocal, CursorSecretDevError } from './dev-secrets';
222
+ export {
223
+ assertNoDevSecretsOutsideLocal,
224
+ CursorSecretDevError,
225
+ devSecretsRefused,
226
+ } from './dev-secrets';
227
+ export { DRAIN_DEADLINE_DEFAULT_MS, DRAIN_DEADLINE_MAX_MS } from './drain-deadline';
180
228
  export type {
181
229
  Env,
182
230
  EnvBooleanVar,
@@ -194,10 +242,8 @@ export type {
194
242
  export { checkEnv, defineEnv, describeEnv, maskedEnvValues } from './env';
195
243
  export type { EnvExampleOptions, EnvExampleReport } from './env-example';
196
244
  export {
197
- assertEnvExample,
198
245
  checkEnvExample,
199
246
  ENV_EXAMPLE_PATH,
200
- EnvExampleDriftError,
201
247
  envFileCandidates,
202
248
  parseEnvKeys,
203
249
  renderEnvExample,
@@ -267,7 +313,6 @@ export {
267
313
  statedDelayMs,
268
314
  stringField,
269
315
  toUltimateError,
270
- ULTIMATE_ERROR_BRAND,
271
316
  UltimateError,
272
317
  } from './exports/error-contract';
273
318
  export type {
@@ -336,7 +381,6 @@ export {
336
381
  configureTelemetry,
337
382
  connections,
338
383
  counter,
339
- createLogger,
340
384
  currentSampler,
341
385
  currentSpan,
342
386
  currentSpanContext,
@@ -403,6 +447,7 @@ export {
403
447
  setLogStream,
404
448
  startMetricExport,
405
449
  startSpan,
450
+ structuredLogger,
406
451
  traceparent,
407
452
  tryOtlpEndpoint,
408
453
  withSpan,
@@ -432,6 +477,7 @@ export {
432
477
  masterKeyPath,
433
478
  openSecrets,
434
479
  parseMasterKey,
480
+ promoteStagedMasterKey,
435
481
  readSecretsFile,
436
482
  requireMasterKey,
437
483
  revealOptionalSecret,
@@ -443,6 +489,7 @@ export {
443
489
  SECRETS_KEY_MODE,
444
490
  SecretsFileInvalidError,
445
491
  SecretsFileMissingError,
492
+ SecretsKeyAclError,
446
493
  SecretsKeyInvalidError,
447
494
  SecretsKeyMismatchError,
448
495
  SecretsKeyMissingError,
@@ -454,6 +501,7 @@ export {
454
501
  secretsPath,
455
502
  serializeSecretValues,
456
503
  stagedMasterKeyPath,
504
+ stageMasterKeyFile,
457
505
  writeMasterKeyFile,
458
506
  writeSecretsFile,
459
507
  } from './exports/secrets';
@@ -464,12 +512,16 @@ export type {
464
512
  FlightGateOptions,
465
513
  FlightGateState,
466
514
  } from './flight-gate';
467
- export { createFlightGate, gateOverloaded } from './flight-gate';
515
+ export { flightGate, gateOverloaded } from './flight-gate';
516
+ export { fnv1a } from './fnv1a';
468
517
  export { formatBytes } from './format-bytes';
469
518
  export type { GenerationFence } from './generation-fence';
470
- export { createFence, isSuperseded } from './generation-fence';
519
+ export { generationFence, isSuperseded } from './generation-fence';
520
+ export type { PublicHealthBody } from './health-disclosure';
521
+ export { DEFAULT_HEALTH_DETAIL_PEERS, healthBody, healthPeerListed } from './health-disclosure';
471
522
  export type { HostDecision, HostRule } from './host-rules';
472
523
  export { ANY_HOST, hostDecision, hostMatches } from './host-rules';
524
+ export { escapeHtml } from './html-escape';
473
525
  export type { Brand, Id } from './ids';
474
526
  export {
475
527
  isSpanId,
@@ -482,8 +534,8 @@ export {
482
534
  spanId,
483
535
  traceId,
484
536
  typedId,
485
- uuid,
486
537
  uuidTimestamp,
538
+ uuidV7,
487
539
  } from './ids';
488
540
  export type { ImageFit, ResizeSpec } from './image/canvas';
489
541
  export { parseColor } from './image/color';
@@ -517,7 +569,7 @@ export type { ImageFormat, ImageInfo } from './image/probe';
517
569
  export { IMAGE_FORMATS, IMAGE_MIME_TYPES, probeImage, sniffImageFormat } from './image/probe';
518
570
  export type { ImageSize, Raster } from './image/raster';
519
571
  export {
520
- createRaster,
572
+ blankRaster,
521
573
  hasAlpha,
522
574
  MAX_IMAGE_PIXELS,
523
575
  } from './image/raster';
@@ -579,7 +631,7 @@ export {
579
631
  } from './lifecycle-grace';
580
632
  export type { ReadinessCheck, ReadinessStatus } from './lifecycle-readiness';
581
633
  export type { SignalHandlerOptions } from './lifecycle-signals';
582
- export { installSignalHandlers } from './lifecycle-signals';
634
+ export { drainSignals, installSignalHandlers } from './lifecycle-signals';
583
635
  export { isSelfOrigin, listeningOrigins, markListening, resetListeners } from './listeners';
584
636
  export type { Direction } from './locale-direction';
585
637
  export { directionOf, isRtl } from './locale-direction';
@@ -588,9 +640,16 @@ export { localeSegment, localizePath, splitLocalePath } from './locale-path';
588
640
  // The process logger's test seam, beside nothing it groups with: where a default-writer line goes.
589
641
  export type { LogSink } from './logger';
590
642
  export { setLogSink } from './logger';
591
- export { isMcpExposed, type McpExposureDeclaration } from './mcp-exposure';
643
+ export {
644
+ isMcpExposed,
645
+ type McpAnnotationHints,
646
+ type McpExposureDeclaration,
647
+ type McpListFilterOp,
648
+ type McpListParams,
649
+ } from './mcp-exposure';
592
650
  export type { MeasurementActorFactory } from './measurement-actor';
593
651
  export {
652
+ declaredMeasurementActor,
594
653
  defineMeasurementActor,
595
654
  MEASUREMENT_ACTOR_ID,
596
655
  measurementActor,
@@ -614,8 +673,11 @@ export {
614
673
  CLIENT_SYNC_META,
615
674
  CLIENT_SYNC_WORKER_META,
616
675
  } from './page-meta';
676
+ /** The structural Postgres seam http, auth, action and jobs share without a `@ultimat3/db` edge. */
677
+ export type { PgExecutor } from './pg-executor';
617
678
  export type { ProcessMetricsOptions, ProcessReading } from './process-metrics';
618
679
  export { readProcess, resetProcessMetrics, startProcessMetrics } from './process-metrics';
680
+ export { hasPublicCause, registerPublicCause, resetPublicCauses } from './public-cause';
619
681
  export { type CappedBody, readWithinLimit } from './read-capped';
620
682
  export type { RecordEnvelope, RecordRows } from './record-envelope';
621
683
  export { decodeRecordEnvelope, encodeRecordEnvelope, RECORDS_HEADER } from './record-envelope';
@@ -646,6 +708,7 @@ export { retry, retryDecision } from './retry';
646
708
  export { isRetryableStatus, RETRYABLE_STATUSES } from './retryable-status';
647
709
  export type { ResolveRoleOptions, Role, RoleInfo, ScalingSignal } from './roles';
648
710
  export { DEFAULT_ROLE, isRole, ROLE_INFO, ROLES, resolveRole } from './roles';
711
+ export { routeRank } from './route-rank';
649
712
  export type { HydrateStrategy, OfflineStrategy, RenderMode } from './route-vocabulary';
650
713
  export { HYDRATE_STRATEGIES, OFFLINE_STRATEGIES, RENDER_MODES } from './route-vocabulary';
651
714
  export { safeUrl, URL_ATTRIBUTES } from './safe-url';
@@ -676,8 +739,11 @@ export {
676
739
  type ServiceFactory,
677
740
  } from './service';
678
741
  export type { FlightJoin, Scheduler, SingleFlight, SingleFlightOptions } from './single-flight';
679
- export { createSingleFlight } from './single-flight';
742
+ export { singleFlight } from './single-flight';
680
743
  export { endOfLiteral, maskLiterals, QUOTES, stripComments } from './source-mask';
744
+ export type { StoreMode } from './store-mode';
745
+ export { STORE_MODES, storeMode } from './store-mode';
746
+ export { THEME_STORAGE_KEY } from './theme-storage';
681
747
  export { timingSafeEqual } from './timing-safe-equal';
682
748
  export {
683
749
  frameworkVersion,
package/src/iso-date.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Single responsibility: re-export `@ultimat3/schema`'s ISO date-time predicate, so a package that
2
2
  // depends on core and not on schema — `@ultimat3/ui` formats dates, it validates nothing — judges a
3
- // date string with the ONE rule `t.date` and `timestamp()` use. The `time-zone-name.ts` shape.
3
+ // date string with the ONE rule `t.date` and `timestamp()` use.
4
4
 
5
5
  export { isIsoDateTime } from '@ultimat3/schema';
@@ -15,7 +15,7 @@ registerErrorCodes({
15
15
  *
16
16
  * Loud rather than tolerant, because the silent version was measured and is worse than a failed
17
17
  * boot: `state` never leaves `stopped` and `drain()` has memoized its promise, so a second
18
- * `createServer().start()` bound a real port, answered `X_DRAINING` (503) to every request, and was
18
+ * `httpServer().start()` bound a real port, answered `X_DRAINING` (503) to every request, and was
19
19
  * still accepting connections after its own `stop()` returned — a dead listener holding a port,
20
20
  * with no log line naming what happened.
21
21
  *
@@ -1,5 +1,8 @@
1
- // Single responsibility: the readiness CHECK — its signature and its two answers. The mode that
2
- // decides what a failing one does to `/readyz` is `config-health.ts`'s.
1
+ // Single responsibility: the readiness CHECK — its signature, its two answers, and the registry of
2
+ // named checks `/readyz` runs. The mode that decides what a failing one does to `/readyz` is
3
+ // `config-health.ts`'s; the lifecycle that reports through them is `lifecycle.ts`.
4
+
5
+ import { UltimateError } from './errors';
3
6
 
4
7
  export type ReadinessStatus = 'ok' | 'failing';
5
8
 
@@ -19,3 +22,58 @@ export type ReadinessStatus = 'ok' | 'failing';
19
22
  * is `failing`.
20
23
  */
21
24
  export type ReadinessCheck = () => boolean;
25
+
26
+ const checks = new Map<string, ReadinessCheck>();
27
+
28
+ /**
29
+ * Register a named readiness check. Returns its unregister — the same shape as `onShutdown`, and
30
+ * owned by whoever can be started twice, for the same reason.
31
+ */
32
+ export function registerReadinessCheck(name: string, check: ReadinessCheck): () => void {
33
+ if (checks.has(name)) {
34
+ throw new UltimateError({
35
+ code: 'X_READINESS_CHECK_DUPLICATE',
36
+ cause: `a readiness check named "${name}" is already registered (have: ${[...checks.keys()].join(', ')})`,
37
+ fix: `name the second check for what it actually probes, e.g. registerReadinessCheck('${name}-replica', check) — or hold the unregister the first registration returned and call it first`,
38
+ meta: { name },
39
+ });
40
+ }
41
+ checks.set(name, check);
42
+ return () => {
43
+ if (checks.get(name) === check) checks.delete(name);
44
+ };
45
+ }
46
+
47
+ /** Test-only: registered checks. A count that climbs across a start/stop cycle is a leak. */
48
+ export function readinessCheckCount(): number {
49
+ return checks.size;
50
+ }
51
+
52
+ /** Drop every check — `resetLifecycle()`'s, and nothing else's. */
53
+ export function clearReadinessChecks(): void {
54
+ checks.clear();
55
+ }
56
+
57
+ /**
58
+ * Every check, run now, by name. A check that throws is `failing` — never an unhandled error —
59
+ * and is handed to `onThrow`, which `lifecycle.ts` routes through its total `report`.
60
+ *
61
+ * Built through `Object.fromEntries`, never by assigning `results[name]`: assignment to the one
62
+ * name `__proto__` sets the PROTOTYPE instead of adding a key, so that check vanished from the
63
+ * report, `ready` was computed over an empty object — vacuously true — and a failing check
64
+ * answered 200. `fromEntries` defines own properties and has no such name.
65
+ */
66
+ export function runReadinessChecks(
67
+ onThrow: (name: string, thrown: unknown) => void,
68
+ ): Readonly<Record<string, ReadinessStatus>> {
69
+ const results: [string, ReadinessStatus][] = [];
70
+ for (const [name, check] of checks) {
71
+ try {
72
+ results.push([name, check() ? 'ok' : 'failing']);
73
+ } catch (thrown) {
74
+ results.push([name, 'failing']);
75
+ onThrow(name, thrown);
76
+ }
77
+ }
78
+ return Object.fromEntries(results);
79
+ }
@@ -7,11 +7,36 @@ export interface SignalHandlerOptions {
7
7
  readonly signals?: readonly ProcessSignal[] | undefined;
8
8
  /** Call `process.exit()` once drained. Off in tests. */
9
9
  readonly exit?: boolean | undefined;
10
+ /** Whose default signal set to install — `process.platform` unless a test names one. */
11
+ readonly platform?: string | undefined;
10
12
  }
11
13
 
12
- /** Install SIGTERM/SIGINT handling. Returns an uninstall function. */
14
+ const POSIX_SIGNALS: readonly ProcessSignal[] = Object.freeze(['SIGTERM', 'SIGINT']);
15
+
16
+ /**
17
+ * Windows never sends SIGTERM to a console process: a service stop or a closed console window
18
+ * arrives as SIGHUP, and Ctrl-Break as SIGBREAK. Listening for the POSIX pair alone meant either
19
+ * one killed the process outright, mid-request, with no drain. SIGHUP stays off POSIX on purpose:
20
+ * there it is a terminal hang-up a supervised process should not read as "stop".
21
+ */
22
+ const WINDOWS_SIGNALS: readonly ProcessSignal[] = Object.freeze([
23
+ 'SIGTERM',
24
+ 'SIGINT',
25
+ 'SIGHUP',
26
+ 'SIGBREAK',
27
+ ]);
28
+
29
+ /** The signals that start a drain on `platform`. */
30
+ export function drainSignals(platform: string = process.platform): readonly ProcessSignal[] {
31
+ return platform === 'win32' ? WINDOWS_SIGNALS : POSIX_SIGNALS;
32
+ }
33
+
34
+ /**
35
+ * Install drain-on-signal handling — SIGTERM/SIGINT, plus SIGHUP/SIGBREAK on Windows
36
+ * (`drainSignals`). Returns an uninstall function.
37
+ */
13
38
  export function installSignalHandlers(options?: SignalHandlerOptions): () => void {
14
- const signals: readonly ProcessSignal[] = options?.signals ?? ['SIGTERM', 'SIGINT'];
39
+ const signals = options?.signals ?? drainSignals(options?.platform);
15
40
  const handlers = new Map<ProcessSignal, () => void>();
16
41
 
17
42
  for (const signal of signals) {