@ultimat3/render 19.2.0 → 19.3.1
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 +1 -0
- package/package.json +5 -5
- package/src/code-unit-order.ts +18 -0
- package/src/registry.ts +4 -1
- package/src/route-component.ts +6 -2
- package/src/surfaces.ts +5 -1
package/CLAUDE.md
CHANGED
|
@@ -69,6 +69,7 @@ for the mirror-image reason, keeping `render → pwa` refused. Never `cli` (upwa
|
|
|
69
69
|
| Route filename | `page.tsx` under `site/`/`app/`, `route.ts` under `api/` — `ROUTE_FILENAME`, one per surface. The URL is the directory path. Anything else is `X_ROUTE_FILE_INVALID`; never widen the table to accept a second spelling. |
|
|
70
70
|
| Registry input | descriptors only. `registerRoute` refuses a raw declaration with `X_ROUTE_UNNORMALIZED` — `defineRoute` is the one normalizer of everything the declaration alone decides, and every reader downstream assumes it ran. The registry fills in exactly one value on top: the island budget, which needs the surface, which is a fact of the file path only the route table reads. |
|
|
71
71
|
| Descriptors | `describeRoutes()` must stay JSON-safe, sorted by path, deterministic. |
|
|
72
|
+
| Every string order in this package | `byCodeUnit` (`code-unit-order.ts`), never `localeCompare`. With no locale argument `localeCompare` answers from the runtime's ICU **default locale** and **collation version**: `'/A'` sorted after `'/a'` on one machine and before it on the next, and `'/zoo'` before `'/ärzte'` under `sv-SE` but after it under `en-US` — for the same route table. Three sites shipped it: `routeEntries()` (so `describeRoutes()`, and with it `x.manifest.json`, the sitemap and `sw.js`'s rule table), `checkSurfaceBoundary`'s report, and `pageComponentOf`'s fallback, whose own comment promised "the same one on every machine". Package-internal on purpose — a comparator is not public API. Same rule `@ultimat3/pwa`'s `precache.ts` and `service-worker.ts` state for the artifact they emit. |
|
|
72
73
|
| Boundary | `surfaces.ts` throws; it never warns. Type-only edges are not violations. |
|
|
73
74
|
| Stream cancellation | the underlying source has a `cancel()`, and `write` is guarded on it. A client that disconnects mid-stream aborts `StreamHole.resolve(signal)` and every later `write`/`close` is a no-op — `settle()` on a cancelled controller threw out of a `void`ed promise, one unhandled rejection per response, while the resolved holes kept doing their database work with nowhere to write. |
|
|
74
75
|
| ISR detach | `attach()`'s returned function clears the revalidator as well as the dependents — and only if the slot is still its own, tracked in `installedRevalidator` because `@ultimat3/cache` holds ONE and offers no read back. Left installed, a detached controller and its whole store stayed reachable and kept receiving revalidations while the live one's pages never went stale. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/render",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.3.1",
|
|
4
4
|
"description": "The route primitive and the five render modes: static, isr, ssr, stream, spa.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"test": "bun test"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@ultimat3/cache": "19.
|
|
40
|
-
"@ultimat3/core": "19.
|
|
41
|
-
"@ultimat3/i18n": "19.
|
|
42
|
-
"@ultimat3/seo": "19.
|
|
39
|
+
"@ultimat3/cache": "19.3.1",
|
|
40
|
+
"@ultimat3/core": "19.3.1",
|
|
41
|
+
"@ultimat3/i18n": "19.3.1",
|
|
42
|
+
"@ultimat3/seo": "19.3.1",
|
|
43
43
|
"sass": "1.102.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one string comparator this package orders derived output with.
|
|
3
|
+
*
|
|
4
|
+
* Package-internal on purpose: it is the ordering rule, not public API. Nothing outside
|
|
5
|
+
* `@ultimat3/render` may need it, and a comparator on the barrel is a shape apps would depend on.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compare by UTF-16 code unit — never `localeCompare`, which with no locale argument answers from
|
|
10
|
+
* the runtime's ICU **default locale** and **collation version**: `'/A'` sorts after `'/a'` on one
|
|
11
|
+
* machine and before it on the next, and `'/zoo'` before `'/ärzte'` under `sv-SE` but after it
|
|
12
|
+
* under `en-US`, for the same input. Everything this package orders is either diffed across
|
|
13
|
+
* deploys (`describeRoutes()` feeds `x.manifest.json`, the sitemap and `sw.js`'s rule table) or is
|
|
14
|
+
* a choice a build makes (`pageComponentOf`'s fallback), so a machine-dependent order is a no-op
|
|
15
|
+
* deploy that reads as a change — or a different page rendered per host. Same rule
|
|
16
|
+
* `@ultimat3/pwa`'s `precache.ts` and `service-worker.ts` state for the artifact they emit.
|
|
17
|
+
*/
|
|
18
|
+
export const byCodeUnit = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);
|
package/src/registry.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { HydrateStrategy, OfflineStrategy, RenderMode } from '@ultimat3/core';
|
|
9
9
|
import { finiteCount } from '@ultimat3/core';
|
|
10
|
+
import { byCodeUnit } from './code-unit-order';
|
|
10
11
|
import {
|
|
11
12
|
RouteDuplicateError,
|
|
12
13
|
RouteFileInvalidError,
|
|
@@ -322,7 +323,9 @@ export function routeCount(): number {
|
|
|
322
323
|
}
|
|
323
324
|
|
|
324
325
|
export function routeEntries(): readonly RouteEntry[] {
|
|
325
|
-
|
|
326
|
+
// Code units, never `localeCompare` — `describeRoutes()` below promises an order "identical for
|
|
327
|
+
// identical input", and `localeCompare` with no locale argument reads the runtime's ICU default.
|
|
328
|
+
return [...routes.values()].sort((a, b) => byCodeUnit(a.path, b.path));
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
export function routeFor(path: string): RouteEntry | undefined {
|
package/src/route-component.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* ship today — so the rule is a fixed precedence, evaluated once, here.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { byCodeUnit } from './code-unit-order';
|
|
7
8
|
import type { JsxComponent } from './jsx';
|
|
8
9
|
|
|
9
10
|
/** The page component of a route module: a function of props, sync or async. */
|
|
@@ -15,14 +16,17 @@ const isComponentExport = (name: string, value: unknown): value is RouteComponen
|
|
|
15
16
|
/**
|
|
16
17
|
* `Page` first, because that is the name `examples/dummy` uses and the one the generators should
|
|
17
18
|
* converge on; then a single `…Page`; then a single capitalised function. Sorted before the last
|
|
18
|
-
* fallback so a module with two components resolves to the same one on every machine
|
|
19
|
+
* fallback so a module with two components resolves to the same one on every machine — by CODE
|
|
20
|
+
* UNIT, because `localeCompare` is what "every machine" fails on: with no locale argument it
|
|
21
|
+
* answers from the runtime's ICU default, which orders `A_Dash` before `ADash` where code units
|
|
22
|
+
* order them the other way.
|
|
19
23
|
*/
|
|
20
24
|
export function pageComponentOf(
|
|
21
25
|
module: Readonly<Record<string, unknown>>,
|
|
22
26
|
): RouteComponent | undefined {
|
|
23
27
|
const components = Object.entries(module)
|
|
24
28
|
.filter(([name, value]) => isComponentExport(name, value))
|
|
25
|
-
.sort(([a], [b]) => a
|
|
29
|
+
.sort(([a], [b]) => byCodeUnit(a, b)) as readonly (readonly [string, RouteComponent])[];
|
|
26
30
|
if (components.length === 0) return undefined;
|
|
27
31
|
|
|
28
32
|
const exact = components.find(([name]) => name === 'Page');
|
package/src/surfaces.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { RenderMode } from '@ultimat3/core';
|
|
9
|
+
import { byCodeUnit } from './code-unit-order';
|
|
9
10
|
import { SurfaceBoundaryError } from './errors';
|
|
10
11
|
|
|
11
12
|
export type Surface = 'site' | 'app' | 'api' | 'shared';
|
|
@@ -147,7 +148,10 @@ export function checkSurfaceBoundary(graph: ImportGraph): readonly BoundaryViola
|
|
|
147
148
|
walk(graph, entry, entrySurface, found);
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
// Code units, never `localeCompare`: two machines running the same check must report the same
|
|
152
|
+
// list in the same order, and `localeCompare` with no locale argument reads the runtime's ICU
|
|
153
|
+
// default locale and collation version.
|
|
154
|
+
return [...found.values()].sort((a, b) => byCodeUnit(keyOf(a), keyOf(b)));
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
function keyOf(v: BoundaryViolation): string {
|