@zerotal/arch 1.7.3 → 1.7.5

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 (127) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +4 -2
  3. package/api-surface.md +4 -4
  4. package/docs/about.md +16 -16
  5. package/docs/admin/actions.md +9 -9
  6. package/docs/admin/auth.md +2 -2
  7. package/docs/admin/dashboard.md +5 -5
  8. package/docs/admin/extending-ui.md +12 -12
  9. package/docs/admin/extending.md +16 -7
  10. package/docs/admin/forms.md +4 -4
  11. package/docs/admin/index.md +2 -2
  12. package/docs/admin/operations.md +9 -9
  13. package/docs/admin/resources.md +3 -3
  14. package/docs/admin/structure.md +6 -6
  15. package/docs/admin/tables.md +8 -8
  16. package/docs/admin/testing.md +4 -4
  17. package/docs/ai.md +118 -11
  18. package/docs/application.md +12 -12
  19. package/docs/arch.md +25 -3
  20. package/docs/assets.md +11 -11
  21. package/docs/audit.md +13 -13
  22. package/docs/authentication.md +39 -39
  23. package/docs/authorization.md +13 -13
  24. package/docs/broadcasting/channels.md +2 -2
  25. package/docs/broadcasting/client.md +2 -2
  26. package/docs/broadcasting/events.md +9 -10
  27. package/docs/broadcasting/index.md +1 -1
  28. package/docs/broadcasting/testing.md +5 -5
  29. package/docs/cache.md +15 -13
  30. package/docs/carbon.md +35 -35
  31. package/docs/changelog.md +165 -1
  32. package/docs/client/index.md +277 -70
  33. package/docs/commands.md +4 -4
  34. package/docs/components.md +116 -115
  35. package/docs/config-system.md +4 -4
  36. package/docs/container.md +27 -27
  37. package/docs/context.md +27 -27
  38. package/docs/contributing.md +46 -0
  39. package/docs/controllers.md +10 -10
  40. package/docs/conventions.md +23 -15
  41. package/docs/cookies.md +3 -3
  42. package/docs/csrf.md +6 -6
  43. package/docs/database.md +22 -17
  44. package/docs/deployment.md +2 -2
  45. package/docs/devtools.md +16 -16
  46. package/docs/email-verification.md +8 -8
  47. package/docs/encryption.md +7 -7
  48. package/docs/errors.md +6 -6
  49. package/docs/events.md +12 -11
  50. package/docs/flow/components.md +32 -32
  51. package/docs/flow/decorators.md +18 -18
  52. package/docs/flow/events.md +16 -16
  53. package/docs/flow/forms.md +17 -17
  54. package/docs/flow/icons.md +199 -0
  55. package/docs/flow/index.md +20 -20
  56. package/docs/flow/layouts.md +35 -35
  57. package/docs/flow/lifecycle.md +16 -16
  58. package/docs/flow/models.md +9 -9
  59. package/docs/flow/pagination.md +8 -8
  60. package/docs/flow/performance.md +7 -7
  61. package/docs/flow/references.md +3 -3
  62. package/docs/flow/routing.md +26 -26
  63. package/docs/flow/testing.md +20 -20
  64. package/docs/getting-started.md +18 -13
  65. package/docs/health.md +17 -8
  66. package/docs/helpers.md +17 -17
  67. package/docs/i18n.md +10 -10
  68. package/docs/inertia/devtools.md +4 -4
  69. package/docs/inertia/index.md +12 -7
  70. package/docs/inertia/props.md +22 -22
  71. package/docs/inertia/rendering.md +86 -7
  72. package/docs/inertia/ssr.md +2 -2
  73. package/docs/lifecycle.md +2 -2
  74. package/docs/lock.md +11 -11
  75. package/docs/logger.md +12 -12
  76. package/docs/media.md +22 -22
  77. package/docs/middleware.md +17 -17
  78. package/docs/migrations.md +13 -13
  79. package/docs/monitor.md +6 -6
  80. package/docs/notifications.md +24 -24
  81. package/docs/orm/casts.md +14 -14
  82. package/docs/orm/factories.md +8 -8
  83. package/docs/orm/index.md +22 -20
  84. package/docs/orm/lifecycle.md +13 -13
  85. package/docs/orm/queries.md +28 -28
  86. package/docs/orm/relationships.md +16 -16
  87. package/docs/orm/serialization.md +11 -11
  88. package/docs/package-development.md +10 -10
  89. package/docs/pagination.md +14 -14
  90. package/docs/password-reset.md +6 -6
  91. package/docs/providers.md +11 -11
  92. package/docs/query-builder.md +18 -18
  93. package/docs/queue.md +14 -12
  94. package/docs/rate-limiting.md +6 -6
  95. package/docs/responses.md +14 -14
  96. package/docs/roles-and-2fa.md +9 -9
  97. package/docs/routing.md +44 -44
  98. package/docs/scaffolding.md +1 -1
  99. package/docs/scheduler.md +10 -10
  100. package/docs/seeding.md +6 -6
  101. package/docs/session.md +16 -14
  102. package/docs/social.md +14 -14
  103. package/docs/storage.md +25 -13
  104. package/docs/structure.md +2 -2
  105. package/docs/support-policy.md +36 -8
  106. package/docs/telemetry.md +12 -12
  107. package/docs/tenancy.md +34 -40
  108. package/docs/testing/browser.md +6 -6
  109. package/docs/testing/console.md +3 -3
  110. package/docs/testing/database.md +10 -10
  111. package/docs/testing/flow-browser.md +6 -6
  112. package/docs/testing/http.md +12 -12
  113. package/docs/testing/index.md +3 -3
  114. package/docs/testing/mocking.md +8 -8
  115. package/docs/upgrade.md +1 -1
  116. package/docs/validator.md +19 -19
  117. package/docs/view.md +14 -14
  118. package/package.json +4 -4
  119. package/src/index.ts +25 -1
  120. package/src/tools/searchDocs.ts +7 -0
  121. package/docs/client/auth.md +0 -113
  122. package/docs/client/errors.md +0 -139
  123. package/docs/client/files.md +0 -118
  124. package/docs/client/references.md +0 -58
  125. package/docs/client/requests.md +0 -131
  126. package/docs/client/resilience.md +0 -141
  127. package/docs/client/testing.md +0 -146
