@zerotal/arch 1.7.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/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/api-surface.md +283 -0
- package/docs/about.md +676 -0
- package/docs/admin/actions.md +183 -0
- package/docs/admin/auth.md +89 -0
- package/docs/admin/dashboard.md +155 -0
- package/docs/admin/extending-ui.md +229 -0
- package/docs/admin/extending.md +205 -0
- package/docs/admin/forms.md +155 -0
- package/docs/admin/index.md +144 -0
- package/docs/admin/operations.md +224 -0
- package/docs/admin/references.md +174 -0
- package/docs/admin/resources.md +132 -0
- package/docs/admin/structure.md +158 -0
- package/docs/admin/tables.md +185 -0
- package/docs/admin/testing.md +138 -0
- package/docs/ai.md +373 -0
- package/docs/application.md +435 -0
- package/docs/arch.md +263 -0
- package/docs/assets.md +755 -0
- package/docs/audit.md +367 -0
- package/docs/authentication.md +1000 -0
- package/docs/authorization.md +529 -0
- package/docs/broadcasting/channels.md +170 -0
- package/docs/broadcasting/client.md +108 -0
- package/docs/broadcasting/events.md +190 -0
- package/docs/broadcasting/index.md +163 -0
- package/docs/broadcasting/references.md +84 -0
- package/docs/broadcasting/testing.md +132 -0
- package/docs/cache.md +387 -0
- package/docs/carbon.md +830 -0
- package/docs/changelog.md +465 -0
- package/docs/client/auth.md +113 -0
- package/docs/client/errors.md +139 -0
- package/docs/client/files.md +118 -0
- package/docs/client/index.md +128 -0
- package/docs/client/references.md +58 -0
- package/docs/client/requests.md +131 -0
- package/docs/client/resilience.md +141 -0
- package/docs/client/testing.md +146 -0
- package/docs/commands.md +450 -0
- package/docs/components.md +2427 -0
- package/docs/config-system.md +258 -0
- package/docs/container.md +596 -0
- package/docs/context.md +579 -0
- package/docs/contributing.md +164 -0
- package/docs/controllers.md +354 -0
- package/docs/conventions.md +466 -0
- package/docs/cookies.md +101 -0
- package/docs/csrf.md +206 -0
- package/docs/database.md +476 -0
- package/docs/deployment.md +434 -0
- package/docs/devtools.md +1013 -0
- package/docs/email-verification.md +320 -0
- package/docs/encryption.md +380 -0
- package/docs/errors.md +317 -0
- package/docs/events.md +420 -0
- package/docs/flow/components.md +846 -0
- package/docs/flow/decorators.md +458 -0
- package/docs/flow/events.md +420 -0
- package/docs/flow/forms.md +551 -0
- package/docs/flow/index.md +568 -0
- package/docs/flow/layouts.md +793 -0
- package/docs/flow/lifecycle.md +336 -0
- package/docs/flow/pagination.md +402 -0
- package/docs/flow/performance.md +262 -0
- package/docs/flow/references.md +317 -0
- package/docs/flow/routing.md +550 -0
- package/docs/flow/testing.md +447 -0
- package/docs/getting-started.md +286 -0
- package/docs/health.md +269 -0
- package/docs/helpers.md +423 -0
- package/docs/i18n.md +414 -0
- package/docs/index.md +65 -0
- package/docs/inertia/build.md +95 -0
- package/docs/inertia/devtools.md +197 -0
- package/docs/inertia/index.md +267 -0
- package/docs/inertia/middleware.md +81 -0
- package/docs/inertia/props.md +507 -0
- package/docs/inertia/references.md +144 -0
- package/docs/inertia/rendering.md +189 -0
- package/docs/inertia/ssr.md +102 -0
- package/docs/inspirations.md +257 -0
- package/docs/lifecycle.md +331 -0
- package/docs/lock.md +454 -0
- package/docs/logger.md +517 -0
- package/docs/media.md +582 -0
- package/docs/middleware.md +509 -0
- package/docs/migrations.md +529 -0
- package/docs/monitor.md +377 -0
- package/docs/notifications.md +848 -0
- package/docs/orm/casts.md +365 -0
- package/docs/orm/factories.md +231 -0
- package/docs/orm/index.md +663 -0
- package/docs/orm/lifecycle.md +421 -0
- package/docs/orm/queries.md +692 -0
- package/docs/orm/relationships.md +440 -0
- package/docs/orm/serialization.md +251 -0
- package/docs/package-development.md +536 -0
- package/docs/pagination.md +340 -0
- package/docs/password-reset.md +335 -0
- package/docs/providers.md +447 -0
- package/docs/query-builder.md +465 -0
- package/docs/queue.md +549 -0
- package/docs/rate-limiting.md +253 -0
- package/docs/responses.md +336 -0
- package/docs/roles-and-2fa.md +418 -0
- package/docs/routing.md +1008 -0
- package/docs/scaffolding.md +335 -0
- package/docs/scheduler.md +533 -0
- package/docs/seeding.md +229 -0
- package/docs/session.md +451 -0
- package/docs/social.md +524 -0
- package/docs/storage.md +622 -0
- package/docs/structure.md +230 -0
- package/docs/support-policy.md +114 -0
- package/docs/telemetry.md +505 -0
- package/docs/tenancy.md +608 -0
- package/docs/testing/browser.md +153 -0
- package/docs/testing/console.md +130 -0
- package/docs/testing/database.md +238 -0
- package/docs/testing/flow-browser.md +216 -0
- package/docs/testing/http.md +441 -0
- package/docs/testing/index.md +240 -0
- package/docs/testing/mocking.md +410 -0
- package/docs/upgrade.md +192 -0
- package/docs/validator.md +549 -0
- package/docs/view.md +453 -0
- package/package.json +60 -0
- package/src/bin/mcp.ts +73 -0
- package/src/config.ts +63 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +56 -0
- package/src/install/ArchInstallCommand.ts +189 -0
- package/src/install/detect.ts +98 -0
- package/src/install/guidelines.ts +290 -0
- package/src/install/markers.ts +77 -0
- package/src/install/mcpConfig.ts +81 -0
- package/src/mcp/index.ts +27 -0
- package/src/mcp/jsonrpc.ts +133 -0
- package/src/mcp/server.ts +307 -0
- package/src/mcp/stdio.ts +111 -0
- package/src/mcp/types.ts +146 -0
- package/src/probe/ArchProbeCommand.ts +48 -0
- package/src/probe/sentinel.ts +13 -0
- package/src/probe/topics.ts +291 -0
- package/src/provider/ArchProvider.ts +101 -0
- package/src/tools/_probe.ts +164 -0
- package/src/tools/apiSurface.ts +241 -0
- package/src/tools/appInfo.ts +88 -0
- package/src/tools/baselines.ts +210 -0
- package/src/tools/context.ts +37 -0
- package/src/tools/doctor.ts +93 -0
- package/src/tools/index.ts +50 -0
- package/src/tools/logs.ts +265 -0
- package/src/tools/routes.ts +133 -0
- package/src/tools/schema.ts +119 -0
- package/src/tools/searchDocs.ts +345 -0
package/docs/audit.md
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Audit
|
|
3
|
+
description: Capture every model write and custom event — with old/new values, actor, and request metadata — into a queryable audit_logs table.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Audit
|
|
7
|
+
|
|
8
|
+
`@zerotal/audit` provides automatic, zero-boilerplate audit logging for Zerotal
|
|
9
|
+
models and manual events. Every create, update, and delete is captured — with old
|
|
10
|
+
and new values, the authenticated actor, and request metadata — and stored in a
|
|
11
|
+
queryable `audit_logs` table.
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
`@zerotal/audit` is a workspace package, so there is no separate install step:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# in your project root
|
|
19
|
+
bun add @zerotal/audit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Register the provider
|
|
23
|
+
|
|
24
|
+
Add `AuditProvider` to the providers array in `bootstrap/providers.ts`, after
|
|
25
|
+
`DatabaseProvider` (the audit table lives in your database):
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// bootstrap/providers.ts
|
|
29
|
+
import { AuditProvider } from "@zerotal/audit";
|
|
30
|
+
|
|
31
|
+
export default [
|
|
32
|
+
DatabaseProvider,
|
|
33
|
+
SessionProvider,
|
|
34
|
+
AuthProvider,
|
|
35
|
+
AuditProvider, // ← add after DatabaseProvider
|
|
36
|
+
// ...
|
|
37
|
+
];
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Registering the provider switches on the following:
|
|
41
|
+
|
|
42
|
+
- `onRegister` — binds the `Auditor` service as the `"audit"` singleton and
|
|
43
|
+
registers the schema concern that provisions the `audit_logs` table.
|
|
44
|
+
- `onBooted` — drains the pending `Auditable` registry, wiring each composed model
|
|
45
|
+
to the live `Auditor` once the container is ready.
|
|
46
|
+
|
|
47
|
+
> **Note** — No migration is required. A boot-time schema concern creates the
|
|
48
|
+
> `audit_logs` table automatically (idempotently, and skipped for the `null`
|
|
49
|
+
> driver). Register the provider and the table appears on first boot.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Create `config/audit.ts` with the `AuditConfig()` helper so every field stays
|
|
54
|
+
type-checked. The file is auto-discovered — its settings are namespaced under
|
|
55
|
+
`audit` in the config tree:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// config/audit.ts
|
|
59
|
+
import { AuditConfig } from "@zerotal/audit";
|
|
60
|
+
|
|
61
|
+
export default AuditConfig({
|
|
62
|
+
driver: "database", // 'database' | 'null'
|
|
63
|
+
table: "audit_logs",
|
|
64
|
+
pruneKeep: 0, // 0 = unlimited; cap records kept per model instance
|
|
65
|
+
captureRequest: true, // attach IP, user-agent, URL automatically
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Field | Required | Default | Description |
|
|
70
|
+
| ---------------- | -------- | -------------- | --------------------------------------------------------------------- |
|
|
71
|
+
| `driver` | yes | `"database"` | Storage backend — `"database"` or `"null"`. |
|
|
72
|
+
| `table` | no | `"audit_logs"` | Table name used by the database driver. |
|
|
73
|
+
| `pruneKeep` | no | `0` | Max audit records kept per model instance. `0` = unlimited. |
|
|
74
|
+
| `captureRequest` | no | `true` | Attach IP, user-agent, and URL from the active request automatically. |
|
|
75
|
+
|
|
76
|
+
## Auditing models
|
|
77
|
+
|
|
78
|
+
### The Auditable mixin
|
|
79
|
+
|
|
80
|
+
Compose `Auditable` into any `Model` subclass. The audit system hooks
|
|
81
|
+
into the ORM lifecycle and records `created`, `updated`, and `deleted` events
|
|
82
|
+
automatically. Like every Zerotal mixin it takes only `(Base)` — configure it with
|
|
83
|
+
overridable static fields on the model.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// app/models/User.ts
|
|
87
|
+
import { Auditable } from "@zerotal/audit";
|
|
88
|
+
import { Model, column, table } from "@zerotal/orm";
|
|
89
|
+
|
|
90
|
+
@table("users")
|
|
91
|
+
export class User extends Model.using(Auditable) {
|
|
92
|
+
protected static auditExcept = ["password", "rememberToken"];
|
|
93
|
+
|
|
94
|
+
@column({ type: "string" }) name: string;
|
|
95
|
+
@column({ type: "string" }) email: string;
|
|
96
|
+
@column({ type: "string" }) password?: string;
|
|
97
|
+
@column({ type: "string" }) rememberToken?: string;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`Auditable` composes with the auth mixins as the outermost wrapper:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// app/models/User.ts
|
|
105
|
+
export class User extends Auditable(WithRoles(WithPermissions(AuthUser))) {
|
|
106
|
+
protected static auditExcept = ["password"];
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> **Danger** — Never audit columns containing credentials. Exclude `password`,
|
|
111
|
+
> `rememberToken`, API tokens, etc. via `auditExcept` (shown above).
|
|
112
|
+
|
|
113
|
+
Configure auditing with overridable static fields, read per event from the
|
|
114
|
+
concrete model:
|
|
115
|
+
|
|
116
|
+
| Static field | Type | Description |
|
|
117
|
+
| ------------- | ---------- | ----------------------------------------------------------------------- |
|
|
118
|
+
| `auditOnly` | `string[]` | Allowlist — only these columns appear in `old_values` / `new_values`. |
|
|
119
|
+
| `auditExcept` | `string[]` | Denylist — exclude these columns (applied when `auditOnly` is not set). |
|
|
120
|
+
| `auditType` | `string` | Override the `auditable_type` string (defaults to the class name). |
|
|
121
|
+
|
|
122
|
+
The mixin also adds two instance methods, `auditLog()` and `auditLogs()`, covered
|
|
123
|
+
in [Manual audit events](#manual-audit-events) and
|
|
124
|
+
[Querying audit logs](#querying-audit-logs).
|
|
125
|
+
|
|
126
|
+
### Programmatic registration
|
|
127
|
+
|
|
128
|
+
For a model you'd rather not wrap in the mixin, register it from a provider's
|
|
129
|
+
`onBooted()` (after the container has resolved the `"audit"` binding). Configure it
|
|
130
|
+
with the same static fields:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// in a ServiceProvider.onBooted()
|
|
134
|
+
import { registerAudit } from "@zerotal/audit";
|
|
135
|
+
|
|
136
|
+
registerAudit(User);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> **Which should I use?** Reach for the `Auditable` mixin by default — it also
|
|
140
|
+
> gives you the `auditLog()` / `auditLogs()` helpers. Use `registerAudit()` only
|
|
141
|
+
> when you cannot change a model's class hierarchy (e.g. a model from another
|
|
142
|
+
> package).
|
|
143
|
+
|
|
144
|
+
Both routes attach the same `AuditObserver`, which is what actually watches the
|
|
145
|
+
model's lifecycle. An update is recorded in two phases, and the reason is worth
|
|
146
|
+
knowing if you ever hook the same events yourself: the previous values only exist
|
|
147
|
+
until the ORM refreshes its snapshot, so the observer captures them during
|
|
148
|
+
`saving` and pairs them with the current values in `updated`. That is why an audit
|
|
149
|
+
row shows a real before _and_ after rather than the same values twice.
|
|
150
|
+
|
|
151
|
+
`AuditObserver` is exported for the rare case of composing it into an observer of
|
|
152
|
+
your own; using the mixin or `registerAudit()` is the supported path.
|
|
153
|
+
|
|
154
|
+
## Manual audit events
|
|
155
|
+
|
|
156
|
+
Log any custom event — logins, exports, settings changes — via the `Audit` facade.
|
|
157
|
+
Pass the **model instance** the event concerns; `auditable_type` and `auditable_id`
|
|
158
|
+
are derived from it, so logs are always linked to a record:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
function log(event: AuditEvent, model: Model, payload?: InstanceAuditPayload): Promise<void>;
|
|
162
|
+
function log(event: AuditEvent, payload: Omit<AuditPayload, "event">): Promise<void>;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// in a controller or service (within a request context)
|
|
167
|
+
import { Audit } from "@zerotal/audit";
|
|
168
|
+
|
|
169
|
+
await Audit.log("login.success", user, {
|
|
170
|
+
tags: { method: "github_oauth" },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await Audit.log("report.exported", report, {
|
|
174
|
+
tags: { format: "csv", rows: 5000 },
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
For an event not tied to a model, pass a raw payload with `auditable_type`. Outside
|
|
179
|
+
a request (queue job, CLI command) supply the actor explicitly:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// in a queue job or CLI command
|
|
183
|
+
await Audit.log("subscription.renewed", {
|
|
184
|
+
auditable_type: "Subscription",
|
|
185
|
+
auditable_id: sub.id,
|
|
186
|
+
actor_type: "user",
|
|
187
|
+
actor_id: sub.userId,
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
When a model is `Auditable`, the same call is available as an instance method:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// in a controller or service
|
|
195
|
+
await user.auditLog("login.success", { tags: { method: "github_oauth" } });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The facade reads the authenticated user and request details (IP, user-agent, URL)
|
|
199
|
+
automatically from the active request context. You only need to supply them when
|
|
200
|
+
operating outside a request.
|
|
201
|
+
|
|
202
|
+
> **Warning** — Audit failures never crash the application; a failed write is
|
|
203
|
+
> logged to the console and swallowed. Treat the audit log as best-effort, not as a
|
|
204
|
+
> transactional guarantee.
|
|
205
|
+
|
|
206
|
+
## Querying audit logs
|
|
207
|
+
|
|
208
|
+
`AuditLog` is a full `Model` with scopes and chainable queries:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
// in a controller or service
|
|
212
|
+
import { AuditLog } from "@zerotal/audit";
|
|
213
|
+
|
|
214
|
+
// Last 25 events for a specific model instance
|
|
215
|
+
const history = await AuditLog.query()
|
|
216
|
+
.where("auditable_type", "User")
|
|
217
|
+
.where("auditable_id", String(user.id))
|
|
218
|
+
.orderBy("id", "desc")
|
|
219
|
+
.limit(25)
|
|
220
|
+
.get();
|
|
221
|
+
|
|
222
|
+
// Built-in scopes return a chainable query builder
|
|
223
|
+
const userHistory = await AuditLog.forModel("User", user.id).get();
|
|
224
|
+
const actorLog = await AuditLog.byActor(user.id).get();
|
|
225
|
+
const loginEvents = await AuditLog.ofEvent("login.success").get();
|
|
226
|
+
|
|
227
|
+
// Paginate
|
|
228
|
+
const page = await AuditLog.query()
|
|
229
|
+
.where("actor_id", user.id)
|
|
230
|
+
.orderBy("id", "desc")
|
|
231
|
+
.paginate(20, 1);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The `Audit` facade exposes the same queries plus a convenience read:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
// in a controller or service
|
|
238
|
+
const logs = await Audit.logs(User, user.id).orderBy("id", "desc").limit(25).get();
|
|
239
|
+
const byActor = await Audit.logsByActor(user.id).get();
|
|
240
|
+
const events = await Audit.logsOfEvent("login.success").get();
|
|
241
|
+
|
|
242
|
+
// Eager array of recent records for a model
|
|
243
|
+
const recent = await Audit.historyFor("User", user.id, 25);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
An `Auditable` model also offers an instance shortcut:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
// in a controller or service
|
|
250
|
+
const logs = await user.auditLogs().orderBy("id", "desc").limit(25).get();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Inspecting a record
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// in a controller or service
|
|
257
|
+
const entry = history[0];
|
|
258
|
+
|
|
259
|
+
entry.event; // "updated"
|
|
260
|
+
entry.auditableType; // "User"
|
|
261
|
+
entry.auditableId; // "42"
|
|
262
|
+
entry.actorId; // 7
|
|
263
|
+
entry.oldValues; // { email: "old@example.com" }
|
|
264
|
+
entry.newValues; // { email: "new@example.com" }
|
|
265
|
+
entry.ipAddress; // "203.0.113.1"
|
|
266
|
+
entry.url; // "http://localhost:3000/profile"
|
|
267
|
+
|
|
268
|
+
entry.changedKeys; // ["email"]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## What gets recorded
|
|
272
|
+
|
|
273
|
+
| Event | `old_values` | `new_values` |
|
|
274
|
+
| --------- | ----------------------- | ---------------------- |
|
|
275
|
+
| `created` | _(empty)_ | full snapshot |
|
|
276
|
+
| `updated` | changed fields (before) | changed fields (after) |
|
|
277
|
+
| `deleted` | full snapshot | _(empty)_ |
|
|
278
|
+
| custom | whatever you pass | whatever you pass |
|
|
279
|
+
|
|
280
|
+
For `updated`, only the diff is stored — unchanged fields are not included.
|
|
281
|
+
Snapshots use the model's `toJSON()`, so columns marked `hidden` are excluded
|
|
282
|
+
automatically.
|
|
283
|
+
|
|
284
|
+
## Drivers
|
|
285
|
+
|
|
286
|
+
| Driver | Class | Description |
|
|
287
|
+
| ---------- | ---------------- | ----------------------------------------------------- |
|
|
288
|
+
| `database` | `DatabaseDriver` | Stores to `audit_logs` using `@zerotal/orm`. Default. |
|
|
289
|
+
| `null` | `NullDriver` | Discards all entries. Useful in tests. |
|
|
290
|
+
|
|
291
|
+
Both classes are exported, so a custom driver can wrap one rather than
|
|
292
|
+
reimplementing it — decorating `DatabaseDriver` to also ship entries to a SIEM,
|
|
293
|
+
for instance.
|
|
294
|
+
|
|
295
|
+
Switch the driver in config:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
// config/audit.ts
|
|
299
|
+
import { AuditConfig } from "@zerotal/audit";
|
|
300
|
+
|
|
301
|
+
export default AuditConfig({ driver: "null" }); // suppress auditing
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Or swap it entirely in a `ServiceProvider` by binding a custom `AuditDriver`
|
|
305
|
+
implementation to the `"audit"` singleton.
|
|
306
|
+
|
|
307
|
+
## Testing
|
|
308
|
+
|
|
309
|
+
In tests, swap to the `NullDriver` so no database is needed. Bind a fresh
|
|
310
|
+
`Auditor` over the `"audit"` key:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
// tests/setup.ts
|
|
314
|
+
import { Application } from "zerotal";
|
|
315
|
+
import { Auditor, NullDriver } from "@zerotal/audit";
|
|
316
|
+
|
|
317
|
+
// Bind a fresh NullDriver so tests don't write to a DB
|
|
318
|
+
app.container.singleton(
|
|
319
|
+
"audit",
|
|
320
|
+
() => new Auditor(new NullDriver(), { driver: "null", captureRequest: false }),
|
|
321
|
+
);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Security notes
|
|
325
|
+
|
|
326
|
+
> **Danger** — Never audit columns containing credentials. Exclude `password`,
|
|
327
|
+
> `rememberToken`, API tokens, etc. via `auditExcept`.
|
|
328
|
+
|
|
329
|
+
> **Warning** — `audit_logs` is append-only by design. The `AuditLog` model
|
|
330
|
+
> disables timestamps; do not add `updated_at` or allow row updates.
|
|
331
|
+
|
|
332
|
+
> **Note** — Grant read access to `audit_logs` only to admin roles.
|
|
333
|
+
|
|
334
|
+
## References
|
|
335
|
+
|
|
336
|
+
`Audit` facade — resolves the `Auditor` bound at `"audit"`:
|
|
337
|
+
|
|
338
|
+
| Method | Signature | Description |
|
|
339
|
+
| ------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
340
|
+
| `log` | `(event, model, payload?) => Promise<void>` · `(event, payload) => Promise<void>` | Record a manual event from a model instance or payload. |
|
|
341
|
+
| `historyFor` | `(type: string, id: string \| number, limit?: number) => Promise<AuditRecord[]>` | Eager array of recent records for a model. |
|
|
342
|
+
| `logs` | `(model: AuditableRef, id: string \| number) => ModelQueryBuilder<AuditLog>` | Chainable query of one model's audit history. |
|
|
343
|
+
| `logsByActor` | `(actorId: number) => ModelQueryBuilder<AuditLog>` | Chainable query of every log by an actor. |
|
|
344
|
+
| `logsOfEvent` | `(event: AuditEvent) => ModelQueryBuilder<AuditLog>` | Chainable query of every log for an event name. |
|
|
345
|
+
|
|
346
|
+
`AuditLog` model — scopes and helpers:
|
|
347
|
+
|
|
348
|
+
| Member | Signature | Description |
|
|
349
|
+
| ------------- | --------------------------------------------------------------------- | -------------------------------------- |
|
|
350
|
+
| `forModel` | `(type: string, id: string \| number) => ModelQueryBuilder<AuditLog>` | Scope to one model instance's history. |
|
|
351
|
+
| `byActor` | `(actorId: number) => ModelQueryBuilder<AuditLog>` | Scope to a single actor. |
|
|
352
|
+
| `ofEvent` | `(event: AuditEvent) => ModelQueryBuilder<AuditLog>` | Scope to a single event name. |
|
|
353
|
+
| `changedKeys` | `string[]` | Getter — keys present in `newValues`. |
|
|
354
|
+
|
|
355
|
+
`Auditable(Base)` instance methods:
|
|
356
|
+
|
|
357
|
+
| Method | Signature | Description |
|
|
358
|
+
| ----------- | ---------------------------------------------------------------------- | ------------------------------------------- |
|
|
359
|
+
| `auditLog` | `(event: AuditEvent, payload?: InstanceAuditPayload) => Promise<void>` | Log a custom event against this instance. |
|
|
360
|
+
| `auditLogs` | `() => ModelQueryBuilder<AuditLog>` | Chainable query of this instance's history. |
|
|
361
|
+
|
|
362
|
+
## Next steps
|
|
363
|
+
|
|
364
|
+
- [ORM Lifecycle](/docs/orm/lifecycle) — the model hooks the audit system listens to.
|
|
365
|
+
- [Authentication](/docs/authentication) — how the actor on each entry is resolved.
|
|
366
|
+
- [Authorization](/docs/authorization) — gate read access to audit logs by role.
|
|
367
|
+
- [Testing](/docs/testing/index) — swap in the null driver for isolated tests.
|