@zerotal/arch 1.7.4 → 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 (119) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/README.md +4 -2
  3. package/docs/about.md +16 -16
  4. package/docs/admin/actions.md +9 -9
  5. package/docs/admin/auth.md +2 -2
  6. package/docs/admin/dashboard.md +5 -5
  7. package/docs/admin/extending-ui.md +12 -12
  8. package/docs/admin/extending.md +16 -7
  9. package/docs/admin/forms.md +4 -4
  10. package/docs/admin/index.md +2 -2
  11. package/docs/admin/operations.md +9 -9
  12. package/docs/admin/resources.md +3 -3
  13. package/docs/admin/structure.md +6 -6
  14. package/docs/admin/tables.md +8 -8
  15. package/docs/admin/testing.md +4 -4
  16. package/docs/ai.md +118 -11
  17. package/docs/application.md +12 -12
  18. package/docs/arch.md +25 -3
  19. package/docs/assets.md +11 -11
  20. package/docs/audit.md +13 -13
  21. package/docs/authentication.md +39 -39
  22. package/docs/authorization.md +13 -13
  23. package/docs/broadcasting/channels.md +2 -2
  24. package/docs/broadcasting/client.md +2 -2
  25. package/docs/broadcasting/events.md +9 -10
  26. package/docs/broadcasting/index.md +1 -1
  27. package/docs/broadcasting/testing.md +5 -5
  28. package/docs/cache.md +15 -13
  29. package/docs/carbon.md +35 -35
  30. package/docs/changelog.md +105 -1
  31. package/docs/client/index.md +13 -13
  32. package/docs/commands.md +4 -4
  33. package/docs/components.md +116 -115
  34. package/docs/config-system.md +4 -4
  35. package/docs/container.md +27 -27
  36. package/docs/context.md +27 -27
  37. package/docs/contributing.md +28 -0
  38. package/docs/controllers.md +10 -10
  39. package/docs/conventions.md +23 -15
  40. package/docs/cookies.md +3 -3
  41. package/docs/csrf.md +6 -6
  42. package/docs/database.md +22 -17
  43. package/docs/deployment.md +2 -2
  44. package/docs/devtools.md +16 -16
  45. package/docs/email-verification.md +8 -8
  46. package/docs/encryption.md +7 -7
  47. package/docs/errors.md +6 -6
  48. package/docs/events.md +12 -11
  49. package/docs/flow/components.md +32 -32
  50. package/docs/flow/decorators.md +18 -18
  51. package/docs/flow/events.md +16 -16
  52. package/docs/flow/forms.md +17 -17
  53. package/docs/flow/icons.md +5 -5
  54. package/docs/flow/index.md +20 -20
  55. package/docs/flow/layouts.md +35 -35
  56. package/docs/flow/lifecycle.md +16 -16
  57. package/docs/flow/models.md +9 -9
  58. package/docs/flow/pagination.md +8 -8
  59. package/docs/flow/performance.md +7 -7
  60. package/docs/flow/references.md +3 -3
  61. package/docs/flow/routing.md +26 -26
  62. package/docs/flow/testing.md +20 -20
  63. package/docs/getting-started.md +18 -13
  64. package/docs/health.md +17 -8
  65. package/docs/helpers.md +17 -17
  66. package/docs/i18n.md +10 -10
  67. package/docs/inertia/devtools.md +4 -4
  68. package/docs/inertia/index.md +6 -6
  69. package/docs/inertia/props.md +21 -21
  70. package/docs/inertia/rendering.md +9 -9
  71. package/docs/inertia/ssr.md +2 -2
  72. package/docs/lifecycle.md +2 -2
  73. package/docs/lock.md +11 -11
  74. package/docs/logger.md +12 -12
  75. package/docs/media.md +22 -22
  76. package/docs/middleware.md +17 -17
  77. package/docs/migrations.md +13 -13
  78. package/docs/monitor.md +6 -6
  79. package/docs/notifications.md +24 -24
  80. package/docs/orm/casts.md +14 -14
  81. package/docs/orm/factories.md +8 -8
  82. package/docs/orm/index.md +22 -20
  83. package/docs/orm/lifecycle.md +13 -13
  84. package/docs/orm/queries.md +28 -28
  85. package/docs/orm/relationships.md +16 -16
  86. package/docs/orm/serialization.md +11 -11
  87. package/docs/package-development.md +10 -10
  88. package/docs/pagination.md +14 -14
  89. package/docs/password-reset.md +6 -6
  90. package/docs/providers.md +11 -11
  91. package/docs/query-builder.md +18 -18
  92. package/docs/queue.md +14 -12
  93. package/docs/rate-limiting.md +6 -6
  94. package/docs/responses.md +14 -14
  95. package/docs/roles-and-2fa.md +9 -9
  96. package/docs/routing.md +44 -44
  97. package/docs/scaffolding.md +1 -1
  98. package/docs/scheduler.md +10 -10
  99. package/docs/seeding.md +6 -6
  100. package/docs/session.md +16 -14
  101. package/docs/social.md +14 -14
  102. package/docs/storage.md +25 -13
  103. package/docs/structure.md +2 -2
  104. package/docs/support-policy.md +23 -10
  105. package/docs/telemetry.md +12 -12
  106. package/docs/tenancy.md +34 -40
  107. package/docs/testing/browser.md +6 -6
  108. package/docs/testing/console.md +3 -3
  109. package/docs/testing/database.md +10 -10
  110. package/docs/testing/flow-browser.md +6 -6
  111. package/docs/testing/http.md +12 -12
  112. package/docs/testing/index.md +3 -3
  113. package/docs/testing/mocking.md +8 -8
  114. package/docs/upgrade.md +1 -1
  115. package/docs/validator.md +19 -19
  116. package/docs/view.md +14 -14
  117. package/package.json +4 -4
  118. package/src/index.ts +25 -1
  119. package/src/tools/searchDocs.ts +7 -0
