@zerotal/arch 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/admin/actions.md +15 -0
- package/docs/admin/auth.md +10 -0
- package/docs/admin/dashboard.md +12 -0
- package/docs/admin/extending-ui.md +14 -0
- package/docs/admin/forms.md +15 -0
- package/docs/admin/operations.md +12 -0
- package/docs/admin/resources.md +6 -0
- package/docs/admin/tables.md +21 -0
- package/docs/audit.md +5 -0
- package/docs/authentication.md +110 -1
- package/docs/broadcasting/references.md +17 -0
- package/docs/cache.md +5 -0
- package/docs/carbon.md +5 -0
- package/docs/changelog.md +159 -1
- package/docs/client/index.md +17 -0
- package/docs/commands.md +6 -0
- package/docs/components.md +73 -0
- package/docs/config-system.md +54 -0
- package/docs/cookies.md +6 -0
- package/docs/deployment.md +103 -13
- package/docs/devtools.md +5 -0
- package/docs/email-verification.md +26 -1
- package/docs/encryption.md +21 -0
- package/docs/errors.md +2 -0
- package/docs/flow/components.md +54 -0
- package/docs/flow/forms.md +57 -0
- package/docs/flow/references.md +14 -0
- package/docs/getting-started.md +38 -0
- package/docs/health.md +19 -0
- package/docs/helpers.md +99 -0
- package/docs/i18n.md +5 -0
- package/docs/inertia/props.md +70 -0
- package/docs/lock.md +15 -0
- package/docs/logger.md +38 -0
- package/docs/migrations.md +47 -0
- package/docs/monitor.md +59 -0
- package/docs/notifications.md +11 -0
- package/docs/orm/casts.md +6 -0
- package/docs/orm/lifecycle.md +18 -0
- package/docs/orm/queries.md +10 -0
- package/docs/orm/relationships.md +30 -0
- package/docs/queue.md +10 -0
- package/docs/rate-limiting.md +39 -4
- package/docs/responses.md +23 -0
- package/docs/routing.md +16 -0
- package/docs/scheduler.md +11 -0
- package/docs/session.md +6 -0
- package/docs/social.md +10 -0
- package/docs/storage.md +21 -0
- package/docs/support-policy.md +13 -1
- package/docs/telemetry.md +8 -0
- package/docs/tenancy.md +6 -0
- package/docs/testing/index.md +6 -0
- package/docs/validator.md +9 -0
- package/docs/view.md +6 -0
- package/package.json +3 -3
- package/src/install/ArchInstallCommand.ts +4 -2
- package/src/probe/topics.ts +40 -0
- package/src/provider/ArchProvider.ts +2 -2
package/docs/admin/actions.md
CHANGED
|
@@ -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.
|
package/docs/admin/auth.md
CHANGED
|
@@ -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.
|
package/docs/admin/dashboard.md
CHANGED
|
@@ -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.
|
package/docs/admin/forms.md
CHANGED
|
@@ -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.
|
package/docs/admin/operations.md
CHANGED
|
@@ -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.
|
package/docs/admin/resources.md
CHANGED
|
@@ -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.
|
package/docs/admin/tables.md
CHANGED
|
@@ -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.
|
package/docs/authentication.md
CHANGED
|
@@ -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`).
|
|
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.
|
package/docs/changelog.md
CHANGED
|
@@ -27,9 +27,167 @@ the section for every version you cross and apply its migration notes, not only
|
|
|
27
27
|
majors. [Releases and versioning](/docs/support-policy#releases-and-versioning) explains
|
|
28
28
|
when that carve-out ends.
|
|
29
29
|
|
|
30
|
+
## 1.9.0 — 2026-08-29
|
|
31
|
+
|
|
32
|
+
The gaps an app was filling in for itself: one Bun per project, a database backup that is not
|
|
33
|
+
`cp`, a release gate the pipeline will actually call, a boundary between a model and a page
|
|
34
|
+
prop, and helpers that work on both sides of the wire.
|
|
35
|
+
|
|
36
|
+
**Two things to know before upgrading.** Both are new refusals or new noise, and both are quiet
|
|
37
|
+
if they do not apply to you.
|
|
38
|
+
|
|
39
|
+
- **`zt` now refuses to run when a project has two Bun runtimes in it** — the shell's `bun` and
|
|
40
|
+
a different one in `node_modules`. If it fires, pick one: `bun update bun` moves the installed
|
|
41
|
+
copy to match your shell, or run everything through `node_modules/.bin/bun`. To boot anyway
|
|
42
|
+
while you sort it out, set `ZT_ALLOW_RUNTIME_MISMATCH=1`. Most projects never see this,
|
|
43
|
+
because most have no `bun` in `node_modules` to disagree with.
|
|
44
|
+
- **Passing an ORM model straight into an Inertia page prop now warns in development**, once per
|
|
45
|
+
model class, if that model declares neither `hidden` nor `visible`. Declaring either silences
|
|
46
|
+
it — and is the fix, not the silencer. Production is unaffected.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **One project, one Bun.** `engines.bun` is a floor and nothing enforced it, so an app could be
|
|
51
|
+
served by one runtime and tested on another — the shell's `bun` and a `node_modules/bun` put
|
|
52
|
+
there by a transitive peer dependency nobody declared. A green suite is then evidence about a
|
|
53
|
+
binary the app is not served by. `startZerotal()` refuses on a mismatch, and `zt test` spawns
|
|
54
|
+
the binary running it rather than a name `PATH` resolves.
|
|
55
|
+
|
|
56
|
+
- **`zt db:backup`** — a verified snapshot of the SQLite database, using `VACUUM INTO` rather
|
|
57
|
+
than `cp`. Copying a live SQLite file can capture a half-written page and produce a backup
|
|
58
|
+
that restores as a corrupt database, months later, from the one file you were relying on.
|
|
59
|
+
Every snapshot is opened and integrity-checked as it is written, `--require-rows` fails a
|
|
60
|
+
backup whose business tables are empty, `--rehearse` performs the actual restore, and every
|
|
61
|
+
failure path exits non-zero — a backup timer that reports success while writing nothing is
|
|
62
|
+
worse than no timer at all. See [Deployment](/docs/deployment#back-up-the-database).
|
|
63
|
+
|
|
64
|
+
- **`DeployTarget.preflight`** — a slot for the app's own release gate, run after the config
|
|
65
|
+
validators and `doctor` and before anything is built or migrated. A command named
|
|
66
|
+
`release:check` is found by convention, with nothing to wire up. A declared name that is not
|
|
67
|
+
registered **fails** the deploy rather than being skipped: a gate nothing calls is a comment.
|
|
68
|
+
|
|
69
|
+
- **`zerotal/shared`** — the helpers with no server in them, importable from a browser bundle:
|
|
70
|
+
`pluralize`, `Str`, and new `formatMoney` / `formatNumber` / `formatDate`. A total that reads
|
|
71
|
+
`R 39 147` on screen and `R39,147.00` on the invoice looks like two different numbers to the
|
|
72
|
+
person paying it, and maintaining that in two files is how it happens. See
|
|
73
|
+
[Helpers](/docs/helpers#sharing-helpers-with-the-browser--zerotalshared).
|
|
74
|
+
|
|
75
|
+
- **`<form data-enhance>`** — a plain server-rendered page, with no Flow component on it, can
|
|
76
|
+
submit without the page flashing. It posts through `fetch` and the matching form in the
|
|
77
|
+
response replaces it in place, so a validation error lands where the person is looking. Its
|
|
78
|
+
own dependency-free bundle at `/__flow/enhance.js`, added with `flowEnhanceTag()` in the
|
|
79
|
+
layout. Every path degrades: a network failure re-submits natively, a redirect is followed and
|
|
80
|
+
`pushState`d, and no JavaScript at all is an ordinary form post.
|
|
81
|
+
|
|
82
|
+
- **Three new `doctor` checks.** A rate limiter that cannot tell two people apart behind a proxy
|
|
83
|
+
— where the socket address is the proxy's for every request, so one attacker can lock out
|
|
84
|
+
everybody. Auth columns missing from a table a migration built without them, which otherwise
|
|
85
|
+
surfaces as `no such column` in tests that have nothing to do with email. And migrations that
|
|
86
|
+
have not run, named, before a request finds out.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- **A model reaching Inertia page props says what it is safe to publish.** Page props are page
|
|
91
|
+
source, and `return inertia("Trips/Show", { trip })` ships every column of the row — the
|
|
92
|
+
internal cost, the margin, the note about the customer, on the customer's own screen. The
|
|
93
|
+
ORM's `hidden` / `visible` lists were already honoured and nothing said so. See the upgrade
|
|
94
|
+
note above and [Inertia props](/docs/inertia/props#page-props-are-page-source).
|
|
95
|
+
|
|
96
|
+
- **A bound field the model will not accept says so.** `flow:model` on a column missing from
|
|
97
|
+
`fillable` was dropped in silence: the form submitted, nothing was written, nothing failed.
|
|
98
|
+
The drop stays — the same path receives whatever a browser sends — but a developer's typo no
|
|
99
|
+
longer produces the same silence as a hostile payload. Development only, once per field.
|
|
100
|
+
|
|
101
|
+
- **INTERNAL: 116 exports leave the recorded API surface** across `core`, `orm`, `flow`,
|
|
102
|
+
`admin`, `flow-ui` and `monitor`. **Nothing is removed and nothing breaks** — they are still
|
|
103
|
+
exported and still work; what changes is the promise. The dev orchestrator, the ORM's
|
|
104
|
+
connection wiring and dialect layer, the admin panel's page machinery and Flow's
|
|
105
|
+
wire-protocol frame types are not things an app constructs, and naming them in a `stable`
|
|
106
|
+
surface implied a guarantee about a protocol that is free to change. Each package's own
|
|
107
|
+
changelog lists its share.
|
|
108
|
+
|
|
109
|
+
- **A minor breaks nothing that can wait.** The roadmap used to say a minor never breaks
|
|
110
|
+
anything, which was false when written — three breaks had already shipped in minors, each
|
|
111
|
+
deliberately, each with a note, exactly as the [support policy](/docs/support-policy) has
|
|
112
|
+
always described. An absolute rule the project knowingly broke is worse than an honest one.
|
|
113
|
+
|
|
114
|
+
### Documented
|
|
115
|
+
|
|
116
|
+
- **Every promised export is documented — 100%, up from 60%.** `maturity: stable` means an
|
|
117
|
+
export keeps its shape for the rest of the 1.x line, and the gate measuring how much of that
|
|
118
|
+
promise was written down stood at 798 gaps. It is zero.
|
|
119
|
+
|
|
120
|
+
Four features turned out to have shipped and been invisible. **Passkeys** — `PasskeyService`
|
|
121
|
+
has been here since 1.7.0 with no page at all, including that `requireUserVerification`
|
|
122
|
+
defaults to `true` because that is what makes a passkey a second factor rather than one.
|
|
123
|
+
**`@zerotal/core/env`**, a typed environment schema that reports every bad variable at once
|
|
124
|
+
rather than one per restart. **The outbound `Http` client**, which the testing guide had been
|
|
125
|
+
linking to a page that did not describe it. And **`@zerotal/monitor`'s Export JSON**, where the
|
|
126
|
+
button was documented and the forty-odd row types it hands you were not.
|
|
127
|
+
|
|
128
|
+
Also named for the first time: `@zerotal/flow-ui`'s sixty-one component prop types, which a
|
|
129
|
+
wrapper component cannot be written without.
|
|
130
|
+
|
|
131
|
+
The gate itself could not see `.tsx` files: with `jsx` unset, TypeScript declines to pull such
|
|
132
|
+
a module into the program rather than failing to parse it, so every symbol in one was
|
|
133
|
+
invisible. It had been inflating exactly the TSX-heavy packages.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- **A rebuilt Inertia bundle no longer 404s on a chunk the browser asks for.**
|
|
138
|
+
`resources/js/app.tsx` builds to `/assets/app.js` under that name every time, while
|
|
139
|
+
`splitting: true` names each chunk after its content. A rebuild therefore rewrites `app.js` to
|
|
140
|
+
import `chunk-NEW.js` and prunes `chunk-OLD.js` — and a browser holding a cached `app.js` asks
|
|
141
|
+
for the pruned one:
|
|
142
|
+
|
|
143
|
+
GET /assets/chunk-hrnspqda.js status=404
|
|
144
|
+
|
|
145
|
+
from a page that renders and a server that is healthy, with nothing in that line leading back
|
|
146
|
+
to the template.
|
|
147
|
+
|
|
148
|
+
The template hardcodes `/assets/app.js` rather than calling `asset()`, so the version token the
|
|
149
|
+
rest of the framework appends never reached it — and cache-busting had only ever been
|
|
150
|
+
implemented for `serve --dev`. It now applies in every environment: the file's mtime in dev,
|
|
151
|
+
where a rebuild happens without a restart, and the boot-derived asset version otherwise. An
|
|
152
|
+
unchanged asset keeps a stable URL and stays cached, which is why the token is derived rather
|
|
153
|
+
than random.
|
|
154
|
+
|
|
155
|
+
## 1.8.1 — 2026-08-26
|
|
156
|
+
|
|
157
|
+
DevTools showed you the wrong request, accurately.
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
|
|
161
|
+
- **A page keeps the DevTools panel while its own assets load.** Opening `/login` selected
|
|
162
|
+
`/login`, then `/favicon.ico` a few milliseconds later, then `/css/app.css`. Live mode
|
|
163
|
+
selected every trace as it arrived and a page's sub-resources arrive right behind it, so the
|
|
164
|
+
bar named a request nobody asked about, the detail below described that request's headers
|
|
165
|
+
and its empty session, and the page you were inspecting had scrolled into the list. Nothing
|
|
166
|
+
shown was wrong; it was all about the wrong request.
|
|
167
|
+
|
|
168
|
+
Traces are now classified into three kinds rather than two, because "not the document" would
|
|
169
|
+
have suppressed the form post and the Inertia visit — the requests most worth watching. What
|
|
170
|
+
gets skipped over is narrower: a sub-resource the browser fetched on its own initiative. The
|
|
171
|
+
browser is asked rather than the URL, since an app may serve an API from a `.js` route and a
|
|
172
|
+
build that hashes its asset names has no extension to read; what was actually served is the
|
|
173
|
+
fallback, so a page fetched by `curl` still reads as a page. Anything unclassifiable counts
|
|
174
|
+
as app traffic, never as an asset — being wrong there decides whether a request is skipped,
|
|
175
|
+
and skipping the wrong one is how the panel stops showing what you came to see.
|
|
176
|
+
|
|
177
|
+
An asset still takes the selection when nothing else has it, so a panel opened mid-load
|
|
178
|
+
shows a request rather than an empty pane. A paused panel still counts assets toward its
|
|
179
|
+
pending badge.
|
|
180
|
+
|
|
181
|
+
### Added
|
|
182
|
+
|
|
183
|
+
- **A `kind` facet on the DevTools All tab**, beside method and status. Assets were never the
|
|
184
|
+
problem, only their claim on the selection, so they are not hidden: pick `page` and `api`
|
|
185
|
+
for a list without fifty stylesheet fetches in it, or `asset` alone for what the browser
|
|
186
|
+
pulled in, what it cost and which of it 404'd — which was not visible anywhere before.
|
|
187
|
+
|
|
30
188
|
## 1.8.0 — 2026-08-24
|
|
31
189
|
|
|
32
|
-
The first render mode, the codemod runner 2.0 depends on, and
|
|
190
|
+
The first render mode, the codemod runner 2.0 depends on, and five failures that
|
|
33
191
|
each looked like something other than what they were.
|
|
34
192
|
|
|
35
193
|
### Added
|
package/docs/client/index.md
CHANGED
|
@@ -328,6 +328,23 @@ This package also ships `Socket`, `Channel` and `PresenceChannel` — a WebSocke
|
|
|
328
328
|
client speaking Zerotal's broadcast protocol. Different job, documented where it is
|
|
329
329
|
used: [Broadcasting → Client](/docs/broadcasting/client).
|
|
330
330
|
|
|
331
|
+
## Types
|
|
332
|
+
|
|
333
|
+
The typed client derives its shape from your app's routes, so most of these are inferred rather
|
|
334
|
+
than written by hand — but a helper that wraps a call needs to name them:
|
|
335
|
+
|
|
336
|
+
| Type | What it is |
|
|
337
|
+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
338
|
+
| `ApiRouteMap` | The generated route table the client is typed against. |
|
|
339
|
+
| `RouteShape` | One route's method, params, body and response. |
|
|
340
|
+
| `PathsFor<M>` | Every path answering a given method — what makes an unknown path a compile error. |
|
|
341
|
+
| `PathParams`, `ParamRecord` | The params a path captures. |
|
|
342
|
+
| `BodyOf<P>`, `QueryOf<P>`, `ResponseOf<P>` | One route's request body, query and response types. |
|
|
343
|
+
| `ResponseMeta` | Status, headers and timing carried alongside a response. |
|
|
344
|
+
| `RequestInterceptor`, `ResponseInterceptor` | Hooks running before a request leaves and after one returns — auth headers, retry, logging. |
|
|
345
|
+
| `CircuitBreakerOptions` | When the client stops calling an endpoint that keeps failing, and when it tries again. |
|
|
346
|
+
| `SocketOptions`, `SocketState`, `SocketLike` | The realtime socket: how it connects, where it is in its lifecycle, and the minimum interface a substitute must satisfy. |
|
|
347
|
+
|
|
331
348
|
## Next steps
|
|
332
349
|
|
|
333
350
|
- [Rate Limiting](/docs/rate-limiting) — throttle what you send and what you accept.
|
package/docs/commands.md
CHANGED
|
@@ -370,10 +370,16 @@ can gate a deploy.
|
|
|
370
370
|
| `bun zt migrate:status` | Show the status (run / pending / batch) of each migration file |
|
|
371
371
|
| `bun zt migrate:generate` | Auto-generate a migration from model schema changes |
|
|
372
372
|
| `bun zt db:seed` | Run all seeders from `database/seeders/` |
|
|
373
|
+
| `bun zt db:backup` | Take a verified SQLite snapshot with `VACUUM INTO` |
|
|
373
374
|
|
|
374
375
|
> **Danger** — `migrate --fresh` and `migrate:fresh` drop every table before
|
|
375
376
|
> re-running migrations. Never run them against a production database.
|
|
376
377
|
|
|
378
|
+
`db:backup` is the counterpart to everything above it: those commands all assume the database
|
|
379
|
+
file will still be there. It verifies every snapshot it writes and exits non-zero on any
|
|
380
|
+
failure, so it is safe to run from a timer — see
|
|
381
|
+
[Deployment](/docs/deployment#back-up-the-database) for the flags and a systemd unit.
|
|
382
|
+
|
|
377
383
|
### Generators
|
|
378
384
|
|
|
379
385
|
| Command | Creates |
|