@@ -18,7 +18,7 @@ Scaffold one with the generator, which writes `app/admin/<Name>Resource.ts`:
18
18
  bun zt make:admin-resource Post
19
19
  ```
20
20
 
21
- ```ts
21
+ ```ts fragment
22
22
  class PostResource extends Resource {
23
23
  static model = Post;
24
24
 
@@ -71,7 +71,7 @@ class PostResource extends Resource {
71
71
  A resource does nothing until the panel knows about it. Register it where the panel
72
72
  is configured:
73
73
 
74
- ```ts
74
+ ```ts fragment
75
75
  Panel.register(PostResource);
76
76
  ```
77
77
 
@@ -93,7 +93,7 @@ any of its links changing.
93
93
  Override `can(ability, record?)` to gate the built-in actions. Abilities used by the
94
94
  presets: `create`, `update`, `delete`, `restore`, `forceDelete`.
95
95
 
96
- ```ts
96
+ ```ts fragment
97
97
  static can(ability: string, record?: AdminRecord) {
98
98
  return Gate.allows(ability, record ?? this.model);
99
99
  }
@@ -21,7 +21,7 @@ real back office takes.
21
21
  A cluster gives a group of resources a shared URL segment and a single sidebar
22
22
  entry. Declare one, then point resources at it:
23
23
 
24
- ```ts
24
+ ```ts fragment
25
25
  import { Cluster } from "@zerotal/admin";
26
26
 
