@zerotal/arch 1.8.1 → 1.10.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.
Files changed (64) hide show
  1. package/api-surface.md +6 -6
  2. package/docs/admin/actions.md +15 -0
  3. package/docs/admin/auth.md +10 -0
  4. package/docs/admin/dashboard.md +12 -0
  5. package/docs/admin/extending-ui.md +14 -0
  6. package/docs/admin/forms.md +15 -0
  7. package/docs/admin/operations.md +12 -0
  8. package/docs/admin/resources.md +6 -0
  9. package/docs/admin/tables.md +21 -0
  10. package/docs/audit.md +5 -0
  11. package/docs/authentication.md +110 -1
  12. package/docs/broadcasting/references.md +17 -0
  13. package/docs/cache.md +5 -0
  14. package/docs/carbon.md +5 -0
  15. package/docs/changelog.md +281 -0
  16. package/docs/client/index.md +17 -0
  17. package/docs/commands.md +6 -0
  18. package/docs/components.md +73 -0
  19. package/docs/config-system.md +54 -0
  20. package/docs/cookies.md +6 -0
  21. package/docs/deployment.md +151 -13
  22. package/docs/devtools.md +5 -0
  23. package/docs/email-verification.md +26 -1
  24. package/docs/encryption.md +21 -0
  25. package/docs/errors.md +2 -0
  26. package/docs/flow/components.md +54 -0
  27. package/docs/flow/forms.md +57 -0
  28. package/docs/flow/references.md +14 -0
  29. package/docs/getting-started.md +38 -0
  30. package/docs/health.md +19 -0
  31. package/docs/helpers.md +150 -0
  32. package/docs/i18n.md +5 -0
  33. package/docs/inertia/middleware.md +44 -0
  34. package/docs/inertia/props.md +70 -0
  35. package/docs/inertia/ssr.md +95 -10
  36. package/docs/lock.md +15 -0
  37. package/docs/logger.md +38 -0
  38. package/docs/middleware.md +31 -0
  39. package/docs/migrations.md +47 -0
  40. package/docs/monitor.md +59 -0
  41. package/docs/notifications.md +11 -0
  42. package/docs/orm/casts.md +6 -0
  43. package/docs/orm/lifecycle.md +18 -0
  44. package/docs/orm/queries.md +10 -0
  45. package/docs/orm/relationships.md +30 -0
  46. package/docs/queue.md +10 -0
  47. package/docs/rate-limiting.md +84 -21
  48. package/docs/responses.md +23 -0
  49. package/docs/routing.md +16 -0
  50. package/docs/scheduler.md +82 -8
  51. package/docs/session.md +6 -0
  52. package/docs/social.md +10 -0
  53. package/docs/storage.md +21 -0
  54. package/docs/support-policy.md +13 -1
  55. package/docs/telemetry.md +8 -0
  56. package/docs/tenancy.md +6 -0
  57. package/docs/testing/index.md +105 -0
  58. package/docs/upgrade.md +48 -0
  59. package/docs/validator.md +9 -0
  60. package/docs/view.md +6 -0
  61. package/package.json +3 -3
  62. package/src/install/guidelines.ts +1 -1
  63. package/src/mcp/stdio.ts +3 -3
  64. package/src/tools/_probe.ts +2 -2
package/docs/telemetry.md CHANGED
@@ -497,6 +497,14 @@ console.log(mem.spans[0]?.attributes["x"]); // 1
497
497
  | `'producer'` | Enqueuing a message. |
498
498
  | `'consumer'` | Processing a queued message. |
499
499
 
500
+ ## Types
501
+
502
+ | Type | What it is |
503
+ | ------------------------------ | ---------------------------------------------------------------------------------------- |
504
+ | `TelemetryOptions` | What `TelemetryConfig()` accepts. |
505
+ | `OtlpExporterOptions` | Where spans are shipped, and how. |
506
+ | `SpanStatus`, `SpanStatusCode` | A span's outcome — `unset`, `ok` or `error`, which is what a backend colours a trace by. |
507
+
500
508
  ## Next steps
501
509
 
502
510
  - [Logger](/docs/logger) — pair traces with structured logs.
package/docs/tenancy.md CHANGED
@@ -593,6 +593,12 @@ Resolved from the container binding `"tenancy"`; the `Tenant` value is the facad
593
593
  | `evict` | `evict(tenant): void` | Close and drop a tenant's connection. |
594
594
  | `closeAll` | `closeAll(): void` | Close every open connection. |
595
595
 
596
+ ## Types
597
+
598
+ `TenancyStrategy` is how a tenant is resolved from a request — subdomain, path, header, or the
599
+ authenticated user. `TenantManagerOptions` configures the manager, and `TenantDeletedHook` runs
600
+ when a tenant is removed, which is where the data belonging to it gets cleaned up.
601
+
596
602
  ## Next steps
