@zerotal/arch 1.12.0 → 1.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/docs/changelog.md CHANGED
@@ -27,6 +27,71 @@ 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.13.0 — 2026-08-31
31
+
32
+ Three retirements, taken together on purpose. Each is a small migration, and three minors
33
+ each asking an app to move costs more than one that asks properly — so this is one crossing
34
+ and one `zt upgrade` run.
35
+
36
+ ```bash
37
+ bun zt upgrade --to 1.13.0
38
+ ```
39
+
40
+ ### Removed — BREAKING
41
+
42
+ - **Flow's `Component.client(…)`.** Use the `` this.$`…` `` tagged template.
43
+
44
+ This is a security fix wearing an ergonomics change's clothes, which is why it did not wait
45
+ for 2.0. `client()` took a **string** and queued it to be evaluated in the browser, so the
46
+ caller owned the escaping — and its own docblock had to warn _never interpolate unescaped
47
+ user input_. A method whose documentation has to tell you not to hold it that way is a
48
+ footgun with a label on. `$` is a tagged template, so every `${…}` is encoded as a JS
49
+ literal before it reaches the page.
50
+
51
+ ```ts
52
+ // before — escaping was yours to remember
53
+ this.client(`toast(${JSON.stringify(this.search)})`);
54
+
55
+ // after — encoded for you
56
+ this.$`toast(${this.search})`;
57
+ ```
58
+
59
+ The codemod rewrites a call whose argument is a single literal. **One whose argument is a
60
+ variable or a concatenation is reported rather than rewritten**: those are precisely the
61
+ ones the warning was about, and wrapping a finished string as `` $`${expr}` `` would encode
62
+ it as a string literal and stop running it as code. A codemod that quietly did that would
63
+ leave an app compiling, running, and no longer doing anything where it used to run a script.
64
+
65
+ Removing it also frees `client` as a property name on a component — the same benefit
66
+ removing `title` gave in 1.7.3.
67
+
68
+ ### Changed — BREAKING
69
+
70
+ - **`LockDriver.extend()` is required.** Only affects a custom lock driver; all three
71
+ built-in drivers already implement it.
72
+
73
+ It shipped optional in 1.5.0 with `acquire(key, owner, ttl)` as the fallback, and that
74
+ fallback was correct only by coincidence. `acquire` happens to be an owner-guarded refresh
75
+ on every built-in driver, and nothing in the interface ever said it had to be — so a
76
+ third-party driver whose `acquire` takes a _free_ lock, which is the ordinary reading of
77
+ the word, would have had `refresh()` silently take a lock another holder owned. That is the
78
+ one thing a lock exists to prevent. Requiring the method turns an assumption the contract
79
+ never stated into something a driver has to answer.
80
+
81
+ - **`routes:types` and `serve --dev` are retired**, in favour of `route:types` and `dev`.
82
+ Both are rewritten by the codemod.
83
+
84
+ `serve --dev` **fails with a message** rather than being ignored, and the flag is still
85
+ declared for that reason alone. Flag parsing runs non-strict, so simply deleting it would
86
+ have left `serve --dev` starting a plain server — no watcher, no rebuild, no explanation. A
87
+ retired flag that silently changes what a command does is worse than one that is still
88
+ there.
89
+
90
+ ### Added
91
+
92
+ - **The `client-tagged-template` codemod**, which is what makes the first item above a
93
+ migration rather than a search.
94
+
30
95
  ## 1.12.0 — 2026-08-31
31
96
 
32
97
  One change, deliberately alone: the minor exists to carry it.
@@ -198,7 +198,7 @@ The names in use:
198
198
  | Rendering | `render` `layout` `placeholder` `slot` `hasSlot` `child` `isInteractive` |
199
199
  | Actions & state | `bind` `validate` `resetValidation` `errors` `addError` `refresh` `$refresh` `$set` `cancelled` `signal` |
200
200
  | Navigation | `redirect` `redirectRoute` `redirectIntended` `currentUrl` `navigateCurrent` |
201
- | Events & realtime | `dispatch` `dispatchSelf` `dispatchTo` `stream` `client` `$` |
201
+ | Events & realtime | `dispatch` `dispatchSelf` `dispatchTo` `stream` `$` |
202
202
  | Misc | `flash` `download` `clearDurable` |
