@ultimat3/core 24.0.0 → 25.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +24 -27
- package/README.md +72 -34
- package/package.json +4 -7
- package/src/actor.ts +21 -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 +18 -11
- 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 +1 -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 +0 -33
- package/src/config.ts +71 -94
- package/src/context.ts +16 -12
- package/src/cookie.ts +267 -3
- package/src/core-error-codes.ts +2 -0
- package/src/cursor-page.ts +41 -0
- package/src/cursor.ts +23 -5
- 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/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 +29 -14
- package/src/generation-fence.ts +1 -1
- package/src/ids.ts +7 -7
- package/src/image/canvas.ts +76 -5
- package/src/image/pipeline.ts +17 -5
- package/src/image/raster.ts +24 -1
- package/src/index.ts +90 -35
- 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 +16 -7
- 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/page.ts +4 -2
- package/src/pg-executor.ts +2 -2
- package/src/registrar.ts +1 -0
- package/src/retry.ts +25 -5
- 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/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/secrets-store.ts
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
// each value into the process environment, under its own name, only where the real environment has
|
|
4
4
|
// nothing. `secrets.ts` owns the envelope; this owns the filesystem and `process.env`.
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
// why: Bun has no synchronous file read or exists check, and `installSecrets()` runs once, at boot,
|
|
7
|
+
// before the process serves anything, so an async read has nothing to overlap with. Writing the
|
|
8
|
+
// key is `secrets-key-file.ts`'s.
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
10
|
// Bun exposes no path-join primitive.
|
|
12
11
|
import { join } from 'node:path';
|
|
13
12
|
import { isUltimateError } from './errors';
|
|
14
13
|
import type { SecretValues } from './secrets';
|
|
15
14
|
import { masterKeyId, openSecrets, parseMasterKey, sealSecrets } from './secrets';
|
|
16
15
|
import { SecretsFileMissingError, SecretsKeyMissingError } from './secrets-errors';
|
|
16
|
+
import { OWNER_ONLY_MODE, renameOver, writeOwnerOnlyFile } from './secrets-key-file';
|
|
17
17
|
|
|
18
18
|
/** Committed. Encrypted at rest, diffable, and the only file `x secrets` writes into the repo. */
|
|
19
19
|
export const SECRETS_FILE = 'secrets.enc.json';
|
|
@@ -21,8 +21,11 @@ export const SECRETS_FILE = 'secrets.enc.json';
|
|
|
21
21
|
export const SECRETS_KEY_FILE = '.secrets.key';
|
|
22
22
|
/** Read first, so a container gets its key from the platform's secret store and never from a file. */
|
|
23
23
|
export const SECRETS_KEY_ENV = 'ULTIMATE_SECRETS_KEY';
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Owner read/write. A key file the rest of the box can read is a key file that has leaked. On
|
|
26
|
+
* Windows the mode is not what keeps other accounts out — the ACL is, and the writers set it.
|
|
27
|
+
*/
|
|
28
|
+
export const SECRETS_KEY_MODE = OWNER_ONLY_MODE;
|
|
26
29
|
|
|
27
30
|
export type MasterKeySource = 'env' | 'file';
|
|
28
31
|
|
|
@@ -118,7 +121,9 @@ export async function writeSecretsFile(
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
/**
|
|
121
|
-
* Write the master key at 0600
|
|
124
|
+
* Write the master key at 0600 — on Windows, with an ACL granting the current account alone
|
|
125
|
+
* (`X_SECRETS_KEY_ACL_FAILED` when `icacls` refuses). Callers must have made the ignore rule true
|
|
126
|
+
* first.
|
|
122
127
|
*
|
|
123
128
|
* Through a fresh temp file renamed over the target, never a write in place: `mode` applies only
|
|
124
129
|
* when a write CREATES the file, so rotating over a key that was 0644 left the new key 0644. The
|
|
@@ -126,14 +131,25 @@ export async function writeSecretsFile(
|
|
|
126
131
|
*/
|
|
127
132
|
export function writeMasterKeyFile(root: string, keyHex: string): string {
|
|
128
133
|
const path = masterKeyPath(root);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
134
|
+
writeOwnerOnlyFile(path, `${keyHex}\n`);
|
|
135
|
+
return path;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Stage a rotation's new key at `stagedMasterKeyPath`, owner-only like the live one. */
|
|
139
|
+
export function stageMasterKeyFile(root: string, keyHex: string): string {
|
|
140
|
+
const path = stagedMasterKeyPath(root);
|
|
141
|
+
writeOwnerOnlyFile(path, `${keyHex}\n`);
|
|
142
|
+
return path;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Make a staged key live: rename it over the key file. Retried briefly when Windows refuses the
|
|
147
|
+
* rename because something holds the old key open (EPERM/EBUSY) — the last step of a rotation is
|
|
148
|
+
* the wrong one to lose to an editor or a virus scan.
|
|
149
|
+
*/
|
|
150
|
+
export function promoteStagedMasterKey(root: string): string {
|
|
151
|
+
const path = masterKeyPath(root);
|
|
152
|
+
renameOver(stagedMasterKeyPath(root), path);
|
|
137
153
|
return path;
|
|
138
154
|
}
|
|
139
155
|
|
package/src/service.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
// Single responsibility: named service factories that `
|
|
1
|
+
// Single responsibility: named service factories that `ctxOf` installs automatically,
|
|
2
2
|
// bound to the exact ctx (actor, clock, tz) they were built for.
|
|
3
3
|
//
|
|
4
4
|
// A service closes over the `ctx` it was constructed with — `ctx.actor.orgId` inside it means
|
|
5
5
|
// "the actor this service was built for", not "whoever is calling right now". So a factory
|
|
6
|
-
// cannot be built once and cached: it has to run again every time `
|
|
6
|
+
// cannot be built once and cached: it has to run again every time `ctxOf` produces a
|
|
7
7
|
// ctx with a different actor, or an impersonated call would read the wrong tenant. Registering
|
|
8
|
-
// with `defineService` is what lets `
|
|
8
|
+
// with `defineService` is what lets `ctxOf` do that automatically instead of every
|
|
9
9
|
// caller wiring `services: { posts: postsService(ctx) }` by hand at every call site.
|
|
10
10
|
|
|
11
11
|
import type { CtxFacts, ServiceBag } from './context';
|
|
12
12
|
import { UltimateError } from './errors';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* `CtxFacts` and not `Ctx`: this factory runs INSIDE `
|
|
15
|
+
* `CtxFacts` and not `Ctx`: this factory runs INSIDE `ctxOf`, against a preview that
|
|
16
16
|
* carries no other registered service — which the paragraph above has always said and the type
|
|
17
|
-
* now enforces. It is also what lets `
|
|
17
|
+
* now enforces. It is also what lets `ctxOf` build that preview without an assertion.
|
|
18
18
|
*/
|
|
19
19
|
export type ServiceFactory<T = unknown> = (ctx: CtxFacts) => T;
|
|
20
20
|
|
package/src/single-flight.ts
CHANGED
|
@@ -56,7 +56,7 @@ const defaultScheduler: Scheduler = (fn, ms) => {
|
|
|
56
56
|
};
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
export function
|
|
59
|
+
export function singleFlight(options?: SingleFlightOptions): SingleFlight {
|
|
60
60
|
const inflight = new Map<string, Flight>();
|
|
61
61
|
const schedule = options?.schedule ?? defaultScheduler;
|
|
62
62
|
const deadlineMs = options?.deadlineMs;
|
package/src/telemetry.ts
CHANGED
|
@@ -340,7 +340,7 @@ const TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
|
340
340
|
|
|
341
341
|
/**
|
|
342
342
|
* Round-trips with `traceId()` / `spanId()` from `ids.ts` and with nothing else. A context whose
|
|
343
|
-
* `traceId` came from `
|
|
343
|
+
* `traceId` came from `uuidV7()` renders a 36-character dashed header here that `parseTraceparent`
|
|
344
344
|
* — and every OTLP collector — rejects, so mint the pair with those two generators.
|
|
345
345
|
*/
|
|
346
346
|
export function traceparent(context: SpanContext): string {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `localStorage` key a visitor's light/dark choice lives under. Tier 0 because the WRITER is
|
|
3
|
+
* `@ultimat3/ui`'s `ThemeToggle` and the READER is `@ultimat3/render`'s inlined boot script — two
|
|
4
|
+
* tier-4 packages that cannot import each other.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One declaration, imported by both. Until 25.0.0 each package carried its own literal, pinned
|
|
9
|
+
* equal by a test: the two disagreeing was exactly the bug — the boot read one key, the toggle
|
|
10
|
+
* wrote another, and a visitor's choice never survived a reload.
|
|
11
|
+
*/
|
|
12
|
+
export const THEME_STORAGE_KEY = 'ultimate.theme';
|
package/src/type-pins.ts
CHANGED
|
@@ -6,8 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
import type { Actor, ActorFactMap, FactKeysOf, FactMapOf } from './actor';
|
|
8
8
|
import type { CacheTierName } from './cache-vocabulary';
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
AppConfig,
|
|
11
|
+
AppConfigInput,
|
|
12
|
+
CacheConfig,
|
|
13
|
+
DatabaseConfig,
|
|
14
|
+
RealtimeConfig,
|
|
15
|
+
ThemeConfig,
|
|
16
|
+
} from './config';
|
|
10
17
|
import type { CtxPatch } from './context';
|
|
18
|
+
import type { Page } from './cursor-page';
|
|
11
19
|
import type { HydrateStrategy, OfflineStrategy, RenderMode } from './route-vocabulary';
|
|
12
20
|
|
|
13
21
|
/** Fails to compile when `T` is anything but `true`. The whole mechanism. */
|
|
@@ -102,6 +110,29 @@ type _DatabaseInputCarriesNoDeadField = Assert<
|
|
|
102
110
|
: false
|
|
103
111
|
>;
|
|
104
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The five keys deleted in 25.0.0 stay deleted — on the config an app reads AND the input it
|
|
115
|
+
* writes. `defineConfig` refuses each by name at run time (`config-removed.ts`); this is the build
|
|
116
|
+
* error, so re-declaring one fails `tsc -b` before any test runs. `locales` / `defaultLocale` were
|
|
117
|
+
* a second declaration of `defineCatalogs({ default })`; `defaultTimeZone` / `defaultCurrency`
|
|
118
|
+
* and `theme.tokens` were read by nothing.
|
|
119
|
+
*/
|
|
120
|
+
type RemovedTopLevelKey = 'locales' | 'defaultLocale' | 'defaultTimeZone' | 'defaultCurrency';
|
|
121
|
+
|
|
122
|
+
type _AppConfigCarriesNoRemovedKey = Assert<
|
|
123
|
+
Extract<keyof AppConfig, RemovedTopLevelKey> extends never ? true : false
|
|
124
|
+
>;
|
|
125
|
+
|
|
126
|
+
type _AppConfigInputCarriesNoRemovedKey = Assert<
|
|
127
|
+
Extract<keyof AppConfigInput, RemovedTopLevelKey> extends never ? true : false
|
|
128
|
+
>;
|
|
129
|
+
|
|
130
|
+
type _ThemeConfigIsDefaultModeAlone = Assert<Exact<keyof ThemeConfig, 'defaultMode'>>;
|
|
131
|
+
|
|
132
|
+
type _ThemeInputCarriesNoTokens = Assert<
|
|
133
|
+
'tokens' extends keyof NonNullable<AppConfigInput['theme']> ? false : true
|
|
134
|
+
>;
|
|
135
|
+
|
|
105
136
|
/**
|
|
106
137
|
* The two `config.cache` fields deleted 2026-08-22, held down for the reason the `database` three
|
|
107
138
|
* are: `cache.tiers` is what BUILDS the ladder, so `driver: 'redis'` was a second selector that
|
|
@@ -219,3 +250,22 @@ type _CacheConfigNamesTheLadder = Assert<Exact<CacheConfig['tiers'], readonly Ca
|
|
|
219
250
|
type _CacheInputNamesTheLadder = Assert<
|
|
220
251
|
Exact<NonNullable<AppConfigInput['cache']>['tiers'], readonly CacheTierName[] | undefined>
|
|
221
252
|
>;
|
|
253
|
+
|
|
254
|
+
// The one page shape (`cursor-page.ts`): the two facts cannot disagree in a value that
|
|
255
|
+
// typechecks, and `hasMore` narrows `nextCursor`.
|
|
256
|
+
|
|
257
|
+
// @ts-expect-error — `hasMore: true` with no cursor is not a page: "more" must say where.
|
|
258
|
+
export const _MoreWithoutCursor: Page<number> = { rows: [], nextCursor: null, hasMore: true };
|
|
259
|
+
|
|
260
|
+
// @ts-expect-error — a cursor on the last page is not a page: it would continue a `while` loop.
|
|
261
|
+
export const _LastWithCursor: Page<number> = { rows: [], nextCursor: 'c', hasMore: false };
|
|
262
|
+
|
|
263
|
+
/** On the `hasMore` side `nextCursor` is a `string` — a loop passes it on with no `?? ''`. */
|
|
264
|
+
export type _HasMoreNarrowsCursor = Assert<
|
|
265
|
+
Extract<Page<number>, { readonly hasMore: true }>['nextCursor'] extends string ? true : false
|
|
266
|
+
>;
|
|
267
|
+
|
|
268
|
+
/** On the last page it is exactly `null` — `while (page.nextCursor !== null)` is the same loop. */
|
|
269
|
+
export type _LastPageHasNoCursor = Assert<
|
|
270
|
+
Extract<Page<number>, { readonly hasMore: false }>['nextCursor'] extends null ? true : false
|
|
271
|
+
>;
|
package/src/image/fixtures.ts
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
// Single responsibility: byte-exact image fixtures produced by an INDEPENDENT encoder
|
|
2
|
-
// (Pillow 10.2 and ffmpeg), plus the pixels they must decode to. A codec that only
|
|
3
|
-
// round-trips against itself proves nothing — these bytes are the outside reference, so a
|
|
4
|
-
// decoder bug shows up as a pixel mismatch rather than a mutually agreed hallucination.
|
|
5
|
-
|
|
6
|
-
export interface ImageFixture {
|
|
7
|
-
/** The encoded file, base64. */
|
|
8
|
-
readonly base64: string;
|
|
9
|
-
readonly width: number;
|
|
10
|
-
readonly height: number;
|
|
11
|
-
/** Expected RGBA decode, row-major. Omitted for probe-only and lossy fixtures. */
|
|
12
|
-
readonly pixels?: readonly number[] | undefined;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const fixtureBytes = (fixture: ImageFixture): Uint8Array =>
|
|
16
|
-
Uint8Array.from(atob(fixture.base64), (c) => c.charCodeAt(0));
|
|
17
|
-
|
|
18
|
-
/** Truecolour + alpha (PNG colour type 6), including fully transparent and premature zeros. */
|
|
19
|
-
export const PNG_RGBA_4X4: ImageFixture = {
|
|
20
|
-
base64:
|
|
21
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAQUlEQVR4nAXBKQGAABAAweU5Po1GE4I4hEBeCpIQ' +
|
|
22
|
-
'gjjoM8sMgIJIgqC6rNv+3qdN2/VkpoqookREVFUd1/PN0zj84AIej7U060EAAAAASUVORK5CYII=',
|
|
23
|
-
width: 4,
|
|
24
|
-
height: 4,
|
|
25
|
-
pixels: [
|
|
26
|
-
0, 0, 0, 255, 255, 0, 0, 255, 0, 255, 0, 128, 0, 0, 255, 0, 255, 255, 255, 255, 10, 20, 30, 40,
|
|
27
|
-
200, 150, 100, 255, 1, 2, 3, 4, 128, 128, 128, 255, 255, 255, 0, 255, 0, 255, 255, 255, 255, 0,
|
|
28
|
-
255, 255, 5, 5, 5, 5, 250, 250, 250, 250, 60, 120, 180, 240, 9, 8, 7, 6,
|
|
29
|
-
],
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/** Truecolour, no alpha (colour type 2) — alpha must be synthesised as 255. */
|
|
33
|
-
export const PNG_RGB_3X2: ImageFixture = {
|
|
34
|
-
base64:
|
|
35
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAIAAAASFvFNAAAAF0lEQVR4nGP4z8DAAMFcInInUowYGBgANh8EmGKC' +
|
|
36
|
-
'N2kAAAAASUVORK5CYII=',
|
|
37
|
-
width: 3,
|
|
38
|
-
height: 2,
|
|
39
|
-
pixels: [
|
|
40
|
-
255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 10, 20, 30, 255, 200, 100, 50, 255, 0, 0, 0,
|
|
41
|
-
255,
|
|
42
|
-
],
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/** Greyscale (colour type 0) — one channel replicated across RGB. */
|
|
46
|
-
export const PNG_GRAY_2X2: ImageFixture = {
|
|
47
|
-
base64:
|
|
48
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAAAAABX3VL4AAAADklEQVR4nGNgCGVY9R8AA60B/2f7ygkAAAAASUVO' +
|
|
49
|
-
'RK5CYII=',
|
|
50
|
-
width: 2,
|
|
51
|
-
height: 2,
|
|
52
|
-
pixels: [0, 0, 0, 255, 85, 85, 85, 255, 170, 170, 170, 255, 255, 255, 255, 255],
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/** Indexed colour (type 3) with a `tRNS` alpha table — the entry PNG readers forget. */
|
|
56
|
-
export const PNG_PALETTE_4X1: ImageFixture = {
|
|
57
|
-
base64:
|
|
58
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAQAAAABCAMAAADO4v//AAADAFBMVEX/AAAA/wAAAP///wAAAAAAAAAAAAAAAAAA' +
|
|
59
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
60
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
61
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
62
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
63
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
64
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
65
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
66
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
67
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
68
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
69
|
-
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
|
|
70
|
-
'AAAAAAAAAAAAAAAAAAAAAAAX3fWLAAAABHRSTlP/gAD/oaGUZgAAAA1JREFUeJxjYGBkYgYAAA8AB4SOmW0AAAAA' +
|
|
71
|
-
'SUVORK5CYII=',
|
|
72
|
-
width: 4,
|
|
73
|
-
height: 1,
|
|
74
|
-
pixels: [255, 0, 0, 255, 0, 255, 0, 128, 0, 0, 255, 0, 255, 255, 0, 255],
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
/** Greyscale + alpha (colour type 4). */
|
|
78
|
-
export const PNG_GRAY_ALPHA_2X2: ImageFixture = {
|
|
79
|
-
base64:
|
|
80
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAQAAADYv8WvAAAAEklEQVR4nGNk+N/QyHCC4b8DABPwBAkrfI8bAAAA' +
|
|
81
|
-
'AElFTkSuQmCC',
|
|
82
|
-
width: 2,
|
|
83
|
-
height: 2,
|
|
84
|
-
pixels: [0, 0, 0, 255, 128, 128, 128, 128, 200, 200, 200, 0, 255, 255, 255, 64],
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/** 16 bits per channel — must be reduced to 8 by taking the high byte. */
|
|
88
|
-
export const PNG_GRAY16_2X2: ImageFixture = {
|
|
89
|
-
base64:
|
|
90
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAIAAAACEAAAAAAHTY67AAAAEklEQVR4nGNgYHBgYGhg+P8fAAbHAr89RY6RAAAA' +
|
|
91
|
-
'AElFTkSuQmCC',
|
|
92
|
-
width: 2,
|
|
93
|
-
height: 2,
|
|
94
|
-
pixels: [0, 0, 0, 255, 64, 64, 64, 255, 128, 128, 128, 255, 255, 255, 255, 255],
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/** Big enough that Pillow picks a different row filter per scanline — all five must work. */
|
|
98
|
-
export const PNG_GRADIENT_32X24: ImageFixture = {
|
|
99
|
-
base64:
|
|
100
|
-
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAIAAAAUMWhjAAAAuklEQVR4nLXSyw6DIBCF4WNrr/aiaNHamL7/W3aB' +
|
|
101
|
-
'GFDEgQ7JtxjGxR/UDMAppRwFgCydKbBLxAzsU5gFcnbLwIGXM3BktBZg+1E9gTMLf+Dyv83ANdagBkqgCNHPNsTA' +
|
|
102
|
-
'jaBz7umB+zrpeRoUeCw0rqUlNPA01PbRLSJQAiUg9LAhLlDRRQQEUAGC5hsaqPUNBFB7DWoICjTGK1KBxuVjHqO/' +
|
|
103
|
-
'wRR42frZhh6Q6wGpvY15xHIDCbRAB7RLPwPnHDcTUr+XAAAAAElFTkSuQmCC',
|
|
104
|
-
width: 32,
|
|
105
|
-
height: 24,
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/** The exact source of `PNG_GRADIENT_32X24` and the JPEG fixtures, as a formula. */
|
|
109
|
-
export const gradientPixel = (x: number, y: number): readonly [number, number, number] => [
|
|
110
|
-
(x * 7) % 256,
|
|
111
|
-
(y * 11) % 256,
|
|
112
|
-
(x * y) % 256,
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* A genuine Adam7 interlaced PNG (written by ImageMagick — Pillow's writer silently ignores
|
|
117
|
-
* `interlace=True`, which is why this one is not generated with the others). The pipeline
|
|
118
|
-
* refuses it with a coded error rather than garbling it.
|
|
119
|
-
*/
|
|
120
|
-
export const PNG_INTERLACED_8X8: ImageFixture = {
|
|
121
|
-
base64:
|
|
122
|
-
'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAAE8ahlKAAAAS0lEQVQI12NgYGBgqGFQYWQoUalhUGGwYxDaxWDG' +
|
|
123
|
-
'wlCiwsCgwshgJWTHAEUQMSEIYpRn4ESSsRKCy+DmMDLIcsozYEEQVZyYiHQJAE1EC4Be7arJAAAAAElFTkSuQmCC',
|
|
124
|
-
width: 8,
|
|
125
|
-
height: 8,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/** Baseline JPEG, 4:4:4, quality 95 — the closest a lossy codec gets to exact. */
|
|
129
|
-
export const JPEG_444_16X16: ImageFixture = {
|
|
130
|
-
base64:
|
|
131
|
-
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAIBAQEBAQIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYFBgYGBwkIBgcJ' +
|
|
132
|
-
'BwYGCAsICQoKCgoKBggLDAsKDAkKCgr/2wBDAQICAgICAgUDAwUKBwYHCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoK' +
|
|
133
|
-
'CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgr/wAARCAAQABADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA' +
|
|
134
|
-
'AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAk' +
|
|
135
|
-
'M2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT' +
|
|
136
|
-
'lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QA' +
|
|
137
|
-
'HwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdh' +
|
|
138
|
-
'cRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp' +
|
|
139
|
-
'anN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk' +
|
|
140
|
-
'5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD83/gx+yH/AKr/AIlfp/BRg8Z5h4eeIfwe+fW3wX/ZDz5X/Er9' +
|
|
141
|
-
'P4K+qweM21P7r8PPEP4PfPpT4L/sh48r/iV+n8FfleDxm2p/hT4eeIfwe+fW3wY/ZD/1X/Er9P4K+qweM8z+6/Dz' +
|
|
142
|
-
'xD+D3z//2Q==',
|
|
143
|
-
width: 16,
|
|
144
|
-
height: 16,
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
/** Baseline JPEG, 4:2:0 — chroma is half resolution and must be upsampled. */
|
|
148
|
-
export const JPEG_420_16X16: ImageFixture = {
|
|
149
|
-
base64:
|
|
150
|
-
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4R' +
|
|
151
|
-
'DgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU' +
|
|
152
|
-
'FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAAQABADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA' +
|
|
153
|
-
'AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAk' +
|
|
154
|
-
'M2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT' +
|
|
155
|
-
'lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QA' +
|
|
156
|
-
'HwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdh' +
|
|
157
|
-
'cRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp' +
|
|
158
|
-
'anN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk' +
|
|
159
|
-
'5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD4/wDBfwh/1f7j9K958F/CH/V/uP0r2PwX8If9X+4/SvefBfwh' +
|
|
160
|
-
'/wBX+4/SjB4zbUPDzxD+D3z/2Q==',
|
|
161
|
-
width: 16,
|
|
162
|
-
height: 16,
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
/** The exact source of the 16x16 JPEG fixtures, as a formula. */
|
|
166
|
-
export const jpegPixel = (x: number, y: number): readonly [number, number, number] => [
|
|
167
|
-
(x * 16) % 256,
|
|
168
|
-
(y * 16) % 256,
|
|
169
|
-
((x + y) * 8) % 256,
|
|
170
|
-
];
|
|
171
|
-
|
|
172
|
-
/** Odd dimensions with 4:2:0: the last MCU column and row are padded and must be cropped. */
|
|
173
|
-
export const JPEG_420_ODD_33X17: ImageFixture = {
|
|
174
|
-
base64:
|
|
175
|
-
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQDAwMDAgQDAwMEBAQFBgoGBgUFBgwICQcKDgwPDg4MDQ0PERYTDxAV' +
|
|
176
|
-
'EQ0NExoTFRcYGRkZDxIbHRsYHRYYGRj/2wBDAQQEBAYFBgsGBgsYEA0QGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgY' +
|
|
177
|
-
'GBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBj/wAARCAARACEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA' +
|
|
178
|
-
'AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAk' +
|
|
179
|
-
'M2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT' +
|
|
180
|
-
'lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QA' +
|
|
181
|
-
'HwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdh' +
|
|
182
|
-
'cRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp' +
|
|
183
|
-
'anN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk' +
|
|
184
|
-
'5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5Q0fwT5u39z+ld1pvw/iSISTIqL6nvXp/h3weiW/nSRfKoyeO' +
|
|
185
|
-
'tdhpvg/di6uot8jcJGBjPsPQf561rgMVg4YSnWrQU5zV9X7sY931d3oktZO6Tvv4XDfG0nJJTPLNO8D2h4hs5JTx' +
|
|
186
|
-
'g7dqn+v6VR+JHgq2Hgu0AtIUYX6AgkyEfu5OwGRX0dZeEGLhZ4ix2gmJDtRQOef59/5VQ+InhLb4LtDs+UXyZ8v5' +
|
|
187
|
-
'F/1cnAPU9OlZ0M4w6xkI8kY66+7HTfpZyVutpuS+0lu/1vNuNH/q7ivf+x380fHP/CERf8+8H/fl6K96/wCEIi/5' +
|
|
188
|
-
'94P+/L0V9J/bf/Til/4Gfz7/AK6f32eh6N/yA3/4D/6EK7LTv+Qlb/8AXIfzNFFfmMP4OE/wUv8A0uofC8M7/f8A' +
|
|
189
|
-
'kjpNG/5Ab/8AAf8A0IVB8RP+QBp3/XxH/wCgy0UV8/k/8bAf4aX/AKXUP2DOf+Sexv8Ahf6HjNFFFfmh+Hn/2Q==',
|
|
190
|
-
width: 33,
|
|
191
|
-
height: 17,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/** The exact source of `JPEG_420_ODD_33X17`, as a formula. */
|
|
195
|
-
export const oddJpegPixel = (x: number, y: number): readonly [number, number, number] => [
|
|
196
|
-
(x * 9) % 256,
|
|
197
|
-
(y * 13) % 256,
|
|
198
|
-
(x * y * 3) % 256,
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
/** Single-component (greyscale) JPEG. */
|
|
202
|
-
export const JPEG_GRAY_16X16: ImageFixture = {
|
|
203
|
-
base64:
|
|
204
|
-
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsO' +
|
|
205
|
-
'CwkJDRENDg8QEBEQCgwSExIQEw8QEBD/wAALCAAQABABAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcI' +
|
|
206
|
-
'CQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcY' +
|
|
207
|
-
'GRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKj' +
|
|
208
|
-
'pKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/APzK' +
|
|
209
|
-
'8P8Ah/8AtXb8mc+1el+H/gt/au3/AETOf9mj4LeH/wC1fI+TOcdq+9fgt8Fv7V8j/RM5x/DX/9k=',
|
|
210
|
-
width: 16,
|
|
211
|
-
height: 16,
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/** Progressive JPEG — refused with a coded error, never decoded as noise. */
|
|
215
|
-
export const JPEG_PROGRESSIVE_16X16: ImageFixture = {
|
|
216
|
-
base64:
|
|
217
|
-
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4R' +
|
|
218
|
-
'DgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU' +
|
|
219
|
-
'FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAAQABADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAA' +
|
|
220
|
-
'Bgf/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAGPvGTw/8QAFhAAAwAAAAAAAAAAAAAAAAAAAAQF' +
|
|
221
|
-
'/9oACAEBAAEFAkpAlIEpAlIP/8QAFxEBAAMAAAAAAAAAAAAAAAAABgAhMf/aAAgBAwEBPwE8hy5//8QAFREBAQAA' +
|
|
222
|
-
'AAAAAAAAAAAAAAAAAwD/2gAIAQIBAT8BFr//xAAWEAADAAAAAAAAAAAAAAAAAAAAASH/2gAIAQEABj8CUFBQUP/E' +
|
|
223
|
-
'ABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEAAT8hCqq//9oADAMBAAIAAwAAABBT/8QAFBEBAAAAAAAAAAAAAAAA' +
|
|
224
|
-
'AAAAAP/aAAgBAwEBPxB//8QAFhEAAwAAAAAAAAAAAAAAAAAAACEx/9oACAECAQE/EIM//8QAFRABAQAAAAAAAAAA' +
|
|
225
|
-
'AAAAAAAAAPH/2gAIAQEAAT8QgoKCgv/Z',
|
|
226
|
-
width: 16,
|
|
227
|
-
height: 16,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
/** Probe-only: GIF87a/89a header. */
|
|
231
|
-
export const GIF_5X7: ImageFixture = {
|
|
232
|
-
base64: 'R0lGODdhBQAHAIEAAAAAAAAAAAAAAAAAACwAAAAABQAHAAAIDAABCBxIsKDBgwcDAgA7',
|
|
233
|
-
width: 5,
|
|
234
|
-
height: 7,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
/** Probe-only: lossy VP8 inside RIFF/WEBP. */
|
|
238
|
-
export const WEBP_9X11: ImageFixture = {
|
|
239
|
-
base64: 'UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoJAAsAAUAmJaQAA3AA/v02aAA=',
|
|
240
|
-
width: 9,
|
|
241
|
-
height: 11,
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
/** Probe-only: AVIF, dimensions read from the `ispe` property box. */
|
|
245
|
-
export const AVIF_12X16: ImageFixture = {
|
|
246
|
-
base64:
|
|
247
|
-
'AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAAD5bWV0YQAAAAAAAAAvaGRscgAAAAAAAAAAcGljdAAA' +
|
|
248
|
-
'AAAAAAAAAAAAAFBpY3R1cmVIYW5kbGVyAAAAAA5waXRtAAAAAAABAAAAHmlsb2MAAAAARAAAAQABAAAAAQAAASEA' +
|
|
249
|
-
'AAAiAAAAKGlpbmYAAAAAAAEAAAAaaW5mZQIAAAAAAQAAYXYwMUNvbG9yAAAAAGppcHJwAAAAS2lwY28AAAAUaXNw' +
|
|
250
|
-
'ZQAAAAAAAAAMAAAAEAAAABBwaXhpAAAAAAMICAgAAAAMYXYxQ4EADAAAAAATY29scm5jbHgAAgACAAIAAAAAF2lw' +
|
|
251
|
-
'bWEAAAAAAAAAAQABBAECgwQAAAAqbWRhdAoKAAAAAZ35//MAgDIUEADAAAACgAAAAKmObLaqsca2hUA=',
|
|
252
|
-
width: 12,
|
|
253
|
-
height: 16,
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
/** Probe-only: SVG is vector — it has a declared box but no raster to decode. */
|
|
257
|
-
export const SVG_120X45: ImageFixture = {
|
|
258
|
-
base64:
|
|
259
|
-
'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iNDUiIHZp' +
|
|
260
|
-
'ZXdCb3g9IjAgMCAxMjAgNDUiPjwvc3ZnPg==',
|
|
261
|
-
width: 120,
|
|
262
|
-
height: 45,
|
|
263
|
-
};
|
package/src/time-zone-name.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// Single responsibility: re-export `@ultimat3/schema`'s IANA zone-name predicate, so `app.config.ts`
|
|
2
|
-
// and `t.timezone` judge a zone with ONE function.
|
|
3
|
-
//
|
|
4
|
-
// This file held a character-for-character copy of it until 2026-08-27 — `core` and `schema` are
|
|
5
|
-
// both tier 0 and neither could import the other — kept equal by a 123-line pin test in
|
|
6
|
-
// `@ultimat3/cli`, a tier-5 package pinning a tier-0 invariant that no rule required to exist.
|
|
7
|
-
// `core -> schema` is now a declared edge (`scripts/lib/tiers.ts`), so the copy is gone.
|
|
8
|
-
//
|
|
9
|
-
// The rule itself, and why a bare `new Intl.DateTimeFormat(…)` probe is not it — ICU 78 resolves
|
|
10
|
-
// `CET`, `EST`, `Japan`, `GMT` and `Zulu`, so `defaultTimeZone: 'CET'` passed validation at boot
|
|
11
|
-
// and threw `X_TIMEZONE_INVALID` on the first `format` call (issue #257) — is written down in
|
|
12
|
-
// `packages/schema/src/time-zone-name.ts` and in `packages/time/src/zone-canonical.ts`.
|
|
13
|
-
|
|
14
|
-
export { isIanaZoneName } from '@ultimat3/schema';
|