597
603
 
598
604
  - [ORM](/docs/orm) — how `Tenantable` composes onto your models via `Model.using`.
@@ -209,6 +209,105 @@ observers, global scopes, and state-machine callbacks, plus framework event
209
209
  subscriptions. `createTestApp()` and `testApp.close()` call it for you, so suites
210
210
  using those helpers don't need the explicit `afterEach`.
211
211
 
212
+ ## `bun test` vs `bun zt test`
213
+
214
+ Both run the same files. `bun zt test` is a wrapper that sets up three things Bun's
215
+ runner does not, and each of them has cost somebody a day:
216
+
217
+ | | `bun test` | `bun zt test` |
218
+ | ---------------- | --------------------- | ---------------------------------------------------------- |
219
+ | Per-test timeout | Bun's default, 5000ms | 30000ms (`--timeout`, override with `--timeout=`) |
220
+ | Runtime check | none | refuses a Bun below the project's `engines.bun` |
221
+ | DB wiring | none | preloads `@zerotal/testing/preload` and passes `ZT_DB_URL` |
222
+
223
+ ### The timeout
224
+
225
+ Bun's default per-test timeout is 5000ms, and a suite that boots an app per file
226
+ exceeds it on a loaded machine — CI, or a laptop that has just run `tsc`. The
227
+ failures look like flakes, which is the expensive part: a flake gets re-run, and a
228
+ re-run passes.
229
+
230
+ `bun zt test` sets `--timeout=30000`. If you run `bun test` directly, pass it
231
+ yourself, because **the two documented-looking alternatives do not work**:
232
+
233
+ - `[test] timeout` in `bunfig.toml` — ignored.
234
+ - `setDefaultTimeout()` in a preload — applies to the first test file only. Bun
235
+ re-imports the preload per file, but the setting does not survive.
236
+
237
+ The command-line flag is the only mechanism that covers hooks as well as tests,
238
+ which matters because it is usually a `beforeAll` that boots the app.
239
+
240
+ ### The runtime
241
+
242
+ `engines.bun` in your `package.json` is a floor, and until you enforce it, it is a
243
+ comment. The shell's `bun` and the project's can differ, and the difference between
244
+ two Bun releases is real and narrow: `Intl` formatting, the SQLite bindings and
245
+ `node:` compatibility all move. So a handful of currency or date assertions go red
246
+ and the rest pass, and you go looking for a bug in the code they touch, because
247
+ nothing in the failure says "wrong binary".
248
+
249
+ `bun zt test` refuses to run below the declared floor. Direct `bun test` runs get the
250
+ same check as a warning if you load the preload:
251
+
252
+ ```toml
253
+ # bunfig.toml
254
+ [test]
255
+ preload = ["@zerotal/testing/preload"]
256
+ timeout = 30000 # note: currently ignored by Bun — pass --timeout on the command line
257
+ ```
258
+
259
+ Set `ZT_ALLOW_RUNTIME_MISMATCH=1` to downgrade the refusal to a warning while you
260
+ are mid-upgrade.
261
+
262
+ ### `@zerotal/core/runtime`
263
+
264
+ The checks behind the two paragraphs above, exported so a script or a test of your
265
+ own can make the same assertion. `zt` runs both at the top of every command; the test
266
+ preload runs the floor check as a warning.
267
+
268
+ | Export | Signature | What it answers |
269
+ | -------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
270
+ | `declaredBunFloor` | `declaredBunFloor(cwd): { range, manifest } \| null` | The nearest `engines.bun` up the tree from `cwd`. |
271
+ | `runtimeBelowFloor` | `runtimeBelowFloor(cwd?): RuntimeFloor \| null` | Is this process below that floor? `null` when it is met or none is declared. |
272
+ | `runtimeBelowFloorMessage` | `runtimeBelowFloorMessage(floor): string` | The explanation to print — both versions, the manifest, and the way out. |
273
+ | `installedBunVersion` | `installedBunVersion(cwd): { version, manifest } \| null` | The Bun in `node_modules`, if the project installs one as a package. |
274
+ | `runtimeMismatch` | `runtimeMismatch(cwd?): RuntimeMismatch \| null` | Does the running Bun differ from the installed one? Compared exactly — a patch is a binary. |
275
+ | `runtimeMismatchMessage` | `runtimeMismatchMessage(mismatch): string` | The explanation for that one. |
276
+ | `runtimeMismatchAllowed` | `runtimeMismatchAllowed(): boolean` | Whether `ZT_ALLOW_RUNTIME_MISMATCH` is set. |
277
+ | `bunBinary` | `bunBinary(): string` | The binary to spawn a child with — `process.execPath`, never the name PATH resolves. |
278
+ | `RUNTIME_MISMATCH_ESCAPE` | `"ZT_ALLOW_RUNTIME_MISMATCH"` | The env var name, so a script can set it without hardcoding the string. |
279
+
280
+ `RuntimeFloor` is `{ running, required, manifest }`; `RuntimeMismatch` is
281
+ `{ running, installed, manifest }`. Both name the file the second version came from,
282
+ because "which one is wrong" is the question you actually have.
283
+
284
+ ## Configuration is per-process, and `bun test` is one process
285
+
286
+ Zerotal resolves configuration once, at boot. `bun test` runs every file in the same
287
+ process, so **whichever file boots the app first fixes the configuration for all of
288
+ them.**
289
+
290
+ A test that sets an environment variable in its own `beforeAll` and then asserts on
291
+ the resulting behaviour passes alone and fails in the suite — or worse, passes in the
292
+ suite for a reason unrelated to what it claims to test:
293
+
294
+ ```typescript fragment
295
+ // Passes alone. In a suite, the app may already be booted with CSRF on, and the
296
+ // three "rejects without a token" assertions below pass on a 419 they would have
297
+ // got anyway — never reaching the guard they name.
298
+ beforeAll(() => {
299
+ Bun.env.CSRF_DISABLED = "1";
300
+ });
301
+ ```
302
+
303
+ Assert on the _relationship_ rather than on a literal — the published origin equals
304
+ the configured one, whatever it is — or boot a dedicated app for the case:
305
+
306
+ ```typescript fragment
307
+ const app = await createTestApp({ config: { app: { url: "https://example.test" } } });
308
+ expect(page.canonical).toBe(config("app.url"));
309
+ ```
310
+
212
311
  ## Running the suite from a script
