@ultimat3/core 6.0.0 → 7.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 CHANGED
@@ -14,6 +14,7 @@ is a change to every package.
14
14
  | New code | add to `CORE_CODE_TITLES` in `error-codes.ts`, else the title is auto-humanised |
15
15
  | Time | take a `Clock`; `Date.now()` / `new Date()` only inside `clock.ts` |
16
16
  | Context | never thread `ctx` as a parameter — `useContext()` |
17
+ | A value ambient across an `await` | `asyncContext<T>(subject)` from `async-context.ts`, in **every** package — never `new AsyncLocalStorage` |
17
18
  | Exports | add to `src/index.ts` explicitly; no `export *`. Three subjects that each span a dozen modules arrive through `src/exports/` — every name is still written out in `index.ts`, so the public surface is one file to read |
18
19
  | Files | < 200 LOC, 500 hard ceiling, one responsibility, `kebab-case.ts`, test beside source |
19
20
  | Type claims | `type-pins.ts`, never a `.test.ts` — `tsconfig.json` excludes tests, so `tsc` never reads one |
@@ -22,6 +23,24 @@ Deliberate cycles (safe — nothing is referenced at module-evaluation time):
22
23
  `errors.ts ⇄ error-codes.ts`. Keep it that way: no top-level `UltimateError` use in
23
24
  `error-codes.ts`.
24
25
 
26
+ **`async-context.ts` is the framework's ONE `AsyncLocalStorage`, and that is a framework rule
27
+ rather than a core one, `As of 2026-08-20`.** `asyncContext` is exported from `src/index.ts` and
28
+ six modules outside this package opened their own before they adopted it — `@ultimat3/db`'s
29
+ transaction, statement attribution and expected-loop scopes, `@ultimat3/entity`'s `crossTenant`,
30
+ `@ultimat3/ai`'s budget ledger and LLM stream sink. Each was a module-scope `new` a browser bundler
31
+ turns into `TypeError: undefined is not a constructor` at module EVALUATION, so importing any of
32
+ those packages from a client bundle failed before a line of app code ran. Reads degrade to
33
+ `undefined`, writes throw `X_ASYNC_CONTEXT_UNAVAILABLE`; deferring the construction changes nothing
34
+ a server can observe — the storage is built on the first `get()` or `run()` rather than at module
35
+ evaluation, and `getStore()` outside a scope answers `undefined` either way.
36
+
37
+ The mechanical half is `scripts/async-context-guard.ts`, collected by `x verify`'s `unit` step
38
+ through `scripts/async-context-guard.test.ts` — it refuses a `new AsyncLocalStorage` **and** the
39
+ import that binds the class, aliased or namespaced, anywhere but this one file. The browser-barrel
40
+ test in `async-context.test.ts` covers the same defect for core alone and cannot see another
41
+ package; the guard cannot see a runtime `await import('node:async_hooks')`. Neither is the other's
42
+ duplicate.
43
+
25
44
  `error-render.ts` imports nothing, including from this package — an error factory that dies
26
45
  formatting its own message is the failure it exists to prevent, so it cannot depend on anything
27
46
  that could itself throw. The same defect shipped three times (`entity`, `flags`, `cli`) before
@@ -85,6 +104,7 @@ shape against a locally declared sample interface for exactly that reason.
85
104
  |---|---|---|
86
105
  | which deploy this is | `environment.ts` (`ULTIMATE_ENV`) | the twin of `ROLE`; never declare a second env var for it |
87
106
  | what this process does | `roles.ts` (`ROLE`) | |
