@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/api-surface.md CHANGED
@@ -119,7 +119,7 @@ interface DoctorReport = {
119
119
  interface GuidelineOptions = {
120
120
  packages: string[]
121
121
  serverName: string
122
- shape?: ProjectShape
122
+ shape?: ProjectShape | undefined
123
123
  }
124
124
 
125
125
  interface InstalledPackage = {
@@ -177,8 +177,8 @@ interface SchemaReport = {
177
177
  }
178
178
 
179
179
  interface SpawnProbeOptions = {
180
- cwd?: string
181
- timeoutMs?: number
180
+ cwd?: string | undefined
181
+ timeoutMs?: number | undefined
182
182
  }
183
183
 
184
184
  interface ToolContext = {
@@ -262,10 +262,10 @@ interface ServerIdentity = {
262
262
  }
263
263
 
264
264
  interface StdioOptions = {
265
- input?: ReadableStream<Uint8Array<ArrayBufferLike>>
266
- log?: (message: string) => void
265
+ input?: ReadableStream<Uint8Array<ArrayBufferLike>> | undefined
266
+ log?: ((message: string) => void) | undefined
267
267
  server: McpServer
268
- write?: (frame: string) => void
268
+ write?: ((frame: string) => void) | undefined
269
269
  }
270
270
 
271
271
  interface ToolOutcome = {
@@ -177,6 +177,21 @@ When the model uses the ORM `SoftDeletes` mixin, the List page gains an
177
177
  **Active / All / Trashed** switch and the row + bulk actions gain Restore and
178
178
  Force-delete automatically — no extra configuration.
179
179
 
180
+ ## Types
181
+
182
+ | Type | What it is |
183
+ | -------------------------------------- | ------------------------------------------------------------------------ |
184
+ | `ActionItem` | One action as the panel holds it, after `action()` has built it. |
185
+ | `ActionHandler` | The callback an action runs, given its `ActionContext`. |
186
+ | `ActionContext` | What that callback receives — the record or selection, and the page. |
187
+ | `ActionVisible` | The predicate deciding whether an action appears for this record at all. |
188
+ | `ActionColor` | The emphasis it is drawn with. |
189
+ | `ConsoleAction`, `ConsoleHeaderAction` | The console's own row and header actions. |
190
+
191
+ **`ActionVisible` hides, it does not authorize.** An action absent from the UI is still
192
+ reachable by anyone who can form the request, so the authorization check belongs in the handler
193
+ as well — see [Authorization](/docs/authorization).
194
+
180
195
  ## Next steps
181
196
 
182
197
  - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
@@ -83,6 +83,16 @@ Panel.configure({
83
83
  `adminTokensCss()` the `:root`/`.dark` custom properties — feed both into your own
84
84
  `tailwind.config` so a compiled stylesheet renders identically.
85
85
 
86
+ ## Types
87
+
88
+ | Type | What it is |
89
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
90
+ | `AdminAuthConfig` | Who may reach the panel and how they sign in. |
91
+ | `AdminAuthorizer` | The check run before a page renders. |
92
+ | `AuthRolesOptions` | How panel access maps onto the app's roles. |
93
+ | `AdminThemeConfig` | Panel theming. |
94
+ | `EnvironmentIndicatorOptions` | The banner naming which deployment you are looking at — worth setting, because the panel looks identical in staging and production and that is exactly when it matters. |
95
+
86
96
  ## Next steps
87
97
 
88
98
  - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
@@ -149,6 +149,18 @@ Custom pages and anything contributed by a package sit in the same sidebar, sort
149
149
  and grouped by the same rules — a page's group heading is just a string, so a page
150
150
  and a resource that name the same group land together.
151
151
 
152
+ ## Types
153
+
154
+ | Type | What it is |
155
+ | ----------------------------------------- | --------------------------------------------------------------------------- |
156
+ | `DashboardWidget` | One widget as declared. |
157
+ | `WidgetContribution` | A widget pushed in by another package. |
158
+ | `WidgetTone` | Its good/warn/bad colouring. |
159
+ | `StatsResolver` | Supplies a stat widget's numbers for the selected range. |
160
+ | `ChartResolver`, `ChartData` | The same for a chart, and the shape it returns. |
161
+ | `TableWidgetColumn` | A column in a table widget — narrower than a resource table's. |
162
+ | `DashboardLayout`, `DashboardLayoutStore` | An arrangement of widgets, and where a user's own arrangement is persisted. |
163
+
152
164
  ## Next steps
153
165
 
154
166
  - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
@@ -221,6 +221,20 @@ the table — the search term, each filter, the trashed scope — and each chip
221
221
  its own undo. A table showing four of two hundred rows for no visible reason is
222
222
  the most common way a panel misleads someone; the chips are the fix.
223
223
 
224
+ ## Types
225
+
226
+ The contribution shapes another package pushes into the panel:
227
+
228
+ | Type | What it is |
229
+ | ------------------------------------------------------------------ | ------------------------------------------------------------------------ |
230
+ | `NavItem`, `NavGroup`, `NavContribution` | A sidebar entry, a heading it sits under, and a contributed one. |
231
+ | `PageContribution` | A whole page added to the panel. |
232
+ | `UserMenu`, `UserMenuItem`, `UserMenuContribution` | The account menu and its entries. |
233
+ | `TopbarSlot` | Where something may be placed in the top bar. |
234
+ | `RenderHookName` | Every point the panel can be extended at — the enumeration of the slots. |
235
+ | `RenderHookContext` | What a hook receives when it runs. |
236
+ | `ConsoleContribution`, `ConsoleColumn`, `ConsoleRow`, `ConsoleTab` | Contributions to the console page. |
237
+
224
238
  ## Next steps
225
239
 
226
240
  - [Tables](/docs/admin/tables) — columns, filters and the query builder.
@@ -149,6 +149,21 @@ section("Line items").schema([
149
149
  ]),
150
150
  ```
151
151
 
152
+ ## Types
153
+
154
+ | Type | What it is |
155
+ | ------------------ | --------------------------------------------------------------------------------- |
156
+ | `FormComponent` | Anything that can appear in a form — a field, a layout block, a custom component. |
157
+ | `FormBlock` | A grouping: a section, a tab, a fieldset. |
158
+ | `FieldMode` | Whether a field is editable, read-only or hidden in this context. |
159
+ | `FieldPredicate` | The condition behind a conditional field. |
160
+ | `FormModeConfig` | How the form differs between create and edit. |
161
+ | `UploadedFileLike` | What a file field hands your handler. |
162
+
163
+ Infolists — the read-only counterpart — use `InfolistComponent`, with `EntryKind`,
164
+ `EntryDisplay`, `EntrySize` and `EntryWeight` describing one entry's type and presentation.
165
+ `PrimeKind` is the leading entry a record view opens with.
166
+
152
167
  ## Next steps
153
168
 
154
169
  - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
@@ -217,6 +217,18 @@ A coloured strip across the top names the environment — red for production, am
217
217
  staging. It returns nothing in local development, so registering it unconditionally is
218
218
  the intended usage; there is nothing to switch off per environment.
219
219
 
220
+ ## Types
221
+
222
+ | Type | What it is |
223
+ | ------------------------------------------- | ------------------------------------------------------ |
224
+ | `HistoryOptions`, `HistoryChange` | Record history: what is tracked, and one entry of it. |
225
+ | `ImportRecordsPayload` | What the import action hands your handler. |
226
+ | `AdminNotification`, `StoredNotification` | A panel notification, and the persisted form. |
227
+ | `DatabaseNotificationOptions` | Where those are stored. |
228
+ | `StoreMediaOptions`, `DatabaseMediaOptions` | Media handling and its database backing. |
229
+ | `SearchHit`, `PanelSearchProvider` | One global-search result, and where results come from. |
230
+ | `CalloutTone` | The emphasis a callout is drawn with. |
231
+
220
232
  ## Next steps
221
233
 
222
234
  - [Panel Structure](/docs/admin/structure) — clusters, nested resources, multiple panels.
@@ -125,6 +125,12 @@ including on create.
125
125
  Both save hooks receive the `mode`, so one implementation can branch on `"create"`
126
126
  versus `"edit"` instead of duplicating the resource.
127
127
 
128
+ ## Types
129
+
130
+ `AdminModel` is what a resource's `model` accepts, and `AdminQuery` the query it builds — the
131
+ seam where the panel hands off to the ORM. `ListOptions` configures the list page,
132
+ and `RelationKind` names the relation types a relation manager understands.
133
+
128
134
  ## Next steps
129
135
 
130
136
  - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
@@ -179,6 +179,27 @@ How the table renders — grid instead of rows, striping, sticky headers, densit
179
179
  and where the filters sit are covered in
180
180
  [Extending the UI](/docs/admin/extending-ui#table-presentation).
181
181
 
182
+ ## Types
183
+
184
+ What a column, filter and query rule are made of. All exported, so a helper that builds a
185
+ column set for several resources can be typed rather than repeated.
186
+
187
+ | Type | What it is |
188
+ | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
189
+ | `ColumnKind` | Which column type this is — text, badge, boolean, date, and the rest. |
190
+ | `ColumnOption` | One column's settings. |
191
+ | `CellAlign` | `start` / `center` / `end`. |
192
+ | `ColumnSummary`, `SummaryKind`, `SummaryResult` | A footer aggregate: what to compute, and what comes back. |
193
+ | `BadgeTone` | The colouring a badge column maps a value to. |
194
+ | `EmptyState` | What the table shows instead of rows when there are none. |
195
+ | `PivotColumn` | A column reading a pivot table's own attributes on a many-to-many. |
196
+ | `FilterOption`, `FilterType`, `FilterApply` | A filter's declaration, its kind, and how it modifies the query. |
197
+ | `ConstraintKind`, `ConstraintOperator`, `ConstraintOption` | The query-builder constraints behind advanced filtering. |
198
+ | `QueryRule`, `Conjunction`, `QueryModifier` | One rule, how rules combine (`and` / `or`), and a raw modifier for what the builder cannot express. |
199
+ | `SavedView`, `SavedViewProvider` | A stored filter set, and where those are kept. |
200
+ | `TableRowsResolver` | Supplying rows yourself instead of letting the resource query. |
201
+ | `ExportFormat` | What the export action writes. |
202
+
182
203
  ## Next steps
183
204
 
184
205
  - [Admin overview](/docs/admin) — the guide's front page and the rest of the sections.
package/docs/audit.md CHANGED
@@ -359,6 +359,11 @@ app.container.singleton(
359
359
  | `auditLog` | `(event: AuditEvent, payload?: InstanceAuditPayload) => Promise<void>` | Log a custom event against this instance. |
360
360
  | `auditLogs` | `() => ModelQueryBuilder<AuditLog>` | Chainable query of this instance's history. |
361
361
 
362
+ ## Types
363
+
364
+ `AuditableOptions` is what `Model.using(Auditable)` accepts — which columns are tracked and
365
+ which are ignored. `AuditConfigShape` is the `audit` config namespace.
366
+
362
367
  ## Next steps
363
368
 
364
369
  - [ORM Lifecycle](/docs/orm/lifecycle) — the model hooks the audit system listens to.
@@ -94,7 +94,12 @@ export class User extends AuthUser {
94
94
  }
95
95
  ```
96
96
 
97
- `AuthUser` provides `getAuthId()` (returns `this.id`) and `getAuthPassword()` (returns `this.password`). Make `Auth.user()` return your concrete type by augmenting `UserModel` once:
97
+ `AuthUser` provides `getAuthId()` (returns `this.id`) and `getAuthPassword()` (returns `this.password`).
98
+
99
+ ### Bind the type — a required step
100
+
101
+ Do this once, or every `Auth.user()` in the app returns the framework's minimal `UserModel`
102
+ and reading your own columns off it is a type error:
98
103
 
99
104
  ```typescript fragment
100
105
  // bootstrap/app.ts (or any file imported at boot)
@@ -105,6 +110,17 @@ declare module "@zerotal/auth" {
105
110
  }
106
111
  ```
107
112
 
113
+ `AuthenticatableUser` in `@zerotal/core/contracts` is the same seam one layer down: `@zerotal/core`
114
+ has to talk about "the user" without depending on `@zerotal/auth`, so it declares an empty
115
+ interface for apps to augment, and `UserModel` extends it. Augmenting either resolves to the same
116
+ shape — augment `UserModel`, which is the one the auth docs and examples use.
117
+
118
+ **The empty body is the point.** The interface is not being given members — it is being
119
+ pointed at your class, so that everywhere the framework says `UserModel` it means `User`.
120
+ That is not a shape anyone guesses, which is why it is here in setup rather than filed
121
+ under advanced usage. Put it somewhere that is imported at boot and leave a comment saying
122
+ what it is for; it looks like dead code otherwise.
123
+
108
124
  ## How it works
109
125
 
110
126
  On every request `PersistUserMiddleware` reads `user_id` from the session and populates `ctx.user`. If `user_id` is present but the user no longer exists, the stale key is cleared.
@@ -133,6 +149,20 @@ await Auth.login(user); // write user_id to session, set ctx.user
133
149
  await Auth.logout(); // clear user_id from session, unset ctx.user
134
150
  ```
135
151
 
152
+ > **`Auth.user()` throws for a guest, and the name does not telegraph that.** It is the right
153
+ > call behind `AuthMiddleware`, where a user is guaranteed — that guarantee is what makes its
154
+ > non-optional return type honest. It is the wrong call on any route a signed-out person can
155
+ > reach. Plenty of audited things happen with nobody signed in: a storefront checkout, a
156
+ > customer approving a change from a tokenised link, an incoming webhook. On those routes an
157
+ > audit line that reaches for `Auth.user()` turns into a 401 on a public page, and the 401 is
158
+ > about the audit line rather than about the request.
159
+ >
160
+ > ```typescript fragment
161
+ > // A guest is a normal outcome here, not a failure.
162
+ > const actor = Auth.userOrNull();
163
+ > await Audit.record("checkout.completed", { actorId: actor?.getAuthId() ?? null });
164
+ > ```
165
+
136
166
  `Auth.attempt()` rolls credential lookup, password verification, and login into one call:
137
167
 
138
168
  ```typescript fragment
@@ -939,8 +969,87 @@ res.assertOk();
939
969
  res.assertSee("Welcome back");
940
970
  ```
941
971
 
972
+ ## Passkeys
973
+
974
+ A passkey replaces the password with a key pair held by the device: the private half never
975
+ leaves the authenticator, so there is nothing on your server worth stealing and nothing for a
976
+ user to reuse across sites. `PasskeyService` implements both WebAuthn ceremonies.
977
+
978
+ You supply where credentials are stored — the service does not assume a schema:
979
+
980
+ ```typescript fragment
981
+ import { PasskeyService } from "@zerotal/auth";
982
+
983
+ const passkeys = new PasskeyService({
984
+ rpName: "Acme",
985
+ rpId: "acme.test", // the domain, no scheme and no port
986
+ origin: "https://acme.test", // or an array, for staging alongside production
987
+ store: {
988
+ findUserCredentials: (userId) => Credential.where("user_id", userId).all(),
989
+ findCredential: (credentialId) => Credential.where("credential_id", credentialId).first(),
990
+ saveCredential: (credential) => Credential.create(credential),
991
+ updateCounter: (id, counter) => Credential.where("id", id).update({ counter }),
992
+ },
993
+ });
994
+ ```
995
+
996
+ Each ceremony is two calls — options out, response verified in. The challenge you hand back to
997
+ `verify…` is the one you issued, which is what stops a replay:
998
+
999
+ | Call | What it does |
1000
+ | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
1001
+ | `registrationOptions(user, existing?)` | Options for enrolment. Pass the user's existing credentials so the browser refuses to register the same authenticator twice. |
1002
+ | `verifyRegistration(user, response, challenge, ctx)` | Verifies enrolment and stores the credential. |
1003
+ | `authenticationOptions(userId?)` | Options for sign-in. Omit `userId` for a usernameless flow, where the authenticator offers the account. |
1004
+ | `verifyAuthentication(assertion, challenge, ctx)` | Returns `{ credential, userId }`, or the string `"passkey.invalid"` — a value rather than a throw, because a failed assertion is an ordinary outcome. |
1005
+
1006
+ `PasskeyUser` is the minimum the ceremonies need (`id`, `name`, `email`); `PasskeyCredential` is
1007
+ what your store holds.
1008
+
1009
+ > **`requireUserVerification` defaults to `true`, and that is what makes a passkey a second
1010
+ > factor.** With it off, an assertion succeeds on possession of an unlocked authenticator alone
1011
+ > — whoever picks up the phone is the user. Turn it off only for a deliberate "the device is the
1012
+ > credential" flow, and then do not count the passkey as a second factor.
1013
+
1014
+ `rpId` is the domain alone — no scheme, no port — and it must match the site the browser is on
1015
+ or the ceremony fails with an error that does not say why. `origin` takes an array when the same
1016
+ deployment answers on more than one.
1017
+
942
1018
  ## References
943
1019
 
1020
+ ### Types
1021
+
1022
+ Every middleware and broker takes an options shape, exported so a helper that builds one can be
1023
+ typed:
1024
+
1025
+ | Type | Configures |
1026
+ | ----------------------------------- | ----------------------------------------------------------------------- |
1027
+ | `AuthOptions` | `AuthMiddleware` — `redirectTo`, `mustVerifyEmail`, `verifyRedirectTo`. |
1028
+ | `GuestOptions` | `GuestMiddleware` — where a signed-in visitor is sent instead. |
1029
+ | `BasicAuthOptions` | HTTP Basic auth. |
1030
+ | `ConfirmPasswordOptions` | `ConfirmPasswordMiddleware`, including the confirmation window. |
1031
+ | `AuthenticateSessionOptions` | Session-guard behaviour on a password change elsewhere. |
1032
+ | `RememberMeOptions` | The persistent-login cookie. |
1033
+ | `TwoFactorOptions` | The second-factor gate. |
1034
+ | `LoginThrottleOptions` | Sign-in rate limiting. |
1035
+ | `EmailOtpOptions` | Passwordless codes by email. |
1036
+ | `MagicLinkBrokerOptions` | Magic-link sign-in. |
1037
+ | `PasskeysOptions` | `PasskeyService`, above. |
1038
+ | `CompromisedCheckOptions` | The have-i-been-pwned style check on a chosen password. |
1039
+ | `JwtGuardOptions`, `JwtSignOptions` | The JWT guard and how tokens are minted. |
1040
+ | `QrSvgOptions` | The 2FA enrolment QR code. |
1041
+
1042
+ What the brokers and guards hand back: `PasswordBrokerResult`, `MagicLinkBrokerResult` and
1043
+ `MagicLinkUser` for the link flows, `JwtPayload` and `TokenBundle` for JWT, and
1044
+ `PasskeyCredential` / `PasskeyUser` for passkeys.
1045
+
1046
+ ### Events
1047
+
1048
+ Emitted on `FrameworkEvents`, so an app can audit or react without wrapping the calls that
1049
+ raise them: `EmailVerified`, `PasswordConfirmed`, `PasswordResetLinkSent`, `PasswordResetEvent`
1050
+ and `CurrentDeviceLogout`. `LoginSucceeded`, `LoggedOut` and `OtherDeviceLogout` are documented
1051
+ above with the calls that emit them.
1052
+
944
1053
  ### Commands
945
1054
 
946
1055
  `@zerotal/auth` ships two commands:
@@ -77,6 +77,23 @@ Notifications can be delivered over a broadcast channel in real time — add `'b
77
77
  notification's `channels()` and implement `toBroadcast()`. See
78
78
  [Notifications → Broadcasting](/docs/notifications#broadcast).
79
79
 
80
+ ## Types
81
+
82
+ Channel and event types, most of which are inferred from your channel declarations:
83
+
84
+ | Type | What it is |
85
+ | --------------------------------------------------------- | --------------------------------------------------------------------------------------- |
86
+ | `StaticChannels`, `ParameterizedChannels` | Channels with fixed names, and those taking params. |
87
+ | `ChannelParams`, `ChannelParamRecord` | What a parameterised channel captures. |
88
+ | `ChannelAuthFn` | The authorization callback for a private channel. |
89
+ | `PresenceAuthFn`, `PresenceMemberData` | The same for presence, plus what a member publishes to the others. |
90
+ | `AuthorizeResult` | What an auth callback may return — a refusal, or the member data. |
91
+ | `TypedBroadcastEvent`, `EventsOf`, `PayloadOf` | An event on a channel, the events a channel carries, and one event's payload. |
92
+ | `BroadcastsModelEventsOptions`, `ModelBroadcastEventName` | Broadcasting a model's own lifecycle, and the event names it produces. |
93
+ | `BroadcastRecord` | A queued broadcast as stored. |
94
+ | `WsConnectionData` | What the server holds per connection. |
95
+ | `PusherPresenceResolver` | Resolving presence members when running against Pusher rather than the built-in server. |
96
+
80
97
  ## Next steps
81
98
 
82
99
  - [Broadcasting overview](/docs/broadcasting) — the guide's front page and the rest of the sections.
package/docs/cache.md CHANGED
@@ -381,6 +381,11 @@ does.
381
381
  > `CacheSerializationError` at write time. It is a bug in the caller rather than
382
382
  > a cache fault, so let it surface in development instead of swallowing it.
383
383
 
384
+ ## Types
385
+
386
+ `IdempotencyOptions` configures `Cache.idempotent()` — the key, the window, and what happens to
387
+ a second identical request that arrives while the first is still running.
388
+
384
389
  ## Next steps
385
390
 
386
391
  - [Query Builder](/docs/query-builder) — the queries you'll most often wrap in `remember()`.
package/docs/carbon.md CHANGED
@@ -823,6 +823,11 @@ test("survives a DST spring-forward", () => {
823
823
  | `forHumans` | `forHumans(options?: { join?: string; short?: boolean }): string` | Human-readable description. |
824
824
  | `toISO` | `toISO(): string` | ISO 8601 duration string (`toJSON` alias). |
825
825
 
826
+ ## Types
827
+
828
+ `DurationLike` is what the arithmetic methods accept — `add`, `subtract` and their kin — so a
829
+ duration can be built once and passed around rather than spelled out at each call site.
830
+
826
831
  ## Next steps
827
832
 
828
833
  - [Casts & Mutators](/docs/orm/casts) — `datetime` columns hydrate to Carbon automatically.