@@ -47,7 +47,7 @@ query → hydrate row → afterFind
47
47
  Run async callbacks before or after any persistence event. Register them once at
48
48
  boot (typically in a service provider's `onBooting()`):
49
49
 
50
- ```typescript
50
+ ```typescript fragment
51
51
  // in a ServiceProvider.onBooting()
52
52
  import { HookRegistry } from "@zerotal/orm";
53
53
 
@@ -76,7 +76,7 @@ HookRegistry.register(Order, "beforeUpdate", async (order) => {
76
76
 
77
77
  The signature is:
78
78
 
79
- ```typescript
79
+ ```typescript fragment
80
80
  HookRegistry.register<T>(ModelClass: Function, hook: HookName, fn: (model: T) => Promise<void> | void): void
81
81
  ```
82
82
 
@@ -108,7 +108,7 @@ names follow the event-tense convention (`creating`/`created`, `updating`/
108
108
  `updated`, `saving`/`saved`, `deleting`/`deleted`, `retrieved`) — implement only
109
109
  the ones you need:
110
110
 
111
- ```typescript
111
+ ```typescript fragment
112
112
  // app/observers/UserObserver.ts
113
113
  import type { ModelObserver } from "@zerotal/orm";
114
114
 
@@ -135,7 +135,7 @@ export class UserObserver implements ModelObserver<User> {
135
135
 
136
136
  Register the observer once at boot:
137
137
 
138
- ```typescript
138
+ ```typescript fragment
139
139
  // in a ServiceProvider.onBooting()
140
140
  import { User } from "#app/models/User.ts";
141
141
  import { UserObserver } from "#app/observers/UserObserver.ts";
@@ -229,7 +229,7 @@ export class Subscription extends Model.using(State) {
229
229
  Register callbacks that fire after a successful transition. Call `onTransition` in
230
230
  a service provider's `onBooting()` so it runs once at startup:
231
231
 
232
- ```typescript
232
+ ```typescript fragment
233
233
  // in a ServiceProvider.onBooting()
234
234
  // Specific state
235
235
  Post.onTransition("published", async (post, { from }) => {
@@ -245,7 +245,7 @@ Post.onTransition("*", async (post, { from, to }) => {
245
245
 
246
246
  ### Using transitions
247
247
 
248
- ```typescript
248
+ ```typescript fragment
249
249
  // in a controller or service
250
250
  // Validates canTransitionTo, runs the guard, saves, then fires callbacks
251
251
  await post.transitionTo("review");
@@ -268,7 +268,7 @@ followed by any `"*"` wildcard callbacks.
268
268
 
269
269
  ### State machine in a controller
270
270
 
271
- ```typescript
271
+ ```typescript fragment
272
272
  // app/controllers/PostController.ts
273
273
  import { StateError } from "@zerotal/orm";
274
274
 
@@ -295,7 +295,7 @@ observers — useful when consumers live in separate modules. See the
295
295
  [ORM index page](/docs/orm#bridging-model-events-to-the-app-event-bus) for the
296
296
  companion overview.
297
297
 
298
- ```typescript
298
+ ```typescript fragment
299
299
  // app/events/PostEvents.ts
300
300
  export class PostCreated {
301
301
  constructor(public post: Post) {}
@@ -319,7 +319,7 @@ ORM runs standalone).
319
319
 
320
320
  Subscribe using the event bus anywhere in your application:
321
321
 
322
- ```typescript
322
+ ```typescript fragment
323
323
  // in a ServiceProvider or listener module
324
324
  import { Events } from "zerotal";
325
325
 
@@ -336,7 +336,7 @@ queries.
336
336
 
337
337
  ### Implementing pruning
338
338
 
339
- ```typescript
339
+ ```typescript fragment
340
340
  // app/models/AuditLog.ts
341
341
  @table("audit_logs")
342
342
  export class AuditLog extends Model {
@@ -354,7 +354,7 @@ export class AuditLog extends Model {
354
354
  }
355
355
  ```
356
356
 
357
- ```typescript
357
+ ```typescript fragment
358
358
  // in a scheduled task or REPL
359
359
  const removed = await AuditLog.prune(); // default chunk size: 1000
360
360
  const fewer = await AuditLog.prune(500); // custom chunk size
@@ -362,7 +362,7 @@ const fewer = await AuditLog.prune(500); // custom chunk size
362
362
 
363
363
  The signature is:
364
364
 
365
- ```typescript
365
+ ```typescript fragment
366
366
  static prune(chunkSize = 1000): Promise<number>
367
367
  ```
368
368
 
@@ -372,7 +372,7 @@ method throws.
372
372
 
373
373
  ### Scheduling pruning
374
374
 
375
- ```typescript
375
+ ```typescript fragment
376
376
  // bootstrap/app.ts or a scheduler provider
377
377
  scheduler
378
378
  .job("prune-audit-logs", async () => {
@@ -16,7 +16,7 @@ fluent API is available on `DB.table()` for unmodelled tables (see
16
16
 
17
17
  ## Basic finders
18
18
 
19
- ```typescript
19
+ ```typescript fragment
20
20
  // in a controller or service
21
21
  // By primary key — returns null if not found
22
22
  const user = await User.find(1);
@@ -48,7 +48,7 @@ const admins = await User.query().where("role", "admin").get<User>();
48
48
 
49
49
  ## Create, update, delete
50
50
 
51
- ```typescript
51
+ ```typescript fragment
52
52
  // in a controller or service
53
53
  // INSERT a single record
54
54
  const user = await User.create({ name: "Alice", email: "alice@example.com" });
@@ -81,7 +81,7 @@ export class Post extends Model.using(SoftDeletes) {
81
81
  }
82
82
  ```
83
83
 
84
- ```typescript
84
+ ```typescript fragment
85
85
  // in a controller or service
86
86
  // Soft delete — sets deleted_at; row hidden from default queries
87
87
  await post.delete();
@@ -108,7 +108,7 @@ await Post.onlyTrashed().get();
108
108
  `upsert` inserts a row, or updates the named columns when a conflict on `conflictKeys`
109
109
  occurs:
110
110
 
111
- ```typescript
111
+ ```typescript fragment
112
112
  function upsert(
113
113
  data: InsertPayload<T>,
114
114
  conflictKeys: (keyof T & string)[],
@@ -116,7 +116,7 @@ function upsert(
116
116
  ): Promise<void>;
117
117
  ```
118
118
 
119
- ```typescript
119
+ ```typescript fragment
120
120
  // in a controller or service
121
121
  // Conflict on email → update name and role; omit updateCols to update every column
122
122
  await User.upsert(
@@ -132,7 +132,7 @@ await User.upsert(
132
132
 
133
133
  ## Convenience finders
134
134
 
135
- ```typescript
135
+ ```typescript fragment
136
136
  // in a controller or service
137
137
  // Find or create — returns the existing or newly-created instance
138
138
  const user = await User.firstOrCreate(
@@ -161,7 +161,7 @@ const user = await User.findOrNew(1);
161
161
 
162
162
  ### Filtering
163
163
 
164
- ```typescript
164
+ ```typescript fragment
165
165
  // in a controller or service
166
166
  Post.query()
167
167
  .where("status", "published")
@@ -184,7 +184,7 @@ Post.query()
184
184
 
185
185
  ### Selecting
186
186
 
187
- ```typescript
187
+ ```typescript fragment
188
188
  // in a controller or service
189
189
  Post.query().select("id", "title", "slug").get();
190
190
  Post.query().selectRaw("COUNT(*) as total, MAX(score) as top").get();
@@ -193,14 +193,14 @@ Post.query().distinct().select("user_id").get();
193
193
 
194
194
  ### Ordering and limits
195
195
 
196
- ```typescript
196
+ ```typescript fragment
197
197
  // in a controller or service
198
198
  Post.query().orderBy("published_at", "desc").orderBy("id", "asc").limit(10).offset(20).get();
199
199
  ```
200
200
 
201
201
  ### Joins
202
202
 
203
- ```typescript
203
+ ```typescript fragment
204
204
  // in a controller or service
205
205
  // Inner join
206
206
  Post.query()
@@ -228,7 +228,7 @@ Post.query()
228
228
 
229
229
  ### Grouping and aggregates
230
230
 
231
- ```typescript
231
+ ```typescript fragment
232
232
  // in a controller or service
233
233
  // Terminal aggregates — return a single value
234
234
  const total = await Post.query().where("status", "published").count();
@@ -250,7 +250,7 @@ await DB.table("posts")
250
250
 
251
251
  Load aggregate values alongside model instances without extra queries:
252
252
 
253
- ```typescript
253
+ ```typescript fragment
254
254
  // in a controller or service
255
255
  const posts = await Post.query()
256
256
  .withCount("comments")
@@ -268,7 +268,7 @@ posts[0].commentsAvg_rating; // number | null
268
268
 
269
269
  ### Relation existence filtering
270
270
 
271
- ```typescript
271
+ ```typescript fragment
272
272
  // in a controller or service
273
273
  // Posts that have at least one comment
274
274
  Post.query().has("comments").get();
@@ -299,7 +299,7 @@ Post.query()
299
299
 
300
300
  Build queries dynamically based on optional inputs without branching `if` statements:
301
301
 
302
- ```typescript
302
+ ```typescript fragment
303
303
  // in a controller
304
304
  const posts = await Post.query()
305
305
  .when(ctx.query("status"), (q, status) => q.where("status", status))
@@ -314,7 +314,7 @@ making it easy to chain optional filters.
314
314
 
315
315
  ### Pessimistic locking
316
316
 
317
- ```typescript
317
+ ```typescript fragment
318
318
  // in a controller or service
319
319
  // Exclusive write lock — SELECT … FOR UPDATE
320
320
  await DB.transaction(async (trx) => {
@@ -346,7 +346,7 @@ const post = await Post.query().where("id", postId).sharedLock().first();
346
346
  Classic page-number pagination. Best for small-to-medium tables where users jump to
347
347
  arbitrary pages:
348
348
 
349
- ```typescript
349
+ ```typescript fragment
350
350
  // in a controller
351
351
  const page = await Post.query()
352
352
  .where("status", "published")
@@ -365,7 +365,7 @@ const page = await Post.query()
365
365
  Simple, performant pagination using the last-seen ID as a cursor. Stable against
366
366
  inserts/deletes between pages:
367
367
 
368
- ```typescript
368
+ ```typescript fragment
369
369
  // in a controller
370
370
  const p1 = await Post.query().cursorPaginate({ limit: 20 });
371
371
  const p2 = await Post.query().cursorPaginate({ cursor: p1.nextCursor, limit: 20 });
@@ -379,7 +379,7 @@ const p2 = await Post.query().cursorPaginate({ cursor: p1.nextCursor, limit: 20
379
379
  Scales to any table size. Uses an indexed column value as the cursor instead of an
380
380
  offset. The best choice for infinite scroll and large datasets:
381
381
 
382
- ```typescript
382
+ ```typescript fragment
383
383
  // in a controller
384
384
  const p1 = await Post.query()
385
385
  .where("status", "published")
@@ -409,7 +409,7 @@ ordering.
409
409
 
410
410
  Use these for large datasets to avoid loading thousands of rows into memory at once:
411
411
 
412
- ```typescript
412
+ ```typescript fragment
413
413
  // in a console command or job
414
414
  // Process in fixed-size batches
415
415
  await Post.query().chunk(100, async (posts) => {
@@ -442,7 +442,7 @@ await Post.query().each(async (post) => {
442
442
  The builder compiles to SQL without executing, so you can inspect exactly what will
443
443
  run:
444
444
 
445
- ```typescript
445
+ ```typescript fragment
446
446
  // in a controller or service
447
447
  // Compiled SQL with `?` placeholders (no bindings)
448
448
  const sql = Post.query().where("status", "published").toSql();
@@ -465,7 +465,7 @@ Post.query().where("active", 1).dump().get();
465
465
 
466
466
  Clone a base query to reuse it with different conditions:
467
467
 
468
- ```typescript
468
+ ```typescript fragment
469
469
  // in a controller or service
470
470
  const base = Post.query().where("active", 1);
471
471
  const admins = await base.clone().where("role", "admin").get();
@@ -476,7 +476,7 @@ const editors = await base.clone().where("role", "editor").get();
476
476
 
477
477
  ### Loading and refreshing
478
478
 
479
- ```typescript
479
+ ```typescript fragment
480
480
  // in a controller or service
481
481
  // Reload a fresh copy from the database (returns a new instance, doesn't mutate)
482
482
  const fresh = await post.fresh();
@@ -503,7 +503,7 @@ await post.loadMax("comments", "score");
503
503
 
504
504
  Know which fields have changed since the last database read or save:
505
505
 
506
- ```typescript
506
+ ```typescript fragment
507
507
  // in a controller or service
508
508
  post.name = "Changed";
509
509
 
@@ -534,7 +534,7 @@ post.markDirty("slug");
534
534
 
535
535
  ### Incrementing and touch
536
536
 
537
- ```typescript
537
+ ```typescript fragment
538
538
  // in a controller or service
539
539
  await post.increment("views"); // +1
540
540
  await post.increment("views", 5); // +5
@@ -546,7 +546,7 @@ await post.touch();
546
546
 
547
547
  ### Comparison and copying
548
548
 
549
- ```typescript
549
+ ```typescript fragment
550
550
  // in a controller or service
551
551
  // True if both are the same model class with the same primary key
552
552
  post.is(otherPost); // boolean
@@ -563,7 +563,7 @@ const copy = post.replicate(["slug", "viewCount"]);
563
563
 
564
564
  ### Saving without updating timestamps
565
565
 
566
- ```typescript
566
+ ```typescript fragment
567
567
  // in a controller or service
568
568
  await User.withoutTimestamps(async () => {
569
569
  user.role = "admin";
@@ -599,7 +599,7 @@ Apply them via `withScopes()`. The callback receives a proxy whose methods invok
599
599
  scope in turn — call them as separate statements (the proxy methods return `void`, so
600
600
  they do not chain):
601
601
 
602
- ```typescript
602
+ ```typescript fragment
603
603
  // in a controller
604
604
  const posts = await Post.query()
605
605
  .withScopes((s) => {
@@ -616,7 +616,7 @@ const posts = await Post.query()
616
616
  Global scopes are applied automatically to every query on the model. Register them in
617
617
  a [service provider's](/docs/providers) `onBooting()`:
618
618
 
619
- ```typescript
619
+ ```typescript fragment
620
620
  // in AppServiceProvider.onBooting()
621
621
  Post.addGlobalScope("tenant", (q) => q.where("tenant_id", currentTenantId()));
622
622
 
@@ -7,7 +7,7 @@ description: Define and query associations between models with relation decorato
7
7
 
8
8
  Relation decorators describe how your models connect — one-to-one, one-to-many, many-to-many, through, and polymorphic — so you can traverse and eager-load associated records without writing JOINs by hand.
9
9
 
10
- Zerotal's ORM supports all standard Active Record relationship types, plus polymorphic variants and eager loading with constraints. Relations are declared on the model class with decorators imported from `@zerotal/orm`; see [ORM](/docs/orm/index) for installing and configuring the package.
10
+ Zerotal's ORM supports all standard Active Record relationship types, plus polymorphic variants and eager loading with constraints. Relations are declared on the model class with decorators imported from `@zerotal/orm`; see [ORM](/docs/orm) for installing and configuring the package.
11
11
 
12
12
  ## One-to-one — @hasOne / @belongsTo
13
13
 
@@ -35,7 +35,7 @@ export class Profile extends Model {
35
35
  }
36
36
  ```
37
37
 
38
- ```typescript
38
+ ```typescript fragment
39
39
  // in a controller
40
40
  const user = await User.query().with("profile").findOrFail(1);
41
41
  const profile = user.profile; // Profile — no extra query
@@ -52,7 +52,7 @@ Both `@hasOne` and `@belongsTo` accept a `localKey` (defaults to `"id"`) to over
52
52
 
53
53
  A `User` has many `Post`s. The foreign key (`user_id`) lives on the `posts` table:
54
54
 
55
- ```typescript
55
+ ```typescript fragment
56
56
  // app/models/User.ts
57
57
  @table("users")
58
58
  export class User extends Model {
@@ -69,7 +69,7 @@ export class Post extends Model {
69
69
  }
70
70
  ```
71
71
 
72
- ```typescript
72
+ ```typescript fragment
73
73
  // in a controller
74
74
  const user = await User.query().with("posts").findOrFail(1);
75
75
  user.posts; // Post[]
@@ -84,7 +84,7 @@ const users = await User.query()
84
84
 
85
85
  Set or clear a `belongsTo` foreign key without having to know the parent's ID directly:
86
86
 
87
- ```typescript
87
+ ```typescript fragment
88
88
  // in a controller
89
89
  post.associate("author", user); // sets post.userId = user.id
90
90
  await post.save();
@@ -121,7 +121,7 @@ export class Tag extends Model {
121
121
 
122
122
  ### Pivot operations
123
123
 
124
- ```typescript
124
+ ```typescript fragment
125
125
  // in a controller
126
126
  // Attach one or multiple tags
127
127
  await post.tags.attach(tagId);
@@ -144,7 +144,7 @@ await post.tags.toggle(tagId);
144
144
 
145
145
  If the pivot table has additional columns, declare them with `withPivot`:
146
146
 
147
- ```typescript
147
+ ```typescript fragment
148
148
  // app/models/User.ts
149
149
  @manyToMany(() => Role, {
150
150
  pivotTable: "user_roles",
@@ -155,7 +155,7 @@ If the pivot table has additional columns, declare them with `withPivot`:
155
155
  roles!: ManyToMany<Role>;
156
156
  ```
157
157
 
158
- ```typescript
158
+ ```typescript fragment
159
159
  // in a controller — attach with extra pivot data
160
160
  await user.roles.attach(roleId, { assigned_by: adminId, assigned_at: new Date() });
161
161
  ```
@@ -166,7 +166,7 @@ await user.roles.attach(roleId, { assigned_by: adminId, assigned_at: new Date()
166
166
 
167
167
  Access distant models through an intermediate model. A `Country` has many `Post`s through `User`s:
168
168
 
169
- ```typescript
169
+ ```typescript fragment
170
170
  // app/models/Country.ts
171
171
  import { hasManyThrough, hasOneThrough } from "@zerotal/orm";
172
172
 
@@ -188,7 +188,7 @@ export class Country extends Model {
188
188
  }
189
189
  ```
190
190
 
191
- ```typescript
191
+ ```typescript fragment
192
192
  // in a controller
193
193
  const country = await Country.query().with("posts").findOrFail(1);
194
194
  country.posts; // Post[] — no manual JOIN required
@@ -202,7 +202,7 @@ Polymorphic relationships let a single model belong to multiple other models usi
202
202
 
203
203
  A `Comment` can belong to either a `Post` or a `Video`:
204
204
 
205
- ```typescript
205
+ ```typescript fragment
206
206
  // app/models/Post.ts
207
207
  import {
208
208
  morphMany,
@@ -243,7 +243,7 @@ The `morphName` (`"commentable"`) determines the `commentable_type` and `comment
243
243
 
244
244
  > **Warning** — Renaming a `morphMap` key without migrating existing `*_type` rows will break every stored polymorphic association. Keep the keys stable — changing one requires a data migration.
245
245
 
246
- ```typescript
246
+ ```typescript fragment
247
247
  // in a controller
248
248
  // Eager load polymorphic relations
249
249
  const posts = await Post.query().with("comments").get();
@@ -257,7 +257,7 @@ comment.commentable; // Post | Video
257
257
 
258
258
  Share a tagging system across multiple model types through a single `taggables` pivot:
259
259
 
260
- ```typescript
260
+ ```typescript fragment
261
261
  // app/models/Post.ts
262
262
  import { morphToMany, morphedByMany, type ManyToMany } from "@zerotal/orm";
263
263
 
@@ -300,7 +300,7 @@ Always prefer eager loading over lazy loading in loops — it prevents N+1 queri
300
300
 
301
301
  > **Tip** — Reach for `.with()` whenever you access a relation across a collection; lazy-loading inside a loop fires one query per row.
302
302
 
303
- ```typescript
303
+ ```typescript fragment
304
304
  // in a controller
305
305
  // Single relation
306
306
  const posts = await Post.query().with("author").get();
@@ -330,7 +330,7 @@ counted[0].commentsCount; // number
330
330
 
331
331
  When you already have a model instance and realise you need a relation:
332
332
 
333
- ```typescript
333
+ ```typescript fragment
334
334
  // in a controller
335
335
  const post = await Post.findOrFail(1);
336
336
 
@@ -345,7 +345,7 @@ await post.loadMissing(["author"]);
345
345
 
346
346
  Filter a parent model based on whether its relation exists, without loading the related rows:
347
347
 
348
- ```typescript
348
+ ```typescript fragment
349
349
  // in a controller
350
350
  // Posts that have at least one comment
351
351
  Post.query().has("comments").get();
@@ -11,7 +11,7 @@ Models serialize to plain JavaScript objects for API responses and `JSON.stringi
11
11
 
12
12
  Every `Model` has a `toJSON()` method. It returns a plain object you can pass straight to the `json()` response helper — and `JSON.stringify()` calls it for you automatically:
13
13
 
14
- ```typescript
14
+ ```typescript fragment
15
15
  // in a controller
16
16
  import { json } from "zerotal";
17
17
  import { User } from "../models/User.ts";
@@ -56,7 +56,7 @@ user.toJSON();
56
56
 
57
57
  `visible` takes precedence over `hidden`. When set (non-empty), `toJSON()` includes **only** those keys:
58
58
 
59
- ```typescript
59
+ ```typescript fragment
60
60
  // app/models/User.ts
61
61
  @table("users")
62
62
  export class User extends Model {
@@ -71,7 +71,7 @@ Use `visible` for models that have many internal columns and you want to be expl
71
71
 
72
72
  Include the result of a getter method in `toJSON()`. The getter runs at serialization time:
73
73
 
74
- ```typescript
74
+ ```typescript fragment
75
75
  // app/models/User.ts
76
76
  @table("users")
77
77
  export class User extends Model {
@@ -97,7 +97,7 @@ user.toJSON();
97
97
 
98
98
  Adjust what a specific instance exposes without modifying the class definition. Each method mutates the instance and returns `this`, so they chain and can be passed straight to `json()`:
99
99
 
100
- ```typescript
100
+ ```typescript fragment
101
101
  // in a controller
102
102
  // Temporarily hide additional fields for this response:
103
103
  json(user.makeHidden("email", "phone").toJSON());
@@ -124,7 +124,7 @@ json(user.makeHidden("password").makeVisible("phoneVerifiedAt").append("isVerifi
124
124
  - **Appended accessors included** — per `static appends` and any `append()` overrides.
125
125
  - **Loaded relations included** — nested models serialize via their own `toJSON()`.
126
126
 
127
- ```typescript
127
+ ```typescript fragment
128
128
  // in a controller
129
129
  const post = await Post.query().with("author").findOrFail(1);
130
130
 
@@ -145,7 +145,7 @@ post.toJSON();
145
145
 
146
146
  When you have an array of models, call `toJSON()` on each item or rely on `JSON.stringify()`:
147
147
 
148
- ```typescript
148
+ ```typescript fragment
149
149
  // in a controller
150
150
  const posts = await Post.query().where("status", "published").get();
151
151
 
@@ -160,7 +160,7 @@ json(posts);
160
160
 
161
161
  Hidden/visible lists apply per-model and do **not** propagate to nested relations. Each nested model serializes via its own class configuration:
162
162
 
163
- ```typescript
163
+ ```typescript fragment
164
164
  // app/models/User.ts and app/models/Post.ts
165
165
  @table("users")
166
166
  export class User extends Model {
@@ -182,7 +182,7 @@ post.toJSON();
182
182
 
183
183
  For fine-grained, per-endpoint serialization, use API Resources instead of class-level `hidden`/`visible`. A `Resource` wraps a model in `this.resource` and lets you shape the output per route without touching the model class. By default the output is wrapped in a `{ data: ... }` envelope:
184
184
 
185
- ```typescript
185
+ ```typescript fragment
186
186
  // app/resources/PostResource.ts
187
187
  import { Resource } from "zerotal/http";
188
188
  import type { Post } from "../models/Post.ts";
@@ -201,7 +201,7 @@ export class PostResource extends Resource<Post> {
201
201
 
202
202
  Build a single resource with `new PostResource(post)`, then `toJson()` for a plain object or `toResponse()` for a `Response`. Serialize a list with the static `collection()` helper, which takes the resource class first:
203
203
 
204
- ```typescript
204
+ ```typescript fragment
205
205
  // in a controller
206
206
  import { json } from "zerotal";
207
207
  import { PostResource } from "../resources/PostResource.ts";
@@ -228,7 +228,7 @@ Use model-level `hidden`/`visible` for globally sensitive fields. Use Resources
228
228
 
229
229
  ## References
230
230
 
231
- `toJSON()` and the override methods live on every [`Model`](/docs/orm/index); the `Resource` helpers are exported from `zerotal`.
231
+ `toJSON()` and the override methods live on every [`Model`](/docs/orm); the `Resource` helpers are exported from `zerotal`.
232
232
 
233
233
  | Member | Signature | Description |
234
234
  | ----------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------- |
@@ -245,7 +245,7 @@ Use model-level `hidden`/`visible` for globally sensitive fields. Use Resources
245
245
 
246
246
  ## Next steps
247
247
 
248
- - [ORM](/docs/orm/index) — model definition, casts, and configuration.
248
+ - [ORM](/docs/orm) — model definition, casts, and configuration.
249
249
  - [ORM relationships](/docs/orm/relationships) — load the related models you serialize here.
250
250
  - [ORM casts](/docs/orm/casts) — control how column values appear in serialized output.
251
251
  - [Responses](/docs/responses) — the `json()` helper that calls `toJSON()`.
@@ -86,7 +86,7 @@ Everything a consumer should import is re-exported from `index.ts`. Keep
86
86
  implementation files internal; export the manager, the provider, the config
87
87
  factory and its shape type, any facade, and the typed error vocabulary.
88
88
 
89
- ```typescript
89
+ ```typescript fragment
90
90
  // packages/cache/src/index.ts
91
91
  export { CacheManager } from "./CacheManager.ts";
92
92
  export { CacheProvider } from "./provider/CacheProvider.ts";
@@ -106,7 +106,7 @@ The provider is the only thing the application boots. It binds your services int
106
106
  the container and registers any commands. It must live at `src/provider/` and
107
107
  declare both `static provides` and `static environments`.
108
108
 
109
- ```typescript
109
+ ```typescript fragment
110
110
  // packages/cache/src/provider/CacheProvider.ts
111
111
  import { ServiceProvider } from "@zerotal/core";
112
112
  import type { AppEnvironment } from "@zerotal/core";
@@ -223,7 +223,7 @@ export function CacheConfig(options: Partial<CacheConfigShape> = {}): CacheConfi
223
223
  Register the namespace for **typed config dot-paths** by augmenting `ConfigRegistry`
224
224
  (the config analogue of `ContainerBindings`) at the bottom of `config.ts`:
225
225
 
226
- ```typescript
226
+ ```typescript fragment
227
227
  // packages/cache/src/config.ts
228
228
  declare module "@zerotal/core" {
229
229
  interface ConfigRegistry {
@@ -291,7 +291,7 @@ import { createFacade } from "@zerotal/core";
291
291
  export const Cache = createFacade("cache");
292
292
  ```
293
293
 
294
- ```typescript
294
+ ```typescript fragment
295
295
  function createFacade<K extends keyof ContainerBindings>(key: K): ContainerBindings[K];
296
296
  ```
297
297
 
@@ -309,7 +309,7 @@ consumers call `Router.flow(...)` as though it shipped with the router. Register
309
309
  it in `onRegister()` — that runs before route files load, so the method exists by
310
310
  the time an app calls it:
311
311
 
312
- ```typescript
312
+ ```typescript fragment
313
313
  // packages/flow/src/FlowProvider.ts — inside onRegister():
314
314
  Router.macro("flow", flowRoute);
315
315
  ```
@@ -317,7 +317,7 @@ Router.macro("flow", flowRoute);
317
317
  The call is untyped on its own; augment the matching interface so consumers get
318
318
  completion and type-checking:
319
319
 
320
- ```typescript
320
+ ```typescript fragment
321
321
  // packages/flow/src/types.ts
322
322
  declare module "@zerotal/core" {
323
323
  interface RouterMacros {
@@ -337,7 +337,7 @@ boot (the way `app/models` and `app/policies` do), contribute a **concern
337
337
  descriptor** from your provider's `onRegister()`. Core stays unaware of your
338
338
  package — discovery is push-based.
339
339
 
340
- ```typescript
340
+ ```typescript fragment
341
341
  // packages/webhooks/src/provider/WebhooksProvider.ts
342
342
  import type { ConcernDescriptor } from "@zerotal/core";
343
343
 
@@ -375,7 +375,7 @@ declare it and `bun zt dev` runs it beside the server in its own tab. Otherwise
375
375
  every user of your package has to remember a second terminal, and there is no way
376
376
  for you to help them.
377
377
 
378
- ```typescript
378
+ ```typescript fragment
379
379
  // packages/webhooks/src/provider/WebhooksProvider.ts
380
380
  import type { DevProcessDefinition } from "@zerotal/core";
381
381
 
@@ -427,7 +427,7 @@ user sees.
427
427
  `bun zt doctor` is what a developer (or an agent) runs to find out whether an app
428
428
  is wired correctly. Contribute the checks only your package can make:
429
429
 
430
- ```typescript
430
+ ```typescript fragment
431
431
  // packages/webhooks/src/provider/WebhooksProvider.ts
432
432
  import type { DoctorCheck } from "@zerotal/core";
433
433
 
@@ -464,7 +464,7 @@ Every package must ship at least one `*.test.ts` file — the linter treats thei
464
464
  absence as a high-severity violation. Co-locate tests next to the code they cover
465
465
  (`CacheManager.test.ts` beside `CacheManager.ts`) and run them with `bun test`.
466
466
 
467
- ```typescript
467
+ ```typescript fragment
468
468
  // packages/cache/src/config.test.ts
469
469
  import { test, expect } from "bun:test";
470
470
  import { CacheConfig } from "./config.ts";