107
+ | how a route renders, caches offline and hydrates | `route-vocabulary.ts` (`RENDER_MODES`, `OFFLINE_STRATEGIES`, `HYDRATE_STRATEGIES`) | tier 0 because SIX packages name them and imports only go down — `render`, `http`, `seo`, `manifest` and `pwa` each kept a hand-copy until 2026-08, and `'spa'` was deleted from one while five went on admitting it under a green typecheck. Every union is `(typeof ARRAY)[number]`, pinned in `type-pins.ts`; `scripts/render-modes.test.ts` refuses a second declaration anywhere in `packages/*/src`. Re-export it, never restate it |
88
108
  | which build of the APP this is | `app-version.ts` (`APP_VERSION`) | one reader, `dev` by default: `db` writes it into `x_migrations` and `jobs` into `x_backfills`, and `jobs` cannot reach `db` for the answer |
89
109
  | the values | `env.ts` | `checkEnv().values` holds REAL secrets — anything that prints goes through `maskedEnvValues()` |
90
110
  | `.env.example` | `env-example.ts` | a projection of the schema, never hand-maintained |
package/README.md CHANGED
@@ -9,7 +9,8 @@ Zero dependencies, zero `@ultimat3/*` imports.
9
9
  | rendering an app's value into a `cause` / `fix` without throwing | `error-render.ts` |
10
10
  | code → `{ title, docs }` registry, `registerErrorCodes()` | `error-codes.ts` |
11
11
  | `Result<T, E>` for boundaries where throwing is wrong | `result.ts` |
12
- | request context on `AsyncLocalStorage` | `context.ts` |
12
+ | the one lazy `AsyncLocalStorage`, every ambient scope in the framework | `async-context.ts` |
13
+ | request context on that seam | `context.ts` |
13
14
  | `Actor` (`user \| service \| agent \| anonymous`) | `actor.ts` |
14
15
  | acting as another actor, with an origin and a reason | `impersonate.ts` |
15
16
  | is an error worth retrying? one classification per code | `error-retry.ts` |
@@ -20,6 +21,7 @@ Zero dependencies, zero `@ultimat3/*` imports.
20
21
  | the committed encrypted secrets envelope, AES-256-GCM | `secrets.ts` |
21
22
  | the two secrets files, and decrypted values → `defineEnv` | `secrets-store.ts` |
22
23
  | `defineConfig()` for `app.config.ts` | `config.ts` |
24
+ | the closed route vocabulary every renderer names | `route-vocabulary.ts` |
23
25
  | runtime roles + `ROLE` resolution | `roles.ts` |
24
26
  | `Clock` — the only source of "now" | `clock.ts` |
