@zerotal/arch 1.13.4 → 1.14.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/docs/changelog.md CHANGED
@@ -27,6 +27,94 @@ the section for every version you cross and apply its migration notes, not only
27
27
  majors. [Releases and versioning](/docs/support-policy#releases-and-versioning) explains
28
28
  when that carve-out ends.
29
29
 
30
+ ## 1.14.0 — 2026-09-01
31
+
32
+ Rendering and the SSR endpoint become separate decisions. Small in practice — most apps
33
+ change nothing — but it is a change to what a config flag does, so it takes a minor and
34
+ says so.
35
+
36
+ ### Changed — BREAKING
37
+
38
+ - **`inertia.ssr: true` no longer registers `POST /__ssr`.**
39
+
40
+ ```ts
41
+ // config/inertia.ts
42
+ export default InertiaConfig({
43
+ ssr: true, // server-render every first page load. Renders nothing else.
44
+ ssrEndpoint: true, // expose POST /__ssr, for a renderer outside this process
45
+ });
46
+ ```
47
+
48
+ **If you set `ssr: true` for server rendering, you need no change.** You keep exactly
49
+ that and stop exposing a route you were not using. Add `ssrEndpoint: true` only when
50
+ something outside the web process calls `/__ssr` — a separate renderer, a second host.
51
+
52
+ The endpoint exists because upstream Inertia runs on hosts with no JavaScript runtime:
53
+ PHP cannot import a `.tsx`, so it posts `{ component, props, url }` to a Node process and
54
+ gets `{ body, head }` back. That hop is forced by the host language, not chosen. Bun _is_
55
+ a JavaScript runtime, so [1.13.5](#1135--2026-09-01) made `ssr` import the component and
56
+ render it inline — no serialisation, no second process, no network.
57
+
58
+ So the endpoint solves a problem this framework does not have, and it stays only for the
59
+ case that is still real: deliberately moving render CPU off the web process. That is a
60
+ deployment choice, and it now has to be made rather than inherited.
61
+
62
+ Turning rendering on should not open a route that renders arbitrary components from POST
63
+ input, however well guarded. One switch, one thing — the same reasoning that separated
64
+ `secureHeaders: false` from the site gate in 1.13.3.
65
+
66
+ ### A note on what this costs
67
+
68
+ Worth stating plainly, because it is easy to expect the opposite: **turning `ssr` on adds
69
+ CPU to the web process.** Before 1.13.5 the flag rendered nothing, so there was no round
70
+ trip to remove — a page load did no rendering at all. Now it renders every first load.
71
+
72
+ The comparison where in-process rendering _is_ cheaper is against the way other frameworks
73
+ do SSR: no HTTP hop, no JSON round trip of the whole page object, no second process to run
74
+ and supervise. Against the framework's own previous behaviour, it is new work in exchange
75
+ for HTML a crawler and a link preview can read.
76
+
77
+ ## 1.13.5 — 2026-09-01
78
+
79
+ **`inertia.ssr: true` server-renders now.** One config line, every page, no controller
80
+ changes — which is what the option is named for and what it did not do.
81
+
82
+ ### Fixed
83
+
84
+ - **The `ssr` flag rendered nothing.** It registered `POST /__ssr` and nothing in the
85
+ request path consulted it. So an app that set `ssr: true` and read
86
+ [the SSR guide](/docs/inertia/ssr) — which stated that the server renders the component
87
+ into the template — got exactly the empty root it had before, and the documentation was
88
+ the reason nobody suspected otherwise.
89
+
90
+ ```ts
91
+ // config/inertia.ts — this is now the whole of it
92
+ export default InertiaConfig({ ssr: true });
93
+ ```
94
+
95
+ `Inertia.render()` renders the component into the root, injects the page's `<Head>` into
96
+ the served `<head>`, and marks the root `data-server-rendered`. The scaffolded `app.tsx`
97
+ already hydrated on that attribute, so the client half needed nothing: turning SSR on is
98
+ one line, and there is no second step.
99
+
100
+ Server rendering was previously reachable only by rewriting each route to
101
+ `Inertia.stream()`, one call site at a time. Two teams did that. **They can go back to
102
+ `render()`** — and should, unless they wanted the streaming.
103
+
104
+ A component that fails to render falls back to the client-rendered document with a
105
+ warning rather than failing the route. The page still works in a browser, and taking a
106
+ route down because an _optimisation_ failed would make `ssr: true` a liability rather
107
+ than an improvement.
108
+
109
+ - **Streaming and SSR are separated in the docs.** `inertiaStream()` is not how you turn
110
+ server rendering on; it decides whether the bytes are **buffered or streamed**. Both
111
+ render the component and both hydrate. Conflating them is what made a per-route rewrite
112
+ look like the supported answer, so the comparison table and the "what a crawler sees"
113
+ remedies now lead with the config flag.
114
+
115
+ `POST /__ssr` is unchanged and documented for what it is: the contract for an
116
+ **external** renderer, not the in-process switch.
117
+
30
118
  ## 1.13.4 — 2026-09-01
31
119
 
32
120
  **Take this one if you are on 1.13.3.** The site gate shipped a staff bypass that failed
@@ -56,15 +56,30 @@ server renderer for the framework your app uses.
56
56
  client-rendered document is served instead, because taking a route down because an
57
57
  _optimisation_ failed would make `ssr: true` a liability rather than an improvement.
58
58
 
59
- ## What `POST /__ssr` is for
59
+ ## What `POST /__ssr` is for, and why you probably do not want it
60
60
 
61
- `ssr: true` also registers it. It accepts `{ component, props, url }` and returns
62
- `{ body, head }` the same contract as the Inertia Node SSR server — and it exists for
63
- an **external** caller: a separate renderer process, or a deployment that renders
64
- somewhere other than the web process.
61
+ Set `ssrEndpoint: true` and the app exposes `POST /__ssr`, which accepts
62
+ `{ component, props, url }` and returns `{ body, head }`.
65
63
 
66
- You do not need it for the switch above, which renders in-process. It is throttled and
67
- loopback-gated; see `ssrSecret` for reaching it from another host.
64
+ That is the contract upstream Inertia uses, and it exists there for a reason that does not
65
+ apply here: **PHP and Ruby have no JavaScript runtime**, so the web framework cannot import
66
+ a `.tsx` and must hand rendering to a separate Node process. The HTTP hop is forced by the
67
+ host language.
68
+
69
+ Bun _is_ a JavaScript runtime. `ssr: true` imports the component and renders it inline, in
70
+ the same process, with no serialisation and no network. So the endpoint solves a problem
71
+ this framework does not have.
72
+
73
+ What it is still good for is the one case that remains real: **deliberately moving render
74
+ CPU off the web process**, onto another process or another host, on an app where rendering
75
+ competes with request handling. Then the hop is the point rather than the cost.
76
+
77
+ It is throttled, answers a 404 to anything that is not loopback, and takes `ssrSecret` for
78
+ a renderer on another host.
79
+
80
+ > **`ssr: true` does not register it, since 1.14.0.** It used to, which meant turning
81
+ > rendering on opened a route the app never asked for. See
82
+ > [the upgrade note](/docs/upgrade#1-13-to-1-14).
68
83
 
69
84
  ## Streaming SSR — a different question
70
85
 
@@ -75,12 +75,13 @@ dependency order, from CI. Never mix versions across packages.
75
75
  tilde if you would rather cross a minor deliberately.
76
76
  - **A break is never silent.** Every one is called out in the release notes as
77
77
  **BREAKING**, with the reason and the migration steps, and the version gets its
78
- own section in the Upgrade Guide. Seven have shipped so far — the
78
+ own section in the Upgrade Guide. Eight have shipped so far — the
79
79
  `ComponentWith` / `BaseModelWith` removal in 1.3.0, Flow's `socket:` listener
80
80
  prefix in 1.7.2, the removal of Flow's `this.title(…)` in 1.7.3, SQLite
81
81
  foreign-key enforcement in 1.11.0, `countTokens` returning `number | null` in
82
82
  1.11.2, the refusal to write a boolean into a text column in 1.12.0, and the removal of
83
- Flow's `Component.client(…)` alongside two retired aliases in 1.13.0.
83
+ Flow's `Component.client(…)` alongside two retired aliases in 1.13.0, and
84
+ `inertia.ssr` no longer registering `POST /__ssr` in 1.14.0.
84
85
  - **One of those five is in the wrong place, and it stays on the record.** 1.11.2
85
86
  is a patch, and by the rule above a patch cannot carry a break. It did: the
86
87
  `countTokens` signature changed in the same release that promoted `@zerotal/ai`
package/docs/upgrade.md CHANGED
@@ -296,6 +296,34 @@ doing something quiet.
296
296
  If it really is a new migration, give it a name that does not collide once the
297
297
  leading digits are removed.
298
298
 
299
+ ## 1.13 to 1.14
300
+
301
+ One breaking change, and it is small in practice.
302
+
303
+ **`inertia.ssr: true` no longer registers `POST /__ssr`.** Rendering and the endpoint are
304
+ separate decisions now:
305
+
306
+ ```ts fragment
307
+ // config/inertia.ts — before
308
+ export default InertiaConfig({ ssr: true }); // rendered, and opened the route
309
+
310
+ // after
311
+ export default InertiaConfig({ ssr: true }); // renders, and opens nothing
312
+ export default InertiaConfig({ ssrEndpoint: true }); // opens the route, for an
313
+ // external renderer
314
+ ```
315
+
316
+ **Most apps need no change.** If you set `ssr: true` for server rendering — which is what
317
+ the option is for — you keep exactly that, and you stop exposing a route you were not
318
+ using. The endpoint had no in-process caller before 1.13.5, so an app that set the flag was
319
+ getting the route and nothing else.
320
+
321
+ **Add `ssrEndpoint: true` only if something outside the web process calls `/__ssr`** — a
322
+ separate renderer, a second host. If you are not running one, you were not using it.
323
+
324
+ Why split them: turning rendering on should not open a route that renders arbitrary
325
+ components from POST input, however well guarded. One switch, one thing.
326
+
299
327
  ## 1.12 to 1.13
300
328
 
301
329
  Three retirements in one crossing, deliberately together: each is a small migration, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/arch",
3
- "version": "1.13.4",
3
+ "version": "1.14.0",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -35,11 +35,11 @@
35
35
  "typecheck": "tsc --noEmit"
36
36
  },
37
37
  "dependencies": {
38
- "@zerotal/core": "1.13.4"
38
+ "@zerotal/core": "1.14.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "typescript": "^5.8.0",
42
- "@zerotal/orm": "1.13.4"
42
+ "@zerotal/orm": "1.14.0"
43
43
  },
44
44
  "description": "The Zerotal agent surface — an MCP server that hands coding agents the framework's machine-readable truth: exact API signatures, live routes and schema, version-matched docs, and `zt doctor`.",
45
45
  "keywords": [