@ultimat3/core 1.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.
@@ -0,0 +1,131 @@
1
+ // Single responsibility: the framework-wide error-code registry (code -> title + docs).
2
+ // One source of truth so the CLI, the dev overlay and `--json` render identical text.
3
+ // The cycle with ./errors is intentional and safe: nothing here touches UltimateError at
4
+ // module-evaluation time.
5
+
6
+ import { UltimateError } from './errors';
7
+
8
+ export interface ErrorCodeDescriptor {
9
+ readonly title: string;
10
+ readonly docs: string;
11
+ }
12
+
13
+ export interface ErrorCodeDeclaration {
14
+ readonly title: string;
15
+ readonly docs?: string | undefined;
16
+ }
17
+
18
+ export interface ErrorCodeEntry extends ErrorCodeDescriptor {
19
+ readonly code: string;
20
+ }
21
+
22
+ export const ERROR_DOCS_BASE = 'https://ultimate.dev/errors/';
23
+
24
+ export function errorDocsUrl(code: string): string {
25
+ return `${ERROR_DOCS_BASE}${code}`;
26
+ }
27
+
28
+ /** Codes owned by `@ultimat3/core`. Every other package calls `registerErrorCodes()`. */
29
+ const CORE_CODE_TITLES = {
30
+ X_ABORTED: 'operation aborted',
31
+ X_CONFIG_INVALID: 'app.config.ts is invalid',
32
+ X_CURSOR_INVALID: 'pagination cursor is malformed, tampered with or from another query',
33
+ X_CURSOR_SECRET_DEV: 'cursors are signed with the shipped development key',
34
+ X_DRAINING: 'process is draining and refuses new work',
35
+ X_ENV_MISSING: 'required environment variables are missing or invalid',
36
+ X_ERROR_CODE_DUPLICATE: 'error code registered twice',
37
+ X_ID_INVALID: 'value is not a valid id',
38
+ X_IMAGE_DECODE_FAILED: 'image bytes are malformed, truncated or internally inconsistent',
39
+ X_IMAGE_TOO_LARGE: 'image exceeds the pipeline pixel ceiling',
40
+ X_IMAGE_UNSUPPORTED: 'the built-in image pipeline cannot read or write this format',
41
+ X_INTERNAL: 'unexpected internal framework error',
42
+ X_INVARIANT: 'invariant violated',
43
+ X_NO_CONTEXT: 'no request context is active',
44
+ X_NOT_IMPLEMENTED: 'this driver does not implement the requested feature',
45
+ X_REGISTRAR_CONFLICT: 'two different registrars are loaded for one primitive kind',
46
+ X_REGISTRAR_MISSING: 'no registrar is loaded for a primitive kind',
47
+ X_ROLE_INVALID: 'ROLE is not a known runtime role',
48
+ X_SERVICE_DUPLICATE: 'a service name is registered twice',
49
+ X_SERVICE_MISSING: 'service is not registered on the request context',
50
+ X_SHUTDOWN_TIMEOUT: 'graceful shutdown exceeded its deadline',
51
+ X_UNREACHABLE: 'unreachable branch was reached',
52
+ } as const;
53
+
54
+ export type CoreErrorCode = keyof typeof CORE_CODE_TITLES;
55
+
56
+ function descriptor(code: string, declaration: ErrorCodeDeclaration): ErrorCodeDescriptor {
57
+ return Object.freeze({ title: declaration.title, docs: declaration.docs ?? errorDocsUrl(code) });
58
+ }
59
+
60
+ export const CORE_ERROR_CODES: Readonly<Record<CoreErrorCode, ErrorCodeDescriptor>> = Object.freeze(
61
+ Object.fromEntries(
62
+ Object.entries(CORE_CODE_TITLES).map(([code, title]) => [code, descriptor(code, { title })]),
63
+ ) as Record<CoreErrorCode, ErrorCodeDescriptor>,
64
+ );
65
+
66
+ const registry = new Map<string, ErrorCodeDescriptor>(Object.entries(CORE_ERROR_CODES));
67
+
68
+ /**
69
+ * Register a package's codes. Throws `X_ERROR_CODE_DUPLICATE` on collision so two packages
70
+ * can never disagree about what a code means.
71
+ */
72
+ export function registerErrorCodes(codes: Readonly<Record<string, ErrorCodeDeclaration>>): void {
73
+ const duplicates: string[] = [];
74
+ for (const code of Object.keys(codes)) {
75
+ if (registry.has(code)) duplicates.push(code);
76
+ }
77
+ if (duplicates.length > 0) {
78
+ throw new UltimateError({
79
+ code: 'X_ERROR_CODE_DUPLICATE',
80
+ cause: `already registered: ${duplicates.join(', ')}`,
81
+ fix: `rename the colliding code(s) in the registering package's src/errors.ts`,
82
+ meta: { duplicates },
83
+ });
84
+ }
85
+ for (const [code, declaration] of Object.entries(codes)) {
86
+ registry.set(code, descriptor(code, declaration));
87
+ }
88
+ }
89
+
90
+ /** `X_DB_DRIFT` -> `db drift`. Deterministic fallback so an unknown code still renders. */
91
+ function humanize(code: string): string {
92
+ return code.replace(/^X_/, '').toLowerCase().replaceAll('_', ' ');
93
+ }
94
+
95
+ export function describeErrorCode(code: string): ErrorCodeDescriptor {
96
+ const known = registry.get(code);
97
+ if (known !== undefined) return known;
98
+ return descriptor(code, { title: humanize(code) });
99
+ }
100
+
101
+ export function hasErrorCode(code: string): boolean {
102
+ return registry.has(code);
103
+ }
104
+
105
+ /** Sorted, stable — the CLI prints this for `x errors --json`. */
106
+ export function listErrorCodes(): readonly ErrorCodeEntry[] {
107
+ return [...registry.entries()]
108
+ .map(([code, value]) => ({ code, title: value.title, docs: value.docs }))
109
+ .sort((a, b) => (a.code < b.code ? -1 : a.code > b.code ? 1 : 0));
110
+ }
111
+
112
+ /** Test-only: drop everything a package registered, keeping core's codes. */
113
+ export function resetErrorCodes(): void {
114
+ registry.clear();
115
+ for (const [code, value] of Object.entries(CORE_ERROR_CODES)) registry.set(code, value);
116
+ }
117
+
118
+ /**
119
+ * Test-only: capture the registry and get the undo back. Every package registers its codes once,
120
+ * at import time, and bun shares one process across test files — so a file that resets the
121
+ * registry permanently strips the titles of every package imported before it, and their errors
122
+ * render the humanised fallback (`X_DB_DRIFT: db drift`) for the rest of the run. Returning the
123
+ * restore rather than a value is deliberate: there is nothing to hand back to the wrong registry.
124
+ */
125
+ export function errorCodeSnapshot(): () => void {
126
+ const saved = new Map(registry);
127
+ return () => {
128
+ registry.clear();
129
+ for (const [code, value] of saved) registry.set(code, value);
130
+ };
131
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,159 @@
1
+ // Single responsibility: `UltimateError` — the one error type every Ultimate package throws.
2
+ // Stable code + cause + exact fix command, rendered identically in the terminal, the browser
3
+ // overlay and `--json`. Never throw a bare Error anywhere in the framework.
4
+
5
+ import { describeErrorCode } from './error-codes';
6
+
7
+ /**
8
+ * Structural brand. `instanceof` is unreliable across duplicated module instances and across
9
+ * tier-0 packages that may not import each other (`@ultimat3/schema` cannot import
10
+ * `@ultimat3/core`), so the guard is duck-typed on a well-known symbol instead.
11
+ */
12
+ export const ULTIMATE_ERROR_BRAND: unique symbol = Symbol.for('ultimate.error');
13
+
14
+ export interface UltimateErrorInit {
15
+ /** `SCREAMING_SNAKE`, prefixed `X_`. Must exist in the code registry to get a title. */
16
+ readonly code: string;
17
+ /** What actually happened, concrete and specific. Never a generic sentence. */
18
+ readonly cause: string;
19
+ /** The exact command or edit that fixes it. */
20
+ readonly fix: string;
21
+ readonly docs?: string | undefined;
22
+ readonly meta?: Readonly<Record<string, unknown>> | undefined;
23
+ /** The underlying thrown value, when this error wraps one. */
24
+ readonly sourceError?: unknown;
25
+ }
26
+
27
+ export interface UltimateErrorJSON {
28
+ readonly code: string;
29
+ readonly title: string;
30
+ readonly cause: string;
31
+ readonly fix: string;
32
+ readonly docs: string;
33
+ readonly meta?: Readonly<Record<string, unknown>> | undefined;
34
+ readonly stack?: string | undefined;
35
+ }
36
+
37
+ export interface FormatErrorOptions {
38
+ /** Append a 4th `docs:` line. Off by default — the contract's rendering is 3 lines. */
39
+ readonly docs?: boolean | undefined;
40
+ }
41
+
42
+ export class UltimateError extends Error {
43
+ readonly [ULTIMATE_ERROR_BRAND] = true;
44
+ override readonly name: string = 'UltimateError';
45
+ readonly code: string;
46
+ readonly title: string;
47
+ /** Set by `Error`'s `cause` option; always a human-readable string in Ultimate. */
48
+ declare readonly cause: string;
49
+ readonly fix: string;
50
+ readonly docs: string;
51
+ readonly meta: Readonly<Record<string, unknown>> | undefined;
52
+ readonly sourceError: unknown;
53
+
54
+ constructor(init: UltimateErrorInit) {
55
+ const described = describeErrorCode(init.code);
56
+ // `message` carries the cause because it is the ONLY field a runtime prints when an
57
+ // error escapes uncaught — a worker log, a CI transcript, a stack trace. A message of
58
+ // just `code: title` tells an operator which rule fired but not which row, column or
59
+ // value, which is the opposite of "errors are instructions". `format()` still renders
60
+ // the canonical 3 lines from the fields, so the two never disagree.
61
+ super(`${init.code}: ${described.title} — ${init.cause}`, { cause: init.cause });
62
+ this.code = init.code;
63
+ this.title = described.title;
64
+ this.fix = init.fix;
65
+ this.docs = init.docs ?? described.docs;
66
+ this.meta = init.meta;
67
+ this.sourceError = init.sourceError;
68
+ }
69
+
70
+ /**
71
+ * The canonical 3-line terminal rendering:
72
+ *
73
+ * ```text
74
+ * X_DB_DRIFT: schema differs from migrations
75
+ * cause: table "posts" has column "publish_at" not present in any migration
76
+ * fix: x db gen "add publish_at"
77
+ * ```
78
+ */
79
+ format(options?: FormatErrorOptions): string {
80
+ const lines = [`${this.code}: ${this.title}`, ` cause: ${this.cause}`, ` fix: ${this.fix}`];
81
+ if (options?.docs === true) lines.push(` docs: ${this.docs}`);
82
+ return lines.join('\n');
83
+ }
84
+
85
+ toJSON(): UltimateErrorJSON {
86
+ return {
87
+ code: this.code,
88
+ title: this.title,
89
+ cause: this.cause,
90
+ fix: this.fix,
91
+ docs: this.docs,
92
+ meta: this.meta,
93
+ stack: this.stack,
94
+ };
95
+ }
96
+ }
97
+
98
+ export function isUltimateError(value: unknown): value is UltimateError {
99
+ return typeof value === 'object' && value !== null && ULTIMATE_ERROR_BRAND in value;
100
+ }
101
+
102
+ /** Init for a subclass that owns its code. */
103
+ export type CodedErrorInit = Omit<UltimateErrorInit, 'code'>;
104
+
105
+ export class ConfigInvalidError extends UltimateError {
106
+ static readonly code = 'X_CONFIG_INVALID';
107
+ override readonly name = 'ConfigInvalidError';
108
+ constructor(init: CodedErrorInit) {
109
+ super({ ...init, code: ConfigInvalidError.code });
110
+ }
111
+ }
112
+
113
+ export class EnvMissingError extends UltimateError {
114
+ static readonly code = 'X_ENV_MISSING';
115
+ override readonly name = 'EnvMissingError';
116
+ constructor(init: CodedErrorInit) {
117
+ super({ ...init, code: EnvMissingError.code });
118
+ }
119
+ }
120
+
121
+ export class NotImplementedError extends UltimateError {
122
+ static readonly code = 'X_NOT_IMPLEMENTED';
123
+ override readonly name = 'NotImplementedError';
124
+ constructor(init: CodedErrorInit) {
125
+ super({ ...init, code: NotImplementedError.code });
126
+ }
127
+ }
128
+
129
+ export class InternalError extends UltimateError {
130
+ static readonly code = 'X_INTERNAL';
131
+ override readonly name = 'InternalError';
132
+ constructor(init: CodedErrorInit) {
133
+ super({ ...init, code: InternalError.code });
134
+ }
135
+ }
136
+
137
+ /** The blessed shape for an unimplemented remote driver. Always carries a real fix line. */
138
+ export function notImplemented(feature: string, fix: string): never {
139
+ throw new NotImplementedError({ cause: `${feature} is not implemented by this driver`, fix });
140
+ }
141
+
142
+ /** Normalise anything caught into an `UltimateError` without losing the original. */
143
+ export function toUltimateError(value: unknown, fix?: string): UltimateError {
144
+ if (isUltimateError(value)) return value;
145
+ const cause =
146
+ value instanceof Error
147
+ ? `${value.name}: ${value.message}`
148
+ : `non-error value thrown: ${String(value)}`;
149
+ return new InternalError({
150
+ cause,
151
+ fix: fix ?? 'fix the underlying failure named in cause, then re-run',
152
+ sourceError: value,
153
+ });
154
+ }
155
+
156
+ /** Render any caught value with the 3-line contract, so CLI output never varies. */
157
+ export function formatError(value: unknown, options?: FormatErrorOptions): string {
158
+ return toUltimateError(value).format(options);
159
+ }
package/src/ids.ts ADDED
@@ -0,0 +1,132 @@
1
+ // Single responsibility: identifier generation. UUIDv7 is the framework default because
2
+ // database indexes, cursors and log sorting all want time-ordered keys.
3
+
4
+ import { type Clock, systemClock } from './clock';
5
+ import { UltimateError } from './errors';
6
+
7
+ /** Nominal typing without a runtime cost. `Brand<string, 'post'>` never mixes with `'user'`. */
8
+ export type Brand<T, K extends string> = T & { readonly __brand: K };
9
+
10
+ /** A branded UUIDv7 for entity `K`. */
11
+ export type Id<K extends string> = Brand<string, K>;
12
+
13
+ const HEX = '0123456789abcdef';
14
+ /** Exactly 64 URL-safe characters, so `byte & 63` is unbiased and never lands out of range. */
15
+ const NANO_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
16
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
17
+
18
+ /** 12-bit counter lives in `rand_a`, seeded low so ~3k ids/ms fit before it overflows. */
19
+ const COUNTER_SEED_MASK = 0x3ff;
20
+ const COUNTER_MAX = 0xfff;
21
+
22
+ let lastEpochMs = -1;
23
+ let counter = 0;
24
+
25
+ function randomBytes(length: number): Uint8Array {
26
+ const bytes = new Uint8Array(length);
27
+ crypto.getRandomValues(bytes);
28
+ return bytes;
29
+ }
30
+
31
+ export function randomHex(byteLength: number): string {
32
+ const bytes = randomBytes(byteLength);
33
+ let out = '';
34
+ for (const byte of bytes) {
35
+ out += HEX[byte >> 4];
36
+ out += HEX[byte & 0x0f];
37
+ }
38
+ return out;
39
+ }
40
+
41
+ /**
42
+ * UUIDv7 per RFC 9562: 48-bit unix ms, version 7, 12-bit monotonic counter, 62 random bits.
43
+ * Strictly increasing lexicographically even within the same millisecond, and never goes
44
+ * backwards when the wall clock does.
45
+ */
46
+ export function uuid(clock: Clock = systemClock): string {
47
+ let epochMs = clock.now().getTime();
48
+ if (epochMs < lastEpochMs) epochMs = lastEpochMs;
49
+
50
+ if (epochMs === lastEpochMs) {
51
+ counter += 1;
52
+ if (counter > COUNTER_MAX) {
53
+ epochMs += 1;
54
+ counter = randomBytes(2)[0]! & COUNTER_SEED_MASK;
55
+ }
56
+ } else {
57
+ counter = randomBytes(2)[0]! & COUNTER_SEED_MASK;
58
+ }
59
+ lastEpochMs = epochMs;
60
+
61
+ const timeHex = epochMs.toString(16).padStart(12, '0').slice(-12);
62
+ const randA = counter.toString(16).padStart(3, '0');
63
+ const tail = randomHex(8);
64
+ // Force the RFC variant bits (0b10) into the first nibble of `rand_b`.
65
+ const variantNibble = HEX[(Number.parseInt(tail[0]!, 16) & 0x3) | 0x8]!;
66
+
67
+ return [
68
+ timeHex.slice(0, 8),
69
+ timeHex.slice(8, 12),
70
+ `7${randA}`,
71
+ `${variantNibble}${tail.slice(1, 4)}`,
72
+ tail.slice(4, 16).padEnd(12, '0'),
73
+ ].join('-');
74
+ }
75
+
76
+ export function isUuid(value: unknown): boolean {
77
+ return typeof value === 'string' && UUID_RE.test(value);
78
+ }
79
+
80
+ /** Recover the generation instant from a v7 id — cheap debugging and cursor windows. */
81
+ export function uuidTimestamp(id: string): Date {
82
+ if (!isUuid(id)) {
83
+ throw new UltimateError({
84
+ code: 'X_ID_INVALID',
85
+ cause: `"${id}" is not a UUIDv7`,
86
+ fix: 'generate ids with uuid() from @ultimat3/core',
87
+ meta: { id },
88
+ });
89
+ }
90
+ return new Date(Number.parseInt(id.slice(0, 8) + id.slice(9, 13), 16));
91
+ }
92
+
93
+ /** URL-safe random id. Not sortable — use it for tokens and slugs, never primary keys. */
94
+ export function nanoid(length = 21): string {
95
+ const bytes = randomBytes(length);
96
+ let out = '';
97
+ for (const byte of bytes) out += NANO_ALPHABET[byte & 63] as string;
98
+ return out;
99
+ }
100
+
101
+ /** `typedId<'post'>()` — a UUIDv7 branded so it cannot be passed where a user id is wanted. */
102
+ export function typedId<K extends string>(clock: Clock = systemClock): Id<K> {
103
+ return uuid(clock) as Id<K>;
104
+ }
105
+
106
+ /** Validate an untrusted string into a branded id. Throws `X_ID_INVALID`. */
107
+ export function parseId<K extends string>(kind: K, value: unknown): Id<K> {
108
+ if (!isUuid(value)) {
109
+ throw new UltimateError({
110
+ code: 'X_ID_INVALID',
111
+ cause: `expected a ${kind} UUIDv7, received ${JSON.stringify(value)}`,
112
+ fix: `pass an id produced by typedId<'${kind}'>()`,
113
+ meta: { kind, value },
114
+ });
115
+ }
116
+ return value as Id<K>;
117
+ }
118
+
119
+ /** W3C trace-context ids: 16 bytes / 8 bytes of hex. */
120
+ export function traceId(): string {
121
+ return randomHex(16);
122
+ }
123
+
124
+ export function spanId(): string {
125
+ return randomHex(8);
126
+ }
127
+
128
+ /** Test-only: reset the monotonic counter so a frozen clock produces a fresh sequence. */
129
+ export function resetIdCounter(): void {
130
+ lastEpochMs = -1;
131
+ counter = 0;
132
+ }
@@ -0,0 +1,34 @@
1
+ // Single responsibility: the framework's ONE colour grammar — hex or `transparent`, nothing else.
2
+ // It is a parser over strings with no knowledge of pixels, so it lives beside the resampler rather
3
+ // than inside it; and it stays deliberately tiny because a second accepted spelling is a second
4
+ // thing an agent has to guess right, for a padding colour nobody looks at twice.
5
+
6
+ import { imageUnsupported } from './errors';
7
+
8
+ const COLOR_FIX =
9
+ "pass '#rgb', '#rgba', '#rrggbb', '#rrggbbaa' or 'transparent' — hex or transparent, " +
10
+ 'there are no named colours';
11
+
12
+ const HEX = /^#[0-9a-f]+$/;
13
+ /** '#rgb', '#rgba', '#rrggbb', '#rrggbbaa' — the whole grammar, hash included. */
14
+ const HEX_LENGTHS: readonly number[] = [4, 5, 7, 9];
15
+
16
+ /** Hex or `transparent`, nothing else — one way to write a colour is one thing to get wrong. */
17
+ export function parseColor(value: string): readonly [number, number, number, number] {
18
+ const text = value.toLowerCase();
19
+ if (text === 'transparent') return [0, 0, 0, 0];
20
+ if (!HEX.test(text) || !HEX_LENGTHS.includes(text.length)) {
21
+ throw imageUnsupported(`'${value}' is not a colour this pipeline understands`, COLOR_FIX, {
22
+ value,
23
+ });
24
+ }
25
+ const hex = text.slice(1);
26
+ const short = hex.length < 6;
27
+ const size = short ? 1 : 2;
28
+ const channel = (index: number): number => {
29
+ const part = hex.slice(index * size, index * size + size);
30
+ return Number.parseInt(short ? part + part : part, 16);
31
+ };
32
+ const opaque = hex.length === 3 || hex.length === 6;
33
+ return [channel(0), channel(1), channel(2), opaque ? 255 : channel(3)];
34
+ }
@@ -0,0 +1,58 @@
1
+ // Single responsibility: the three failure modes of the image pipeline, as coded errors.
2
+ // Every one names the format AND a runnable way forward, because an agent that hits
3
+ // "unsupported" needs to know which format to ask for instead, not that it lost.
4
+
5
+ import { UltimateError } from '../errors';
6
+
7
+ export class ImageUnsupportedError extends UltimateError {
8
+ static readonly code = 'X_IMAGE_UNSUPPORTED';
9
+ override readonly name = 'ImageUnsupportedError';
10
+ constructor(cause: string, fix: string, meta?: Readonly<Record<string, unknown>>) {
11
+ super({ code: ImageUnsupportedError.code, cause, fix, meta });
12
+ }
13
+ }
14
+
15
+ export class ImageDecodeFailedError extends UltimateError {
16
+ static readonly code = 'X_IMAGE_DECODE_FAILED';
17
+ override readonly name = 'ImageDecodeFailedError';
18
+ constructor(cause: string, fix: string, meta?: Readonly<Record<string, unknown>>) {
19
+ super({ code: ImageDecodeFailedError.code, cause, fix, meta });
20
+ }
21
+ }
22
+
23
+ export class ImageTooLargeError extends UltimateError {
24
+ static readonly code = 'X_IMAGE_TOO_LARGE';
25
+ override readonly name = 'ImageTooLargeError';
26
+ constructor(cause: string, fix: string, meta?: Readonly<Record<string, unknown>>) {
27
+ super({ code: ImageTooLargeError.code, cause, fix, meta });
28
+ }
29
+ }
30
+
31
+ /** A format or a coding feature the built-in pipeline does not implement. */
32
+ export const imageUnsupported = (
33
+ cause: string,
34
+ fix: string,
35
+ meta?: Readonly<Record<string, unknown>>,
36
+ ): ImageUnsupportedError => new ImageUnsupportedError(cause, fix, meta);
37
+
38
+ /** Malformed, truncated or internally inconsistent bytes. Never a silent black image. */
39
+ export const imageDecodeFailed = (
40
+ cause: string,
41
+ meta?: Readonly<Record<string, unknown>>,
42
+ ): ImageDecodeFailedError =>
43
+ new ImageDecodeFailedError(
44
+ cause,
45
+ 'check the file is a complete, uncorrupted image: `file <path>` then re-export it',
46
+ meta,
47
+ );
48
+
49
+ /** The decompression-bomb guard: pixel count is checked from the header, before allocation. */
50
+ export const imageTooLarge = (
51
+ cause: string,
52
+ meta?: Readonly<Record<string, unknown>>,
53
+ ): ImageTooLargeError =>
54
+ new ImageTooLargeError(
55
+ cause,
56
+ 'downscale the source before it reaches the pipeline, or raise MAX_IMAGE_PIXELS deliberately',
57
+ meta,
58
+ );