@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/core",
3
- "version": "22.12.0",
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.12.0"
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 for the reason `urlEnv`, `poolSize` and `schema` were
50
- * (below): accepted, defaulted and merged here, and read by NO file — `dummy/social-media-clone`
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
- * - `poolSize` — `@ultimat3/db`'s `baseClient()` layers `DATABASE_POOL_MAX` over the role profile,
96
- * so the knob works; this was a second, non-functioning spelling of it.
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, and no `RealtimeTier` — deleted 2026-08-23, the thirteenth instance of the
158
- * same defect and the dangerous shape of it. It accepted
159
- * `'channels' | 'live-queries' | 'local-first'`, defaulted to `'channels'`, was documented with
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 extends SiteSectionsInput, NavigationSectionInput {
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
- function defaults(name: string): Omit<AppConfig, 'name' | 'site' | 'seo' | 'navigation'> {
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,