@ultimat3/core 22.12.0 → 22.13.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-islands.ts +46 -0
- package/src/config.ts +18 -20
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/core",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.13.0",
|
|
4
4
|
"description": "Ultimate's foundation: errors, context, env, config, clock, ids, logging, telemetry, lifecycle",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,6 @@
|
|
|
40
40
|
"test": "bun test"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ultimat3/schema": "22.
|
|
43
|
+
"@ultimat3/schema": "22.13.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Single responsibility: the `islands` block of `app.config.ts` — how the build bundles the app's
|
|
2
|
+
// `*.island.tsx` client entries (`@ultimat3/cli`'s `island-bundle.ts`). Its own file for the
|
|
3
|
+
// reason `config-navigation.ts` is: shape, merge and screen are one subject, and `config.ts` is at
|
|
4
|
+
// its 500-line ceiling.
|
|
5
|
+
|
|
6
|
+
import { describeValue } from './error-render';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `sharedChunks: true` builds every island in ONE split bundle, so a module two islands import is
|
|
10
|
+
* one shared chunk a page fetches once and a browser caches across pages. OFF BY DEFAULT, and the
|
|
11
|
+
* reason is measured rather than cautious: tree shaking across one split build keeps whatever ANY
|
|
12
|
+
* importer uses, so an island that imports one helper from a module other islands use heavily
|
|
13
|
+
* pays for all of it. `examples/dummy`'s plain-DOM update banner went 712 → 16,288 B with it on;
|
|
14
|
+
* notificado.co's `/afiliados` with two upload islands went 55,585 → 42,331 B. Turn it on where a
|
|
15
|
+
* surface's pages render many islands over one graph, and weigh the routes with `x verify`.
|
|
16
|
+
*/
|
|
17
|
+
export interface IslandsConfig {
|
|
18
|
+
readonly sharedChunks: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IslandsSection {
|
|
22
|
+
readonly islands: IslandsConfig;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IslandsSectionInput {
|
|
26
|
+
readonly islands?: { readonly sharedChunks?: boolean | undefined } | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The last layer that said it wins, as every scalar does. */
|
|
30
|
+
export function mergeIslands(layers: readonly IslandsSectionInput[]): IslandsSection {
|
|
31
|
+
let sharedChunks = false;
|
|
32
|
+
for (const layer of layers) {
|
|
33
|
+
const said = layer.islands?.sharedChunks;
|
|
34
|
+
if (said !== undefined) sharedChunks = said;
|
|
35
|
+
}
|
|
36
|
+
return { islands: { sharedChunks } };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Appends every refusal the section earns to `issues`, `config.ts`' one list. */
|
|
40
|
+
export function islandsIssues(config: IslandsSection, issues: string[]): void {
|
|
41
|
+
// `unknown`: an untyped config file reaches this validator with whatever it wrote.
|
|
42
|
+
const said: unknown = config.islands.sharedChunks;
|
|
43
|
+
if (typeof said !== 'boolean') {
|
|
44
|
+
issues.push(`islands.sharedChunks must be true or false, not ${describeValue(said)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -11,6 +11,8 @@ import { countIssue } from './config-count';
|
|
|
11
11
|
import { BASE_FIX, CACHE_TIER_FIX, TIMEZONE_FIX } from './config-fixes';
|
|
12
12
|
import type { DrainConfig, HealthConfig } from './config-health';
|
|
13
13
|
import { readinessModeIssue } from './config-health';
|
|
14
|
+
import type { IslandsConfig, IslandsSectionInput } from './config-islands';
|
|
15
|
+
import { islandsIssues, mergeIslands } from './config-islands';
|
|
14
16
|
import { type Input, lastSaid, layered } from './config-merge';
|
|
15
17
|
import type { NavigationConfig, NavigationSectionInput } from './config-navigation';
|
|
16
18
|
import { mergeNavigation, navigationIssues } from './config-navigation';
|
|
@@ -46,10 +48,8 @@ export interface ThemeConfig {
|
|
|
46
48
|
* every unauthenticated visitor to a 404. Null means the visitor gets the problem document — the
|
|
47
49
|
* right answer for an agent, and what a browser got in production until this existed.
|
|
48
50
|
*
|
|
49
|
-
* `afterSignInPath` was removed 2026-08
|
|
50
|
-
*
|
|
51
|
-
* set `/dashboard` and got whatever its sign-in route did on its own. The landing path belongs to
|
|
52
|
-
* the app's sign-in route, which is the only code that can honour it.
|
|
51
|
+
* `afterSignInPath` was removed 2026-08, like `database.urlEnv` (below): read by NO file. The
|
|
52
|
+
* landing path belongs to the app's sign-in route, the only code that can honour it.
|
|
53
53
|
*/
|
|
54
54
|
export interface AuthConfig {
|
|
55
55
|
readonly signInPath: string | null;
|
|
@@ -92,11 +92,8 @@ export const INBOX_RETENTION_KEYS = ['inboxReadRetentionMs', 'inboxUnreadRetenti
|
|
|
92
92
|
* read them** — the only reader of any `config.database.*` field in the repo was this file's own
|
|
93
93
|
* validator, and each of the three was unfixable where it sat:
|
|
94
94
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* - `urlEnv` — `client.ts` reads `process.env['DATABASE_URL']` as a hardcoded literal, so a
|
|
98
|
-
* different key here could not be honoured.
|
|
99
|
-
* - `schema` — nothing emits `SET search_path`.
|
|
95
|
+
* `poolSize` was a dead second spelling of `DATABASE_POOL_MAX`; `client.ts` reads `DATABASE_URL`
|
|
96
|
+
* as a literal, so no `urlEnv` could be honoured; nothing emits `SET search_path` for `schema`.
|
|
100
97
|
*
|
|
101
98
|
* Wiring them instead would need a tier-0 → tier-1 read the tier table forbids. Deleting is axiom
|
|
102
99
|
* 3 applied to configuration: a value that produces neither a build error nor a runtime effect is
|
|
@@ -154,15 +151,9 @@ export interface JobsConfig {
|
|
|
154
151
|
* `max(1000, floor(ttlMs / 3))`). A second knob is a second number that can disagree with the one
|
|
155
152
|
* it is a fraction of, and a knob nothing reads is a knob nothing enforces — axioms 1 and 3.
|
|
156
153
|
*
|
|
157
|
-
* No `tier` either
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* per-value semantics, was set by both tracked apps — and no file anywhere compared it, branched
|
|
161
|
-
* on it or dereferenced it. `transport` and `urlEnv` are the only two fields of this section any
|
|
162
|
-
* code reads. So `tier: 'local-first'` bought the durable client store that does not exist
|
|
163
|
-
* (`createOpfsLocalStore` still throws `X_NOT_IMPLEMENTED`), exactly as `jobs: { driver: 'redis' }`
|
|
164
|
-
* bought Postgres. Which realtime tier an app is on is decided by what it DECLARES — a `channel()`
|
|
165
|
-
* topic, a `live: true` query, a local store — never by a config key.
|
|
154
|
+
* No `tier` either (deleted 2026-08-23): no file read it, so `tier: 'local-first'` bought nothing.
|
|
155
|
+
* An app's realtime tier is what it DECLARES — a `channel()` topic, a `live: true` query, a local
|
|
156
|
+
* store — never a config key; `transport` and `urlEnv` are the only fields any code reads.
|
|
166
157
|
*/
|
|
167
158
|
export interface RealtimeConfig {
|
|
168
159
|
readonly enabled: boolean;
|
|
@@ -208,6 +199,7 @@ export interface AppConfig {
|
|
|
208
199
|
readonly site: SiteConfig;
|
|
209
200
|
readonly seo: SeoConfig;
|
|
210
201
|
readonly navigation: NavigationConfig;
|
|
202
|
+
readonly islands: IslandsConfig;
|
|
211
203
|
}
|
|
212
204
|
|
|
213
205
|
/** `mcp` is the only member, and it is NESTED — `Input<AiConfig>` would make it all-or-nothing. */
|
|
@@ -226,7 +218,10 @@ export interface PwaConfigInput extends Omit<Input<PwaConfig>, 'offline'> {
|
|
|
226
218
|
readonly offline?: Input<PwaOfflineConfig> | undefined;
|
|
227
219
|
}
|
|
228
220
|
|
|
229
|
-
export interface AppConfigInput
|
|
221
|
+
export interface AppConfigInput
|
|
222
|
+
extends SiteSectionsInput,
|
|
223
|
+
NavigationSectionInput,
|
|
224
|
+
IslandsSectionInput {
|
|
230
225
|
readonly name: string;
|
|
231
226
|
readonly locales?: readonly string[] | undefined;
|
|
232
227
|
readonly defaultLocale?: string | undefined;
|
|
@@ -268,7 +263,8 @@ function isLocale(value: string): boolean {
|
|
|
268
263
|
}
|
|
269
264
|
}
|
|
270
265
|
|
|
271
|
-
|
|
266
|
+
type Sectioned = 'name' | 'site' | 'seo' | 'navigation' | 'islands';
|
|
267
|
+
function defaults(name: string): Omit<AppConfig, Sectioned> {
|
|
272
268
|
return {
|
|
273
269
|
locales: ['en'],
|
|
274
270
|
defaultLocale: 'en',
|
|
@@ -380,6 +376,7 @@ function validate(config: AppConfig): void {
|
|
|
380
376
|
if (pwaIssues(config.pwa, issues)) pwaFix.push(PWA_FIX);
|
|
381
377
|
siteIssues(config, issues);
|
|
382
378
|
navigationIssues(config, issues);
|
|
379
|
+
islandsIssues(config, issues);
|
|
383
380
|
|
|
384
381
|
// A rung the ladder cannot build is the defect this key had: `sortTiers` places a name by its
|
|
385
382
|
// index in `CACHE_TIERS`, and a name missing from it sorts to `-1` — AHEAD of the request memo.
|
|
@@ -493,6 +490,7 @@ export function defineConfig(
|
|
|
493
490
|
),
|
|
494
491
|
...mergeSite(layers),
|
|
495
492
|
...mergeNavigation(layers),
|
|
493
|
+
...mergeIslands(layers),
|
|
496
494
|
};
|
|
497
495
|
|
|
498
496
|
validate(config);
|
package/src/index.ts
CHANGED
|
@@ -113,6 +113,7 @@ export type {
|
|
|
113
113
|
export { defineConfig, INBOX_RETENTION_KEYS } from './config';
|
|
114
114
|
export type { DrainConfig, HealthConfig, ReadinessMode } from './config-health';
|
|
115
115
|
export { READINESS_MODES } from './config-health';
|
|
116
|
+
export type { IslandsConfig, IslandsSection, IslandsSectionInput } from './config-islands';
|
|
116
117
|
export type {
|
|
117
118
|
NavigationConfig,
|
|
118
119
|
NavigationSection,
|