25
27
  | UUIDv7, nanoid, branded ids | `ids.ts` |
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@ultimat3/core",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "Ultimate's foundation: errors, context, env, config, clock, ids, logging, telemetry, lifecycle",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
+ "sideEffects": [
8
+ "./src/context.ts",
9
+ "./src/lifecycle-errors.ts",
10
+ "./src/schema-error-codes.ts",
11
+ "./src/secrets-errors.ts"
12
+ ],
7
13
  "repository": {
8
14
  "type": "git",
9
15
  "url": "git+https://github.com/developerz-ai/ultimate.git",
@@ -16,16 +16,18 @@ export interface AsyncContext<T> {
16
16
  }
17
17
 
18
18
  /**
19
- * The storage is constructed on first `run()`, never at module scope. That is the whole point of
20
- * this file: a browser bundler stubs `node:async_hooks` to `{}` — Bun's `target: 'browser'` emits
21
- * `var { AsyncLocalStorage } = (() => ({}))` — so a module-scope `new` threw
19
+ * The storage is constructed on the first `get()` or `run()`, never at module scope. That is the
20
+ * whole point of this file: a browser bundler stubs `node:async_hooks` to `{}` — Bun's
21
+ * `target: 'browser'` emits `var { AsyncLocalStorage } = (() => ({}))` — so a module-scope `new` threw
22
22
  * `TypeError: undefined is not a constructor` at module EVALUATION, and every package that
23
23
  * transitively imports core was dead on arrival in a client bundle. `@ultimat3/ui` calls itself a
24
24
  * SolidJS design system and could not be put on a client by the only client bundler the framework
25
25
  * has, for this reason and no other.
26
26
  *
27
- * The server pays nothing: `getStore()` before any `run()` answers `undefined` whether the storage
28
- * was ever constructed or not, so deferring the construction changes no observable behaviour.
27
+ * The laziness buys the browser bundle, not a server allocation. `open()` runs on a READ as well as
28
+ * a write, so a server whose first call is `get()` constructs the storage there it is deferred,
29
+ * never skipped. What deferring changes is nothing observable: `getStore()` outside a scope answers
30
+ * `undefined` whether the storage was ever constructed or not, which is what makes it safe.
29
31
  *
30
32
  * **Reads degrade, writes throw**, and that split is the doctrine rather than a convenience.
31
33
  * `get()` answers `undefined` in a browser because that is TRUE — nothing is in flight there, so
package/src/config.ts CHANGED
@@ -4,10 +4,13 @@
4
4
 
5
5
  import { ConfigInvalidError } from './errors';
6
6
  import { ROLES, type Role } from './roles';
7
+ // `app.config.ts` CONSUMES the route vocabulary; it does not own it. Declaring `OfflineStrategy`
8
+ // here is what made it copyable — `render`, `manifest` and `pwa` each wrote their own rather than
9
+ // import a name that reads like a config key.
10
+ import type { OfflineStrategy } from './route-vocabulary';
7
11
  import { isIanaZoneName } from './time-zone-name';
8
12
 
9
13
  export type ThemeMode = 'light' | 'dark' | 'system';
10
- export type OfflineStrategy = 'precache' | 'runtime' | 'network-only';
11
14
  export type CacheTier = 'memo' | 'lru' | 'shared' | 'isr' | 'cdn';
12
15
  export type RealtimeTier = 'channels' | 'live-queries' | 'local-first';
13
16
  export type RealtimeTransport = 'memory' | 'nats' | 'redis';
@@ -64,7 +64,7 @@ export function parseSentryDsn(dsn: string): SentryDsn {
64
64
  }
65
65
 
66
66
  /** The protocol's own level names. `warning`/`error`/`fatal` happen to be the same three words. */
67
- const LEVELS: Readonly<Record<ErrorSeverity, string>> = Object.freeze({
67
+ const LEVELS = Object.freeze<Record<ErrorSeverity, string>>({
68
68
  warning: 'warning',
69
69
  error: 'error',
70
70
  fatal: 'fatal',
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ export {
31
31
  } from './actor';
32
32
  export { APP_VERSION_KEY, appVersion, DEFAULT_APP_VERSION } from './app-version';
33
33
  export { assert, assertNever, type InvariantOptions, invariant } from './assert';
34
+ export { type AsyncContext, asyncContext } from './async-context';
34
35
  export { canonicalJson, fingerprint } from './canonical-json';
35
36
  export { type Clock, type FrozenClock, frozenClock, systemClock } from './clock';
36
37
  export type {
@@ -45,7 +46,6 @@ export type {
45
46
  DatabaseConfig,
46
47
  JobsConfig,
47
48
  McpConfig,
48
- OfflineStrategy,
49
49
  PwaConfig,
50
50
  RealtimeConfig,
51
51
  RealtimeTier,
@@ -479,6 +479,8 @@ export type { Err, Ok, Result } from './result';
479
479
  export { err, isErr, isOk, map, mapErr, ok, tryCatch, unwrap, unwrapOr } from './result';
480
480
  export type { ResolveRoleOptions, Role, RoleInfo, ScalingSignal } from './roles';
481
481
  export { DEFAULT_ROLE, isRole, ROLE_INFO, ROLES, resolveRole } from './roles';
482
+ export type { HydrateStrategy, OfflineStrategy, RenderMode } from './route-vocabulary';
483
+ export { HYDRATE_STRATEGIES, OFFLINE_STRATEGIES, RENDER_MODES } from './route-vocabulary';
482
484
  export { safeUrl, URL_ATTRIBUTES } from './safe-url';
483
485
  export { defineService, resetServices, type ServiceFactory } from './service';
484
486
  export { timingSafeEqual } from './timing-safe-equal';
package/src/logger.ts CHANGED
@@ -17,7 +17,7 @@ export const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal', '
17
17
 
18
18
  export type LogLevel = (typeof LOG_LEVELS)[number];
19
19
 
20
- const LEVEL_WEIGHT: Readonly<Record<LogLevel, number>> = Object.freeze({
20
+ const LEVEL_WEIGHT = Object.freeze<Record<LogLevel, number>>({
21
21
  trace: 10,
22
22
  debug: 20,
23
23
  info: 30,
@@ -23,7 +23,7 @@ import type {
23
23
  } from './telemetry';
24
24
 
25
25
  /** OTLP's `SpanKind` enum; `UNSPECIFIED` is 0 and Ultimate never emits it. */
26
- const SPAN_KIND: Readonly<Record<SpanKind, number>> = Object.freeze({
26
+ const SPAN_KIND = Object.freeze<Record<SpanKind, number>>({
27
27
  internal: 1,
28
28
  server: 2,
29
29
  client: 3,
@@ -31,7 +31,7 @@ const SPAN_KIND: Readonly<Record<SpanKind, number>> = Object.freeze({
31
31
  consumer: 5,
32
32
  });
33
33
 
34
- const STATUS_CODE: Readonly<Record<SpanStatusCode, number>> = Object.freeze({
34
+ const STATUS_CODE = Object.freeze<Record<SpanStatusCode, number>>({
35
35
  unset: 0,
36
36
  ok: 1,
37
37
  error: 2,
package/src/roles.ts CHANGED
@@ -26,7 +26,7 @@ export interface RoleInfo {
26
26
  readonly stateful: boolean;
27
27
  }
28
28
 
29
- export const ROLE_INFO: Readonly<Record<Role, RoleInfo>> = Object.freeze({
29
+ export const ROLE_INFO = Object.freeze<Record<Role, RoleInfo>>({
30
30
  web: { role: 'web', scalesOn: 'rps', maxReplicas: null, stateful: false },
31
31
  sync: { role: 'sync', scalesOn: 'ws-connections', maxReplicas: null, stateful: false },
32
32
  worker: { role: 'worker', scalesOn: 'queue-depth', maxReplicas: null, stateful: false },
@@ -0,0 +1,23 @@
1
+ // Single responsibility: the three closed vocabularies a route is declared in — how it renders,
2
+ // how it survives offline, when it hydrates. Tier 0 so every package that names one imports it.
3
+ // Deliberately not `config.ts`: `app.config.ts` CONSUMES `OfflineStrategy`, it does not own it.
4
+
5
+ /**
6
+ * Each union is DERIVED from its array rather than written twice, so the pair cannot disagree:
7
+ * the array is the one place a member is added or removed and the type follows.
8
+ *
9
+ * This module exists because the alternative was measured. Twelve declarations of these three sets
10
+ * lived across six packages — `render` alone spelled `RenderMode` and `RENDER_MODES` separately —
11
+ * and `'spa'` was deleted from one of them while five others went on admitting it under a green
12
+ * project-wide typecheck. `@ultimat3/pwa`'s copy mapped `spa` to `cache-first`, the one strategy
13
+ * that gives an `app/` route a SHARED cache entry: one member's authed HTML served to the next.
14
+ * A copy is not a style question. `scripts/render-modes.test.ts` refuses a second declaration.
15
+ */
16
+ export const RENDER_MODES = ['static', 'isr', 'ssr', 'stream'] as const;
17
+ export type RenderMode = (typeof RENDER_MODES)[number];
18
+
19
+ export const OFFLINE_STRATEGIES = ['precache', 'runtime', 'network-only'] as const;
20
+ export type OfflineStrategy = (typeof OFFLINE_STRATEGIES)[number];
21
+
22
+ export const HYDRATE_STRATEGIES = ['idle', 'visible', 'interaction', 'never'] as const;
23
+ export type HydrateStrategy = (typeof HYDRATE_STRATEGIES)[number];
@@ -14,7 +14,7 @@ import type { ScalingSignal } from './roles';
14
14
  * own comment ("via the ingress metric adapter") already assumes. The other two are instantaneous
15
15
  * values a scrape can read directly, so their series names are the chart's words verbatim.
16
16
  */
17
- export const SCALING_METRICS: Readonly<Record<ScalingSignal, string | null>> = Object.freeze({
17
+ export const SCALING_METRICS = Object.freeze<Record<ScalingSignal, string | null>>({
18
18
  rps: 'http_requests_total',
19
19
  'ws-connections': 'connections',
20
20
  'queue-depth': 'queue_depth',
package/src/type-pins.ts CHANGED
@@ -1,11 +1,11 @@
1
- // Compile-time pins for the actor-facts seam and the config surface. Source, not a `.test.ts`,
2
- // on purpose:
3
- // `tsconfig.json` excludes `src/**/*.test.ts`, so `tsc -b` never reads a test file and a
4
- // type-level assertion written there can never fail. This module emits nothing and exports
5
- // nothing anybody imports — a regression is a build error, the only enforcement that counts.
1
+ // Compile-time pins for the actor-facts seam, the config surface and the route vocabulary.
2
+ // Source, not a `.test.ts`, on purpose: `tsconfig.json` excludes `src/**/*.test.ts`, so `tsc -b`
3
+ // never reads a test file and a type-level assertion written there can never fail. This module
4
+ // emits nothing and exports nothing anybody imports a regression is a build error.
6
5
 
7
6
  import type { Actor, ActorFactMap, FactKeysOf, FactMapOf } from './actor';
8
7
  import type { AppConfigInput, DatabaseConfig } from './config';
8
+ import type { HydrateStrategy, OfflineStrategy, RenderMode } from './route-vocabulary';
9
9
 
10
10
  /** Fails to compile when `T` is anything but `true`. The whole mechanism. */
11
11
  type Assert<T extends true> = T;
@@ -98,3 +98,28 @@ type _DatabaseInputCarriesNoDeadField = Assert<
98
98
  ? true
99
99
  : false
100
100
  >;
101
+
102
+ /**
103
+ * Mutual assignability, not one-way. The tuples are load-bearing: a bare `A extends B` distributes
104
+ * over a union and answers `true` for every member separately, so it cannot see a widening — which
105
+ * is the only thing these three pins are looking for.
106
+ */
107
+ type Exact<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
108
+
109
+ /**
110
+ * Each route vocabulary's union must stay DERIVED from its array. `(typeof ARRAY)[number]` is what
111
+ * makes the pair unable to disagree, and it is one careless edit from being a hand-written union
112
+ * again — which is the shape six packages shipped until `route-vocabulary.ts` existed. Restating
113
+ * the members here is a pin, not a copy: nothing imports these, and a member added to the array
114
+ * without a word in the changelog is a build error rather than a silent widening five packages
115
+ * inherit through a re-export.
116
+ */
117
+ type _RenderModeIsItsArray = Assert<Exact<RenderMode, 'static' | 'isr' | 'ssr' | 'stream'>>;
118
+
119
+ type _OfflineStrategyIsItsArray = Assert<
120
+ Exact<OfflineStrategy, 'precache' | 'runtime' | 'network-only'>
121
+ >;
122
+
123
+ type _HydrateStrategyIsItsArray = Assert<
124
+ Exact<HydrateStrategy, 'idle' | 'visible' | 'interaction' | 'never'>
125
+ >;