@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.
- package/CLAUDE.md +29 -26
- package/README.md +98 -30
- package/package.json +4 -7
- package/src/actor.ts +9 -0
- package/src/address-class.ts +40 -4
- package/src/assert.ts +9 -5
- package/src/audit.ts +144 -0
- package/src/aws-sigv4.ts +275 -0
- package/src/backoff.ts +16 -0
- package/src/bunfs.ts +17 -0
- package/src/client-dispatch.ts +24 -3
- package/src/client-flight.ts +68 -13
- package/src/client-problem.ts +62 -6
- package/src/client-retry-after.ts +47 -0
- package/src/client-transport.ts +3 -1
- package/src/client-wire.ts +27 -3
- package/src/config-ai.ts +32 -0
- package/src/config-defaults.ts +53 -0
- package/src/config-fixes.ts +0 -10
- package/src/config-health.ts +9 -2
- package/src/config-jobs.ts +51 -0
- package/src/config-keys.ts +170 -0
- package/src/config-mail.ts +73 -0
- package/src/config-merge.ts +9 -1
- package/src/config-navigation.ts +1 -25
- package/src/config-pwa.ts +42 -5
- package/src/config-removed.ts +131 -0
- package/src/config-shape.ts +79 -0
- package/src/config-site.ts +14 -3
- package/src/config.ts +141 -173
- package/src/context.ts +29 -13
- package/src/cookie.ts +299 -0
- package/src/core-error-codes.ts +2 -0
- package/src/cursor-page.ts +41 -0
- package/src/cursor.ts +26 -5
- package/src/decimal-order.ts +5 -4
- package/src/deprecation.ts +77 -0
- package/src/dev-secrets.ts +18 -7
- package/src/drain-deadline.ts +43 -0
- package/src/env-example.ts +9 -29
- package/src/error-reporter-sentry.ts +7 -3
- package/src/errors.ts +18 -9
- package/src/exports/error-contract.ts +0 -1
- package/src/exports/observability.ts +1 -1
- package/src/exports/secrets.ts +3 -0
- package/src/finite-option.ts +1 -1
- package/src/flight-gate.ts +43 -16
- package/src/fnv1a.ts +19 -0
- package/src/generation-fence.ts +1 -1
- package/src/health-disclosure.ts +43 -0
- package/src/host-rules.ts +28 -1
- package/src/html-escape.ts +24 -0
- package/src/ids.ts +7 -7
- package/src/image/canvas.ts +76 -5
- package/src/image/errors.ts +3 -1
- package/src/image/pipeline.ts +17 -5
- package/src/image/png-pixels.ts +29 -6
- package/src/image/probe.ts +7 -2
- package/src/image/raster.ts +27 -2
- package/src/index.ts +99 -33
- package/src/iso-date.ts +1 -1
- package/src/lifecycle-errors.ts +1 -1
- package/src/lifecycle-readiness.ts +60 -2
- package/src/lifecycle-signals.ts +27 -2
- package/src/lifecycle-types.ts +96 -0
- package/src/lifecycle.ts +44 -133
- package/src/locale-direction.ts +1 -1
- package/src/logger.ts +92 -16
- package/src/mcp-exposure.ts +70 -8
- package/src/measurement-actor.ts +16 -1
- package/src/metric-errors.ts +32 -0
- package/src/metric-registry.ts +151 -0
- package/src/metric-series.ts +94 -0
- package/src/metrics.ts +9 -255
- package/src/nearest-name.ts +11 -2
- package/src/otlp-metric-exporter.ts +1 -1
- package/src/otlp-span-exporter.ts +1 -1
- package/src/otlp.ts +44 -13
- package/src/page.ts +4 -2
- package/src/pg-executor.ts +15 -0
- package/src/public-cause.ts +37 -0
- package/src/registrar.ts +22 -4
- package/src/retry.ts +40 -7
- package/src/route-rank.ts +36 -0
- package/src/same-origin.ts +1 -1
- package/src/sampler.ts +6 -2
- package/src/secrets-errors.ts +14 -3
- package/src/secrets-key-file.ts +139 -0
- package/src/secrets-store.ts +32 -16
- package/src/service.ts +5 -5
- package/src/single-flight.ts +1 -1
- package/src/source-mask.ts +14 -8
- package/src/store-mode.ts +23 -0
- package/src/telemetry.ts +1 -1
- package/src/theme-storage.ts +12 -0
- package/src/type-pins.ts +51 -1
- package/src/image/fixtures.ts +0 -263
- package/src/time-zone-name.ts +0 -14
package/src/image/png-pixels.ts
CHANGED
|
@@ -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
|
-
/**
|
|
109
|
-
|
|
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
|
|
127
|
-
|
|
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
|
-
|
|
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}`,
|
package/src/image/probe.ts
CHANGED
|
@@ -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
|
-
/**
|
|
70
|
-
|
|
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;
|
package/src/image/raster.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
9
|
-
// the module regardless (oven-sh/bun#40650)
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
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 {
|
|
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 `
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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.
|
|
3
|
+
// date string with the ONE rule `t.date` and `timestamp()` use.
|
|
4
4
|
|
|
5
5
|
export { isIsoDateTime } from '@ultimat3/schema';
|
package/src/lifecycle-errors.ts
CHANGED
|
@@ -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
|
-
* `
|
|
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
|
|
2
|
-
// decides what a failing one does to `/readyz` is
|
|
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
|
+
}
|
package/src/lifecycle-signals.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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) {
|