203
203
 
204
204
  Anything beginning with `_` is also framework-internal, as are the statics `durable`
@@ -75,11 +75,12 @@ 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. Six have shipped so far — the
78
+ own section in the Upgrade Guide. Seven 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
- 1.11.2, and the refusal to write a boolean into a text column in 1.12.0.
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
84
  - **One of those five is in the wrong place, and it stays on the record.** 1.11.2
84
85
  is a patch, and by the rule above a patch cannot carry a break. It did: the
85
86
  `countTokens` signature changed in the same release that promoted `@zerotal/ai`
package/docs/upgrade.md CHANGED
@@ -289,6 +289,64 @@ doing something quiet.
289
289
  If it really is a new migration, give it a name that does not collide once the
290
290
  leading digits are removed.
291
291
 
292
+ ## 1.12 to 1.13
293
+
294
+ Three retirements in one crossing, deliberately together: each is a small migration, and
295
+ three minors each asking an app to move costs more than one that asks properly. `zt upgrade`
296
+ does the mechanical half.
297
+
298
+ ```bash fragment
299
+ bun zt upgrade --to 1.13.0
300
+ ```
301
+
302
+ ### `Component.client(…)` is removed — use the `$` tagged template
303
+
304
+ The reason this did not wait: `client()` took a **string** and queued it to be evaluated in
305
+ the browser, so the caller owned the escaping. Its own docblock had to say _never interpolate
306
+ unescaped user input_, which is a warning about a footgun rather than a design. `$` is a
307
+ tagged template, so every `${…}` is encoded as a JS literal before it reaches the page.
308
+
309
+ ```ts fragment
310
+ // in a component class body — before
311
+ this.client(`$refs.titleInput.focus()`);
312
+ this.client(`toast(${JSON.stringify(this.search)})`); // escaping was yours to remember
313
+
314
+ // after
315
+ this.$`$refs.titleInput.focus()`;
316
+ this.$`toast(${this.search})`; // encoded for you
317
+ ```
318
+
319
+ The codemod rewrites a call whose argument is a single literal. **A call whose argument is a
320
+ variable or a concatenation is reported rather than rewritten**, because those are exactly the
321
+ ones the security note was about — and wrapping the finished string as `` $`${expr}` `` would
322
+ encode it as a string literal and stop running it as code. Read those and interpolate through
323
+ `$` instead.
324
+
325
+ Removing it also frees `client` as a property name on your components, the way removing
326
+ `title` did in 1.7.3.
327
+
328
+ ### `LockDriver.extend()` is required
329
+
330
+ Only affects a **custom lock driver**; the three built-in ones already implement it.
331
+
332
+ It shipped optional in 1.5.0 with `acquire(key, owner, ttl)` as the fallback, and the fallback
333
+ was correct only by coincidence: `acquire` happens to be an owner-guarded refresh on every
334
+ built-in driver, and nothing in the interface said it had to be. A driver whose `acquire` takes
335
+ a _free_ lock — the ordinary reading of the word — would have had `refresh()` silently take a
336
+ lock another holder owned, which is the one thing a lock exists to prevent.
337
+
338
+ Implement `extend(key, owner, ttlSeconds)`: push the deadline out, return `false` when the key
339
+ is free or held by someone else.
340
+
341
+ ### `routes:types` and `serve --dev` are retired
342
+
343
+ `route:types` and `dev` are the real names. The codemod rewrites both in scripts and CI config.
344
+
345
+ `serve --dev` **fails loudly** rather than being ignored. The flag is still declared for exactly
346
+ that reason: flag parsing is non-strict, so deleting it would have left `serve --dev` starting a
347
+ plain server with no watcher and no message — a retired flag that silently changes what a
348
+ command does.
349
+
292
350
  ## 1.11 to 1.12
293
351
 
294
352
  One breaking change, and it is the intended kind: a minor, announced, with the reason.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/arch",
3
- "version": "1.12.0",
3
+ "version": "1.13.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.12.0"
38
+ "@zerotal/core": "1.13.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "typescript": "^5.8.0",
42
- "@zerotal/orm": "1.12.0"
42
+ "@zerotal/orm": "1.13.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": [