@ultimat3/core 18.0.0 → 19.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/package.json +2 -2
- package/src/config-pwa.ts +36 -5
- package/src/config.ts +21 -4
- package/src/index.ts +13 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.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",
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"test": "bun test"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@ultimat3/schema": "
|
|
40
|
+
"@ultimat3/schema": "19.0.0"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/config-pwa.ts
CHANGED
|
@@ -11,18 +11,37 @@
|
|
|
11
11
|
// subjects (a config file, and a function argument a library caller supplies directly).
|
|
12
12
|
|
|
13
13
|
import { describeValue } from './error-render';
|
|
14
|
-
// `app.config.ts` CONSUMES the route vocabulary; it does not own it — `config.ts`'s rule, and the
|
|
15
|
-
// reason `OfflineStrategy` is imported rather than restated.
|
|
16
|
-
import type { OfflineStrategy } from './route-vocabulary';
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
16
|
* `installPrompt` was removed 2026-08, same rule: `@ultimat3/pwa`'s `createInstallController` is
|
|
20
17
|
* real and complete, nothing ever threaded the flag into it, and both tracked apps plus every
|
|
21
18
|
* scaffolded app set a switch with no wire. Call the controller from your own affordance instead.
|
|
22
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* What the service worker needs that no route can say for itself.
|
|
22
|
+
*
|
|
23
|
+
* `fallback` is the document a navigation gets when the network is gone and the cache has no
|
|
24
|
+
* answer — the one thing an offline app cannot do without, and the reason this block replaced a
|
|
25
|
+
* bare `offline: OfflineStrategy`. That key was an app-wide DEFAULT for a field `defineRoute`
|
|
26
|
+
* makes **required** on every route (`route.ts` refuses a route without one), so it defaulted
|
|
27
|
+
* nothing, was read by nobody, and could not be given a reader without inventing a meaning for it
|
|
28
|
+
* (#390).
|
|
29
|
+
*
|
|
30
|
+
* The other three are `@ultimat3/pwa`'s `OfflineConfig` verbatim, and each is read by the emitter
|
|
31
|
+
* that builds `sw.js`: a placeholder image, a placeholder font, and the request patterns that must
|
|
32
|
+
* never be answered from a cache — auth and payments, where a stale 200 is worse than a failure.
|
|
33
|
+
*/
|
|
34
|
+
export interface PwaOfflineConfig {
|
|
35
|
+
/** Absolute route path of the offline document, e.g. `/offline`. Required once `enabled`. */
|
|
36
|
+
readonly fallback: string | null;
|
|
37
|
+
readonly image: string | null;
|
|
38
|
+
readonly font: string | null;
|
|
39
|
+
readonly neverCache: readonly string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
export interface PwaConfig {
|
|
24
43
|
readonly enabled: boolean;
|
|
25
|
-
readonly offline:
|
|
44
|
+
readonly offline: PwaOfflineConfig;
|
|
26
45
|
readonly backgroundSync: boolean;
|
|
27
46
|
readonly push: boolean;
|
|
28
47
|
/**
|
|
@@ -79,7 +98,7 @@ export const PWA_COLOR_KEYS = ['themeColor', 'backgroundColor'] as const;
|
|
|
79
98
|
* boolean needs to see the complete shape and the opt-out in the same line.
|
|
80
99
|
*/
|
|
81
100
|
export const PWA_FIX =
|
|
82
|
-
"in app.config.ts, complete the pwa block: pwa: { enabled: true, offline: '
|
|
101
|
+
"in app.config.ts, complete the pwa block: pwa: { enabled: true, offline: { fallback: '/offline' }, name: 'My App', colors: { light: { themeColor: '#1b1f3b', backgroundColor: '#ffffff' }, dark: { themeColor: '#1b1f3b', backgroundColor: '#0b0d1a' } } } — a browser paints the install splash and the address bar from those four values before any stylesheet loads, so there is nothing for the framework to derive them from, and offline.fallback is the document the emitted sw.js serves when the network is gone, so add a route at that path too; or set pwa.enabled: false";
|
|
83
102
|
|
|
84
103
|
/**
|
|
85
104
|
* Every rule `validate` applies to the block, appended to the caller's own issue list. Answers
|
|
@@ -96,6 +115,18 @@ export function pwaIssues(pwa: PwaConfig, issues: string[]): boolean {
|
|
|
96
115
|
if (typeof name !== 'string' || name.trim() === '') {
|
|
97
116
|
issues.push(`pwa.name is required when pwa.enabled is true, and is ${describeValue(name)}`);
|
|
98
117
|
}
|
|
118
|
+
// An ABSOLUTE path, screened here rather than at emit for this file's own stated reason: a
|
|
119
|
+
// relative one resolves against whatever document registered the worker, so `offline` served
|
|
120
|
+
// under `/posts/1` is `/posts/offline` — a 404 cached as the answer to every offline navigation.
|
|
121
|
+
// `pwa.enabled` means an installable app, and an installable app that shows the browser's error
|
|
122
|
+
// page offline is the failure the whole block exists to prevent, so this is required rather
|
|
123
|
+
// than optional: the alternative is two meanings for one switch (axiom 1).
|
|
124
|
+
const fallback: unknown = pwa.offline?.fallback;
|
|
125
|
+
if (typeof fallback !== 'string' || !fallback.startsWith('/')) {
|
|
126
|
+
issues.push(
|
|
127
|
+
`pwa.offline.fallback is required when pwa.enabled is true and must be an absolute route path like "/offline", and is ${describeValue(fallback)}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
99
130
|
// `typeof !== 'object' || null`, never `=== undefined`: an untyped `app.config.ts` writing
|
|
100
131
|
// `pwa.colors: null` reached `null[scheme]` one line down and took the boot out with a native
|
|
101
132
|
// `TypeError`, from the validator whose whole job is producing an instruction instead of one.
|
package/src/config.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { CURRENCY_CODE_PATTERN } from '@ultimat3/schema';
|
|
|
7
7
|
// them. Declaring them here is what let `cache.tiers` and the ladder `@ultimat3/cache` orders by
|
|
8
8
|
// drift into two vocabularies with no map between them (issue #293).
|
|
9
9
|
import { CACHE_TIERS, type CacheTierName } from './cache-vocabulary';
|
|
10
|
-
import type { PwaConfig } from './config-pwa';
|
|
10
|
+
import type { PwaConfig, PwaOfflineConfig } from './config-pwa';
|
|
11
11
|
import { PWA_FIX, pwaIssues } from './config-pwa';
|
|
12
12
|
import { describeValue } from './error-render';
|
|
13
13
|
import { ConfigInvalidError } from './errors';
|
|
@@ -197,6 +197,17 @@ export interface AiConfigInput {
|
|
|
197
197
|
readonly mcp?: Input<McpConfig> | undefined;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* `offline` is NESTED for `AiConfigInput`'s reason, and here it is load-bearing rather than
|
|
202
|
+
* ergonomic: `section` applies a patch ONE level deep, so an app writing
|
|
203
|
+
* `offline: { fallback: '/offline' }` under a flat `Input<PwaConfig>` would replace the whole
|
|
204
|
+
* default block — leaving `image`, `font` and `neverCache` absent at run time while the type says
|
|
205
|
+
* they are there, which is the exact shape of defect this repo keeps re-shipping.
|
|
206
|
+
*/
|
|
207
|
+
export interface PwaConfigInput extends Omit<Input<PwaConfig>, 'offline'> {
|
|
208
|
+
readonly offline?: Input<PwaOfflineConfig> | undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
200
211
|
export interface AppConfigInput {
|
|
201
212
|
readonly name: string;
|
|
202
213
|
readonly locales?: readonly string[] | undefined;
|
|
@@ -205,7 +216,7 @@ export interface AppConfigInput {
|
|
|
205
216
|
readonly defaultCurrency?: string | undefined;
|
|
206
217
|
readonly theme?: Input<ThemeConfig> | undefined;
|
|
207
218
|
readonly auth?: Input<AuthConfig> | undefined;
|
|
208
|
-
readonly pwa?:
|
|
219
|
+
readonly pwa?: PwaConfigInput | undefined;
|
|
209
220
|
readonly roles?: readonly Role[] | undefined;
|
|
210
221
|
readonly database?: Input<DatabaseConfig> | undefined;
|
|
211
222
|
readonly cache?: Input<CacheConfig> | undefined;
|
|
@@ -260,7 +271,7 @@ function defaults(name: string): Omit<AppConfig, 'name'> {
|
|
|
260
271
|
auth: { signInPath: null },
|
|
261
272
|
pwa: {
|
|
262
273
|
enabled: false,
|
|
263
|
-
offline:
|
|
274
|
+
offline: { fallback: null, image: null, font: null, neverCache: [] },
|
|
264
275
|
backgroundSync: false,
|
|
265
276
|
push: false,
|
|
266
277
|
name: '',
|
|
@@ -405,7 +416,13 @@ export function defineConfig(
|
|
|
405
416
|
defaultCurrency: merged.defaultCurrency ?? base.defaultCurrency,
|
|
406
417
|
theme: section(base.theme, merged.theme),
|
|
407
418
|
auth: section(base.auth, merged.auth),
|
|
408
|
-
|
|
419
|
+
// Two `section` calls, one per level: the outer one may not see `offline` at all, or it would
|
|
420
|
+
// drop the nested defaults `PwaConfigInput` exists to keep. Hence the cast — the outer patch is
|
|
421
|
+
// this block minus the key the inner call owns.
|
|
422
|
+
pwa: {
|
|
423
|
+
...section(base.pwa, { ...merged.pwa, offline: undefined } as Input<PwaConfig>),
|
|
424
|
+
offline: section(base.pwa.offline, merged.pwa?.offline),
|
|
425
|
+
},
|
|
409
426
|
roles: merged.roles ?? base.roles,
|
|
410
427
|
database: section(base.database, merged.database),
|
|
411
428
|
cache: section(base.cache, merged.cache),
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
// secrets — because each is one subject spread over a dozen modules. Every name they carry is
|
|
6
6
|
// still written out below: `export *` would make the contract something a reader has to resolve.
|
|
7
7
|
|
|
8
|
+
// Anchored on purpose, and NOT by the `sideEffects` array: Bun reads any array as `false` and drops
|
|
9
|
+
// the module regardless (oven-sh/bun#40650). This module registers @ultimat3/schema's error TITLES,
|
|
10
|
+
// because schema is tier 0 and cannot register its own — and what reads them is `UltimateError`'s
|
|
11
|
+
// constructor, which never imports this file. Shaken out, every X_VALIDATION_FAILED renders
|
|
12
|
+
// untitled in the browser with nothing to say why. `SIDE_EFFECTS_ANCHORS` carries the argument and
|
|
13
|
+
// `bun run side-effects` enforces it. `context.ts`, `lifecycle-errors.ts` and `secrets-errors.ts`
|
|
14
|
+
// are declared side-effecting too and are deliberately NOT anchored: each is reached by whatever
|
|
15
|
+
// uses it, and anchoring `context.ts` alone measured +3,485 B on a browser chunk for a provider a
|
|
16
|
+
// browser can never fire.
|
|
17
|
+
import './schema-error-codes';
|
|
18
|
+
|
|
8
19
|
export type {
|
|
9
20
|
Actor,
|
|
10
21
|
ActorFactKey,
|
|
@@ -66,13 +77,14 @@ export type {
|
|
|
66
77
|
JobsConfig,
|
|
67
78
|
McpConfig,
|
|
68
79
|
NotifyConfig,
|
|
80
|
+
PwaConfigInput,
|
|
69
81
|
RealtimeConfig,
|
|
70
82
|
RealtimeTransport,
|
|
71
83
|
ThemeConfig,
|
|
72
84
|
ThemeMode,
|
|
73
85
|
} from './config';
|
|
74
86
|
export { defineConfig, INBOX_RETENTION_KEYS } from './config';
|
|
75
|
-
export type { PwaColors, PwaConfig, PwaSchemeColors } from './config-pwa';
|
|
87
|
+
export type { PwaColors, PwaConfig, PwaOfflineConfig, PwaSchemeColors } from './config-pwa';
|
|
76
88
|
export { PWA_COLOR_KEYS, PWA_SCHEMES } from './config-pwa';
|
|
77
89
|
export type { Ctx, CtxFacts, CtxInit, CtxPatch, CtxServices, ServiceBag } from './context';
|
|
78
90
|
export {
|