213
312
 
214
313
  A script that gates on the tests has to read the tests' exit status, and a pipe hides it:
@@ -256,6 +355,12 @@ its full surface.
256
355
  | `fake` | `typeof fake` | South-African-flavoured random data generator. |
257
356
  | `fakeFile` | `typeof fakeFile` | Real PNG/JPEG/GIF/PDF files for upload tests. |
258
357
 
358
+ ### Types
359
+
360
+ `TestResponseContext` is what an assertion receives, `SessionDecoder` reads the session out of a
361
+ response so a test can assert on it, and `FakeFile` / `TestFileInput` / `TestFormValue` are the
362
+ shapes a multipart submission takes in a test.
363
+
259
364
  ## Next steps
260
365
 
261
366
  - [HTTP Tests](/docs/testing/http) — the full `TestApp` and `TestResponse` API.
package/docs/upgrade.md CHANGED
@@ -169,6 +169,54 @@ these are the changes that need action. Full detail is in the
169
169
  interface so pages that read them do not look unpassed; see
170
170
  [Typed props](/docs/inertia/props#typed-props).
171
171
 
172
+ ## 1.9 to 1.10
173
+
174
+ Three settings changed meaning. Each is quiet if it does not apply to you, and each is
175
+ worth thirty seconds of checking if it does.
176
+
177
+ 1. **`scheduler.timezone` is honoured.** It was documented as informational and read by
178
+ nothing, so whatever you put there had no effect and your schedules ran in the
179
+ server's zone. It is now the zone every schedule is evaluated in unless the task sets
180
+ its own.
181
+
182
+ Its default moved from the literal `"UTC"` to **the system zone**, so an app that never
183
+ set the key keeps doing exactly what it did. The case to check is an app that _did_:
184
+
185
+ ```ts fragment
186
+ // config/scheduler.ts
187
+ export default SchedulerConfig({ timezone: env("APP_TIMEZONE", "UTC") });
188
+ ```
189
+
190
+ On a server that is not on UTC, that line used to do nothing and now moves every
191
+ schedule. Either set it to the zone you actually want your crons read in — which is
192
+ the point of the setting — or delete the key to keep the server's zone.
193
+
194
+ `bun zt schedule:list` prints each task's next run in its own zone, which is the
195
+ quickest way to see whether anything moved.
196
+
197
+ 2. **Named rate limiters need `.trustedProxies(n)` behind a proxy.** `RateLimiter`'s
198
+ `.byIp()`, `.byUser()` and `.byApiKey()` ignored the proxy count entirely and read
199
+ `X-Forwarded-For` unconditionally. They now follow the same rule `ThrottleMiddleware`
200
+ already did — the header is consulted only when you say how many proxies sit in front:
201
+
202
+ ```ts fragment
203
+ RateLimiter.for("login").limit(5).every(60).byIp().trustedProxies(1).register();
204
+ ```
205
+
206
+ Without it the address used is the socket's, which behind a proxy is the _proxy's_, and
207
+ every visitor shares one bucket. `bun zt doctor` reports any limiter that needs this —
208
+ it could not before, because its check exempted custom key resolvers and all three of
209
+ these are one.
210
+
211
+ 3. **React apps using SSR need `@inertiajs/react` installed.** The same adapter your
212
+ browser entry point already uses. Server-side rendering now goes through its `<App>`,
213
+ which is what makes `<Head>` produce a title and an og: card in the HTML your server
214
+ actually sends. If it is missing you get a named error at render time, not a silent
215
+ omission.
216
+
217
+ Nothing to change if you already have it as a dependency, which every React Inertia app
218
+ does.
219
+
172
220
  ## The managed zt.ts
173
221
 
174
222
  `zt.ts` is framework-managed — the header says _do not modify_. If a release
package/docs/validator.md CHANGED
@@ -540,6 +540,15 @@ res.assertUnprocessable(); // 422 for a JSON request
540
540
  | `validate` | `validate(ctx, factory): Promise<Infer<…>>` | One-off HTTP validation; throws on failure. |
541
541
  | `Validator.check` | `check(data, factory): ValidationOutcome<…>` | Non-HTTP validation; returns a result, never throws. |
542
542
 
543
+ ## Types
544
+
545
+ | Type | What it is |
546
+ | --------------------------- | -------------------------------------------------------------------------------------------------------- |
547
+ | `ValidationErrors` | The error bag — field name to messages, which is what `withErrors()` and the `errors` shared prop carry. |
548
+ | `FieldRuleDefinition` | One field's rules as declared. |
549
+ | `InferFieldType<R>` | The type a rule set produces, so validated data is typed rather than `unknown`. |
550
+ | `PrecognitionResponseError` | What a precognition request returns when a field fails ahead of submission. |
551
+
543
552
  ## Next steps
544
553
 
545
554
  - [Requests Context](/docs/context#reading-input) — read the input that `FormRequest` validates.
package/docs/view.md CHANGED
@@ -444,6 +444,12 @@ res.assertDontSee("Draft"); // unpublished posts stay hidden
444
444
  | `Children` | Type for the `children` prop. |
445
445
  | `HttpContext<T>` | The request context a view component or controller action receives; route params and model bindings live on `ctx.params`. |
446
446
 
447
+ ## Types
448
+
449
+ `ViewComponent` is what `view()` accepts — a function taking the request context and your props,
450
+ returning markup. `ViewLayout` is the wrapper form a layout takes. Both are exported so a helper
451
+ that returns a component, or a registry that holds several, can be typed.
452
+
447
453
  ## Next steps
448
454
 
449
455
  - [Flow](/docs/flow) — server-driven interactive components over WebSocket.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/arch",
3
- "version": "1.8.1",
3
+ "version": "1.10.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.8.1"
38
+ "@zerotal/core": "1.10.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "typescript": "^5.8.0",
42
- "@zerotal/orm": "1.8.1"
42
+ "@zerotal/orm": "1.10.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": [
@@ -150,7 +150,7 @@ export interface GuidelineOptions {
150
150
  * How this project is configured, from {@link detectShape}. Omitted, the block
151
151
  * is what it always was — a function of the package list.
152
152
  */
153
- shape?: ProjectShape;
153
+ shape?: ProjectShape | undefined;
154
154
  }
155
155
 
156
156
  /**
package/src/mcp/stdio.ts CHANGED
@@ -27,11 +27,11 @@ import type { JsonRpcResponse } from "./types.ts";
27
27
  export interface StdioOptions {
28
28
  server: McpServer;
29
29
  /** Byte source. Defaults to this process's stdin. */
30
- input?: ReadableStream<Uint8Array>;
30
+ input?: ReadableStream<Uint8Array> | undefined;
31
31
  /** Frame sink. Defaults to this process's stdout. Injected in tests. */
32
- write?: (frame: string) => void;
32
+ write?: ((frame: string) => void) | undefined;
33
33
  /** Diagnostics sink. Defaults to stderr — never stdout. */
34
- log?: (message: string) => void;
34
+ log?: ((message: string) => void) | undefined;
35
35
  }
36
36
 
37
37
  /**
@@ -35,8 +35,8 @@ export interface ProbeRunner {
35
35
 
36
36
  export interface SpawnProbeOptions {
37
37
  /** Where to start looking for the app. Defaults to the server's working directory. */
38
- cwd?: string;
39
- timeoutMs?: number;
38
+ cwd?: string | undefined;
39
+ timeoutMs?: number | undefined;
40
40
  }
41
41
 
42
42
  /**