27
27
  export class ShopCluster extends Cluster {
@@ -42,7 +42,7 @@ the cluster segment on its own. Nothing in a resource needs to know its own path
42
42
 
43
43
  A cluster's `ability` gates the whole section:
44
44
 
45
- ```ts
45
+ ```ts fragment
46
46
  export class FinanceCluster extends Cluster {
47
47
  static override slug = "finance";
48
48
  static override title = "Finance";
@@ -59,7 +59,7 @@ Some records have no meaning apart from their parent. Comments belong to a post;
59
59
  there is no useful screen listing every comment in the database. Declare the
60
60
  parent and the resource moves inside it:
61
61
 
62
- ```ts
62
+ ```ts fragment
63
63
  export class CommentResource extends Resource {
64
64
  static override model = Comment;
65
65
  static override parent = { resource: () => PostResource, foreignKey: "post_id" };
@@ -83,7 +83,7 @@ on whichever side of the import cycle evaluated first.
83
83
  When the parent declares the child with `hasMany`, its view page links into the
84
84
  nested pages instead of rendering an inline table:
85
85
 
86
- ```ts
86
+ ```ts fragment
87
87
  export class PostResource extends Resource {
88
88
  static override relations() {
89
89
  return [hasMany(CommentResource, "post_id").title("Comments")];
@@ -96,7 +96,7 @@ export class PostResource extends Resource {
96
96
  Site settings are one row. A list of one, with a view page and an edit page behind
97
97
  it, is three screens too many:
98
98
 
99
- ```ts
99
+ ```ts fragment
100
100
  export class SettingsResource extends Resource {
101
101
  static override model = Setting;
102
102
  static override singular = true;
@@ -123,7 +123,7 @@ than an error.
123
123
  Most applications have one panel, and `Panel.configure(...)` / `Panel.register(...)`
124
124
  write to it. When a second audience needs a second set of screens, make another:
125
125
 
126
- ```ts
126
+ ```ts fragment
127
127
  // The back office — everything.
128
128
  Panel.configure({ brand: "Acme", path: "/admin", middleware: [AuthMiddleware] });
129
129
  Panel.register(ProductResource, OrderResource, UserResource);
@@ -7,7 +7,7 @@ description: Columns, filters, sorting, search, and bulk actions on the list scr
7
7
 
8
8
  `columns()` returns `Column`s built with `text(key)` plus chainable modifiers.
9
9
 
10
- ```ts
10
+ ```ts fragment
11
11
  static columns() {
12
12
  return [
13
13
  text("title").label("Title").sortable().searchable(),
@@ -42,14 +42,14 @@ static columns() {
42
42
  formatter) render a `<tfoot>` total computed over the **full filtered dataset**, plus
43
43
  per-group subtotals when grouping is active.
44
44
 
45
- ```ts
45
+ ```ts fragment
46
46
  text("amount").align("end").sum("Revenue", (n) => `$${n.toFixed(2)}`),
47
47
  text("id").count("Orders"),
48
48
  ```
49
49
 
50
50
  ## Filters
51
51
 
52
- ```ts
52
+ ```ts fragment
53
53
  import { selectFilter, ternaryFilter } from "@zerotal/admin";
54
54
 
55
55
  static filters() {
@@ -70,7 +70,7 @@ Fixed filters work when you can name the useful questions in advance. A catalogu
70
70
  or a ledger is queried in too many ways for that, so `queryBuilder` lets the user
71
71
  stack their own comparisons and nest AND/OR groups:
72
72
 
73
- ```ts
73
+ ```ts fragment
74
74
  import {
75
75
  queryBuilder, textConstraint, numberConstraint,
76
76
  selectConstraint, booleanConstraint, dateConstraint,
@@ -112,7 +112,7 @@ link someone can send to a colleague.
112
112
  `tab(key)` adds quick-filter tabs above the table, each scoping the query and
113
113
  optionally showing a count badge.
114
114
 
115
- ```ts
115
+ ```ts fragment
116
116
  import { tab } from "@zerotal/admin";
117
117
 
118
118
  static tabs() {
@@ -129,7 +129,7 @@ static tabs() {
129
129
  `group(column)` adds a "Group by" menu; the page renders header rows per bucket with
130
130
  counts (and per-group summary subtotals).
131
131
 
132
- ```ts
132
+ ```ts fragment
133
133
  import { group } from "@zerotal/admin";
134
134
 
135
135
  static groups() {
@@ -143,7 +143,7 @@ static defaultGroup = "status"; // optional
143
143
  Set `reorderable` to an integer position column to show up/down handles that persist
144
144
  order:
145
145
 
146
- ```ts
146
+ ```ts fragment
147
147
  static reorderable = "sort";
148
148
  ```
149
149
 
@@ -158,7 +158,7 @@ automatically and are URL-driven.
158
158
  A blank table teaches nobody anything. Override `emptyState()` to say why the list
159
159
  is empty and what will fill it:
160
160
 
161
- ```ts
161
+ ```ts fragment
162
162
  static emptyState() {
163
163
  return {
164
164
  heading: "No orders yet",
@@ -26,7 +26,7 @@ works alongside the admin-specific ones below.
26
26
  | `AdminTest.view(Resource, id)` | View page | Infolist entries and header actions for a record |
27
27
  | `AdminTest.form(Resource, mode?)` | Create / Edit form | Fields, validation and saving |
28
28
 
29
- ```ts
29
+ ```ts fragment
30
30
  // tests/admin/users.test.ts
31
31
  import { AdminTest, assertHasColumn, assertHasAction } from "@zerotal/admin/testing";
32
32
  import { UserResource } from "../../app/admin/UserResource.ts";
@@ -41,7 +41,7 @@ list.assertSee("ada@example.com");
41
41
  default) or `"edit"`. Drive the form through its `form` property, which holds
42
42
  every field's value:
43
43
 
44
- ```ts
44
+ ```ts fragment
45
45
  const form = await AdminTest.form(UserResource, "create");
46
46
  assertHasField(form, UserResource, "name");
47
47
  await form.set("form", { name: "" });
@@ -56,7 +56,7 @@ those values as the second argument to `AdminTest.list()` and the page mounts as
56
56
  though the reader had arrived on that URL — which is how you assert on a filtered
57
57
  or sorted table without first driving the clicks that would produce it.
58
58
 
59
- ```ts
59
+ ```ts fragment
60
60
  const list = await AdminTest.list(UserResource, {
61
61
  search: "ada",
62
62
  sortBy: "createdAt",
@@ -125,7 +125,7 @@ Every helper accepts a panel as its last argument, defaulting to
125
125
  `Panel.default()`. Apps that register more than one panel pass the one under test,
126
126
  so the page resolves that panel's own configuration and navigation:
127
127
 
128
- ```ts
128
+ ```ts fragment
129
129
  const shop = Panel.get("shop");
130
130
 
131
131
  const list = await AdminTest.list(OrderResource, {}, shop);
package/docs/ai.md CHANGED
@@ -116,7 +116,7 @@ a price with `registerModelPrice()`.
116
116
 
117
117
  ## Generating text
118
118
 
119
- ```ts
119
+ ```ts fragment
120
120
  import { Ai } from "@zerotal/ai";
121
121
 
122
122
  // Just the text.
@@ -152,7 +152,7 @@ Reach for `effort` instead. It trades thoroughness against cost and latency:
152
152
 
153
153
  ### Streaming
154
154
 
155
- ```ts
155
+ ```ts fragment
156
156
  for await (const chunk of Ai.stream({ prompt, signal })) {
157
157
  if (chunk.type === "text") process.stdout.write(chunk.text);
158
158
  if (chunk.type === "done") console.log(chunk.response.usage);
@@ -166,7 +166,7 @@ generation. See [Flow](/docs/flow) for streaming straight into a component.
166
166
 
167
167
  ### Structured output
168
168
 
169
- ```ts
169
+ ```ts fragment
170
170
  const review = await Ai.object({ prompt: `Classify this review:\n\n${text}` }, (rule) => ({
171
171
  sentiment: rule.string().in(["positive", "neutral", "negative"]),
172
172
  summary: rule.string().max(140),
@@ -189,13 +189,13 @@ schema is defined, not when the request is sent.
189
189
  `strippedConstraints()` names exactly what the model will not see, if you want to
190
190
  check that a load-bearing constraint is visible to it:
191
191
 
192
- ```ts
192
+ ```ts fragment
193
193
  strippedConstraints({ title: rule.string().min(3) }); // → ["title: min"]
194
194
  ```
195
195
 
196
196
  ## Tools and the agent loop
197
197
 
198
- ```ts
198
+ ```ts fragment
199
199
  import { Ai, tool } from "@zerotal/ai";
200
200
 
201
201
  const lookupOrder = tool({
@@ -238,7 +238,7 @@ back and re-requests.
238
238
 
239
239
  ### Locking a run
240
240
 
241
- ```ts
241
+ ```ts fragment
242
242
  await Ai.agent({
243
243
  prompt: "Refund order 4821 if it shipped over 30 days ago.",
244
244
  tools: [lookupOrder, issueRefund],
@@ -264,7 +264,7 @@ checking crashes on a response the API considers fine.
264
264
 
265
265
  This package checks the stop reason first and raises a typed error:
266
266
 
267
- ```ts
267
+ ```ts fragment
268
268
  import { AiRefusedError } from "@zerotal/ai";
269
269
 
270
270
  try {
@@ -283,7 +283,7 @@ with `drivers.anthropic.fallbacks: false`.
283
283
 
284
284
  ## Embeddings
285
285
 
286
- ```ts
286
+ ```ts fragment
287
287
  const { embeddings } = await Ai.embed(["first chunk", "second chunk"]);
288
288
  ```
289
289
 
@@ -294,7 +294,7 @@ One vector per input, in input order.
294
294
  A queued generation is serialized, so its completion handler is registered by **name**
295
295
  — a closure cannot survive the trip to a worker process:
296
296
 
297
- ```ts
297
+ ```ts fragment
298
298
  // in a service provider's onBooted(), so the worker registers it too
299
299
  Ai.onGenerated("summarize-ticket", async (response, meta) => {
300
300
  await Ticket.query().where("id", meta.ticketId).update({ summary: response.text });
@@ -313,7 +313,7 @@ in-process where its tools are.
313
313
  `AiFake` replaces the container binding and answers from a script. No API key, no
314
314
  network, no flakiness:
315
315
 
316
- ```ts
316
+ ```ts fragment
317
317
  import { AiFake } from "@zerotal/ai";
318
318
 
319
319
  const ai = AiFake.install();
@@ -361,7 +361,7 @@ suffix, a gateway that rewrites the base URL.
361
361
  Implement `AiDriver` — `text`, `stream`, `object`, `countTokens`, `verify` — and
362
362
  register it:
363
363
 
364
- ```ts
364
+ ```ts fragment
365
365
  // in a service provider's onBooted()
366
366
  const ai = app.container.makeSync("ai");
367
367
  ai.extend("bedrock", () => new BedrockDriver(config));
@@ -371,3 +371,110 @@ Nothing else is needed. Spend ceilings, redaction, telemetry, the lock, and the
371
371
  loop all live above the driver, so a new provider is a translation layer and nothing
372
372
  more. `agent()` is optional on the interface and none of the built-in three implement
373
373
  it — they all run the same shared loop.
374
+
375
+ ## Reference
376
+
377
+ Every exported name, grouped by the job it belongs to. The behaviour is in the
378
+ sections above; this is the index.
379
+
380
+ ### Requests and responses
381
+
382
+ | Name | Description |
383
+ | ------------------- | ------------------------------------------------------------------------------ |
384
+ | `AiRequest` | What every generation call takes. `prompt` and `messages` are interchangeable. |
385
+ | `AiResponse` | A finished, non-streaming generation. |
386
+ | `AiStreamChunk` | One event from a streaming generation. |
387
+ | `AiObjectResponse` | A structured-output generation: the parsed value plus the usual accounting. |
388
+ | `AiMessage` | One turn of a conversation. |
389
+ | `AiRole` | Who said it. Tool results ride inside a `user` turn, as the providers expect. |
390
+ | `AiUsage` | Token accounting for one request. Fields a provider does not report stay 0. |
391
+ | `AiStopReason` | Why generation stopped. `refusal` is a successful HTTP response, not an error. |
392
+ | `AiEffort` | How hard the model should work before answering. Mapped per-driver. |
393
+ | `AiProviderOptions` | Per-driver escape hatch, keyed by driver name and passed through untouched. |
394
+ | `AiEmbedRequest` | A vector embedding request. |
395
+ | `AiEmbedResponse` | Embeddings, one vector per input, in input order. |
396
+
397
+ ### Tools and the agent loop
398
+
399
+ | Name | Description |
400
+ | ---------------- | -------------------------------------------------------------------------------------- |
401
+ | `AiToolCall` | A tool call the model asked for, lifted out of whatever block shape the provider used. |
402
+ | `AiToolResult` | The answer to one `AiToolCall`. |
403
+ | `AiToolContext` | What a tool handler is told about the turn that invoked it. |
404
+ | `AiToolCalled` | Emitted once per tool call inside an agent run. |
405
+ | `AiAgentRequest` | An agent run, plus the two things only the caller can decide. |
406
+ | `AgentOptions` | What the agent loop needs from the caller, beyond the request itself. |
407
+ | `AiAgentResult` | The result of running the agent loop to completion. |
408
+ | `AiAgentStep` | One tool call and its result within an agent run. |
409
+
410
+ ### Errors
411
+
412
+ | Name | Description |
413
+ | -------------------------- | --------------------------------------------------------------------------- |
414
+ | `AiError` | Base class for all `@zerotal/ai` errors. |
415
+ | `AiConfigError` | Thrown at boot, or on first use, for a config combination that cannot work. |
416
+ | `AiRequestError` | Thrown for any other non-2xx from the provider, carrying its status. |
417
+ | `AiRateLimitError` | Thrown when the provider rate-limits. The SDKs already retried. |
418
+ | `AiSpendLimitError` | Thrown when a request would breach a configured spend ceiling. |
419
+ | `AiAgentLimitError` | Thrown when the agent loop hits its step or resume ceiling. |
420
+ | `AiCancelledError` | Thrown when the caller's `AbortSignal` fired before the call finished. |
421
+ | `AiDriverUnavailableError` | Thrown when a driver's optional peer package is not installed. |
422
+ | `UnknownAiDriverError` | Thrown for a driver name the manager does not know. |
423
+
424
+ ### Configuration
425
+
426
+ | Name | Description |
427
+ | ----------------------- | ----------------------------------------------------------------------------- |
428
+ | `AiConfigInput` | What `AiConfig()` accepts — every key optional, all the way down. |
429
+ | `AiConfigFromEnv` | The zero-config fallback: an Anthropic driver built from `ANTHROPIC_API_KEY`. |
430
+ | `AiLimitsConfigShape` | Spend ceilings, enforced before the request leaves. |
431
+ | `AiAgentConfigShape` | How the agent loop behaves. |
432
+ | `AnthropicConfigShape` | Anthropic driver settings. |
433
+ | `OpenAiConfigShape` | OpenAI driver settings. |
434
+ | `OllamaConfigShape` | Ollama driver settings — a local server, so no key. |
435
+ | `EmbeddingsConfigShape` | Embeddings are their own block with their own driver. |
436
+
437
+ ### Drivers and pricing
438
+
439
+ | Name | Description |
440
+ | ------------------------ | ---------------------------------------------------------------------------- |
441
+ | `AnthropicDriver` | The Anthropic driver. |
442
+ | `OpenAiDriver` | The OpenAI driver — Chat Completions over `fetch`, no SDK. |
443
+ | `OllamaDriver` | The Ollama driver — a local model server, so no API key and no billing. |
444
+ | `EmbeddingsDriver` | What an embeddings provider implements. |
445
+ | `OpenAiEmbeddingsDriver` | OpenAI embeddings over `fetch`. No SDK, no dependency. |
446
+ | `OllamaEmbeddingsDriver` | Ollama embeddings — a local server, so no key and no per-token cost. |
447
+ | `DriverStatus` | What `zt ai:test` prints for one driver. |
448
+ | `ModelPrice` | USD per million tokens. |
449
+ | `modelPrice` | The price for a model, or `undefined` when we have none. |
450
+ | `estimateCost` | Estimated USD for one request's usage. Returns 0 for an unpriced model. |
451
+ | `modelRejectsSampling` | Whether a Claude model rejects `temperature` / `top_p` / `top_k` with a 400. |
452
+
453
+ ### Spend and statistics
454
+
455
+ | Name | Description |
456
+ | -------------------- | --------------------------------------------------------- |
457
+ | `spentToday` | USD recorded so far today, in this process. |
458
+ | `resetSpend` | Reset the ledger. Tests, and the `ai:spend --reset` path. |
459
+ | `AiDelivery` | One recorded generation. |
460
+ | `modelStats` | Per-model roll-up over everything still in the buffer. |
461
+ | `ModelStat` | Rolled-up figures for one model. |
462
+ | `recentGenerations` | The most recent generations, newest first. |
463
+ | `refusalRate` | Share of recorded calls that the provider declined, 0–1. |
464
+ | `resetStats` | Reset the buffer. Tests. |
465
+ | `CapturedGeneration` | One recorded call, as `AiFake` captures it. |
466
+
467
+ ### Queued generation
468
+
469
+ | Name | Description |
470
+ | ---------------- | -------------------------------------------- |
471
+ | `AiQueueOptions` | What `Ai.queue()` needs beyond the request. |
472
+ | `AiQueueHandler` | What a queued generation's handler receives. |
473
+
474
+ ### Structured-output schemas
475
+
476
+ | Name | Description |
477
+ | ----------------- | --------------------------------------------------------------------------- |
478
+ | `SchemaInput` | Either shape callers have on hand: the builder map, or the raw definitions. |
479
+ | `toSchema` | Normalise either input shape to raw definitions. |
480
+ | `translateSchema` | Translate a validator schema into the JSON Schema the providers accept. |
@@ -73,7 +73,7 @@ helper reaches it anywhere.
73
73
  > **Note** — `Application.create({ providers })` and
74
74
  > `Application.create({ providers, config, env })` accept explicit providers and
75
75
  > config for lightweight in-process test setups that bypass auto-discovery. See
76
- > [Testing](/docs/testing/index).
76
+ > [Testing](/docs/testing).
77
77
 
78
78
  ### The current application
79
79
 
@@ -159,7 +159,7 @@ Framework packages (`@zerotal/orm`, `@zerotal/auth`, `@zerotal/notifications`,
159
159
  their own providers. List them in the **providers array** in
160
160
  `bootstrap/providers.ts` and pass that array to `Application.create({ providers })`:
161
161
 
162
- ```typescript
162
+ ```typescript fragment
163
163
  // bootstrap/providers.ts
164
164
  import { DatabaseProvider } from "@zerotal/orm";
165
165
  import { AuthProvider } from "@zerotal/auth";
@@ -170,7 +170,7 @@ const providers = [DatabaseProvider, AuthProvider, AppServiceProvider];
170
170
  export default providers;
171
171
  ```
172
172
 
173
- ```typescript
173
+ ```typescript fragment
174
174
  // bootstrap/app.ts
175
175
  import { Application, basePath } from "zerotal";
176
176
  import providers from "./providers.ts";
@@ -190,7 +190,7 @@ A deferred provider boots lazily — only the first time one of its container
190
190
  bindings is resolved. This keeps cold-start fast when a service isn't used on
191
191
  every request.
192
192
 
193
- ```typescript
193
+ ```typescript fragment
194
194
  // bootstrap/app.ts — defer() is called on the app instance (and returns it)
195
195
  const app = Application.create({ providers });
196
196
 
@@ -216,7 +216,7 @@ config-driven wiring, middleware. But when you just need to register a binding o
216
216
  two, that's a lot of ceremony. For those cases, `bind()` lets you register
217
217
  straight from `bootstrap/app.ts`:
218
218
 
219
- ```typescript
219
+ ```typescript fragment
220
220
  // bootstrap/app.ts
221
221
  import { Application, basePath } from "zerotal";
222
222
  import { Clock, SystemClock } from "../app/services/clock.ts";
@@ -249,7 +249,7 @@ Declare where your routes live; the application loads them at boot, after every
249
249
  provider has registered its middleware groups (so a route file can always
250
250
  reference `web`, `api`, or any provider-supplied group).
251
251
 
252
- ```typescript
252
+ ```typescript fragment
253
253
  // bootstrap/app.ts
254
254
  Application.create().routing({
255
255
  web: "./routes/web.ts",
@@ -262,7 +262,7 @@ middleware group, `api` mounts at `/api` with the `api` group. Any other group
262
262
  name must declare both `prefix` and `middleware` explicitly (or an error is thrown
263
263
  at boot):
264
264
 
265
- ```typescript
265
+ ```typescript fragment
266
266
  // bootstrap/app.ts
267
267
  .routing({
268
268
  web: "./routes/web.ts",
@@ -273,7 +273,7 @@ at boot):
273
273
  For directory-based routing, use `fileBasedRouting()` — same key semantics, but
274
274
  each value is a directory that's scanned for exported HTTP-method handlers:
275
275
 
276
- ```typescript
276
+ ```typescript fragment
277
277
  // bootstrap/app.ts
278
278
  Application.create().fileBasedRouting({ web: "./app/routes" });
279
279
  ```
@@ -285,7 +285,7 @@ root regardless of the calling file.
285
285
 
286
286
  Register global middleware that runs on every request, in array order:
287
287
 
288
- ```typescript
288
+ ```typescript fragment
289
289
  // bootstrap/app.ts
290
290
  import { CorsMiddleware, SecureHeadersMiddleware } from "zerotal";
291
291
 
@@ -296,7 +296,7 @@ The resolved pipeline runs provider-registered middleware first, then everything
296
296
  you added via `.use()`. You can read the final ordering back off the app instance
297
297
  (handy in a test or a diagnostic):
298
298
 
299
- ```typescript
299
+ ```typescript fragment
300
300
  // in a test or diagnostic
301
301
  currentApp().globalMiddleware; // PipeClass[] in execution order
302
302
  ```
@@ -315,7 +315,7 @@ class to push it onto the global pipeline automatically. See
315
315
  These are the remaining configuration methods, all called on the app instance in
316
316
  `bootstrap/app.ts` (and all returning it, so they chain):
317
317
 
318
- ```typescript
318
+ ```typescript fragment
319
319
  // bootstrap/app.ts
320
320
  import { Handler } from "../app/exceptions/Handler.ts";
321
321
 
@@ -360,7 +360,7 @@ provider hooks through a fixed set of phases.
360
360
  5. **Started** — `Bun.serve()` is live; `onStarted()` fires. A health endpoint, a
361
361
  PID file, and `SIGTERM`/`SIGINT`/`SIGUSR2` handlers are installed.
362
362
 
363
- ```typescript
363
+ ```typescript fragment
364
364
  // zt.ts (managed) ultimately does:
365
365
  const app = (await import("./bootstrap/app.ts")).default;
366
366
  await app.start(Number(env("PORT", 3000)));
package/docs/arch.md CHANGED
@@ -24,7 +24,7 @@ bun add -d @zerotal/arch
24
24
 
25
25
  Register the provider in `bootstrap/providers.ts`:
26
26
 
27
- ```typescript
27
+ ```typescript fragment
28
28
  // bootstrap/providers.ts
29
29
  import { ArchProvider } from "@zerotal/arch";
30
30
 
@@ -189,7 +189,7 @@ The transport is exported separately from the tools, on the `@zerotal/arch/mcp`
189
189
  A tool is a plain object with a schema and a `run`, so you can serve your own alongside
190
190
  these — or serve these over a transport of your own.
191
191
 
192
- ```typescript
192
+ ```typescript fragment
193
193
  import { McpServer, serveStdio } from "@zerotal/arch/mcp";
194
194
  import { archTools, vendoredDocsDir, spawnProbe } from "@zerotal/arch";
195
195
 
@@ -256,8 +256,30 @@ The transport is on the `@zerotal/arch/mcp` subpath: `McpServer`, `McpServerOpti
256
256
  `JsonRpcRequest`, `JsonRpcResponse`, `JsonRpcSuccess`, `JsonRpcFailure`, `JsonRpcId`,
257
257
  `RpcError`, `Meta`, `MODERN_VERSION`, `LEGACY_VERSIONS` and `SUPPORTED_VERSIONS`.
258
258
 
259
+ ## What `stable` covers here
260
+
261
+ The usual promise — anything importable without an `@internal` marker keeps its
262
+ shape for the rest of the 1.x line — and one more, because this package's real
263
+ interface is not its exports.
264
+
265
+ **The MCP tool contract is covered.** Tool names, the arguments they accept, and
266
+ the shape of what they return do not change within 1.x. That is the surface an
267
+ agent client is configured against: a renamed tool or a dropped field breaks
268
+ every `.mcp.json` pointing at this server, and none of it is visible to a
269
+ type-level check — `archTools = (ctx) => ArchTool[]` is byte-identical whatever
270
+ the tools are called. `mcp-surface.md` records all nine and CI diffs it.
271
+
272
+ **The protocol revision is not.** Which version of the Model Context Protocol the
273
+ server speaks follows the protocol, not this package's major version. A revision
274
+ that requires a transport change will land in a minor release, described in the
275
+ notes.
276
+
277
+ **The writers behind `arch:install` are not.** `detectAgents`, `applyMcpConfig`,
278
+ `applyBlock`, `buildGuidelines` and the rest are `@internal`: they exist for the
279
+ command, and the format of the files it writes is not a promise.
280
+
259
281
  ## Next steps
260
282
 
261
283
  - [Commands](/docs/commands) — the full `bun zt` vocabulary an agent is told about.
262
284
  - [Package Development](/docs/package-development) — contributing doctor checks of your own.
263
- - [Support policy](/docs/support-policy#maturity-levels) — what `beta` promises.
285
+ - [Support policy](/docs/support-policy#maturity-levels) — what `stable` promises.
package/docs/assets.md CHANGED
@@ -159,14 +159,14 @@ sufficient for most apps.
159
159
 
160
160
  Add `Router.static` to serve `public/` at the root:
161
161
 
162
- ```typescript
162
+ ```typescript fragment
163
163
  // routes/index.ts
164
164
  Router.static("/", "public");
165
165
  ```
166
166
 
167
167
  Or target `public/css/` specifically:
168
168
 
169
- ```typescript
169
+ ```typescript fragment
170
170
  // routes/index.ts
171
171
  Router.static("/css", "public/css");
172
172
  ```
@@ -242,7 +242,7 @@ public/
242
242
  runs as part of the JS build) and boots the Inertia client, resolving each page from
243
243
  the generated registry:
244
244
 
245
- ```typescript
245
+ ```typescript fragment
246
246
  // resources/js/app.tsx (React)
247
247
  import { createInertiaApp, type ResolvedComponent } from "@inertiajs/react";
248
248
  import { createRoot } from "react-dom/client";
@@ -261,7 +261,7 @@ createInertiaApp({
261
261
  });
262
262
  ```
263
263
 
264
- ```typescript
264
+ ```typescript fragment
265
265
  // resources/js/app.tsx (Vue)
266
266
  import { createInertiaApp } from "@inertiajs/vue3";
267
267
  import { createApp, h, type DefineComponent } from "vue";
@@ -393,7 +393,7 @@ the connections that need it most.
393
393
 
394
394
  Set a per-extension loader to emit them as separate files instead:
395
395
 
396
- ```typescript
396
+ ```typescript fragment
397
397
  // config/app.ts
398
398
  export default AppConfig({
399
399
  assets: {
@@ -523,7 +523,7 @@ Bun's bundler understands CSS Modules out of the box. Import a `.module.css` fil
523
523
  use the generated class names — styles are scoped to the component, so names never
524
524
  collide across files:
525
525
 
526
- ```tsx
526
+ ```tsx fragment
527
527
  // app/components/Button.tsx
528
528
  import styles from "./Button.module.css";
529
529
 
@@ -603,7 +603,7 @@ the asset is served, and that a deploy invalidates the old one.
603
603
  **Assert the page references the asset**, which catches a build config that
604
604
  silently stopped emitting it:
605
605
 
606
- ```typescript
606
+ ```typescript fragment
607
607
  // tests/http/assets.test.ts
608
608
  import { test } from "bun:test";
609
609
  import { createApp } from "../helpers.ts";
@@ -621,7 +621,7 @@ test("the layout links the built stylesheet", async () => {
621
621
  **Assert it is actually served**, because a reference to a missing file is a
622
622
  `404` the page will not tell you about:
623
623
 
624
- ```typescript
624
+ ```typescript fragment
625
625
  // tests/http/assets.test.ts
626
626
  const css = await app.get("/assets/app.css");
627
627
 
@@ -637,7 +637,7 @@ testing only one leaves the failure that actually reaches users uncovered.
637
637
  carries a `?v=` token that changes on rebuild; in production `ASSET_VERSION`
638
638
  drives the Inertia version header:
639
639
 
640
- ```typescript
640
+ ```typescript fragment
641
641
  // tests/http/assets.test.ts
642
642
  test("asset URLs carry a version in dev", async () => {
643
643
  const res = await app.get("/");
@@ -662,7 +662,7 @@ one obvious job rather than scattering unrelated failures through the whole suit
662
662
  pinned down without a request at all. This is the cheapest place to catch a
663
663
  misconfigured CDN prefix:
664
664
 
665
- ```typescript
665
+ ```typescript fragment
666
666
  // tests/unit/asset.test.ts
667
667
  import { asset } from "zerotal/assets";
668
668
 
@@ -679,7 +679,7 @@ Inertia apps carry an asset version on every page object, and a request whose
679
679
  bundle do a full reload after a deploy. That flow only fires when the version
680
680
  actually changes between builds, so it is worth one test:
681
681
 
682
- ```typescript
682
+ ```typescript fragment
683
683
  // tests/http/assets.test.ts
684
684
  test("a stale bundle is told to reload", async () => {
685
685
  const res = await app.get("/dashboard", {