Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ adapters/*
!adapters/README.md
!adapters/AGENTS.md
background-jobs-dbs
tests/jest_tests/db/
tests/jest_tests/db/
connectors/adminforth-connector-*/
2 changes: 1 addition & 1 deletion adminforth/dataConnectors/baseConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import type { AdminUser } from "../types/Common.js"

import { suggestIfTypo } from "../modules/utils.js";
import { interpretResource } from "../modules/restApi.js";
import { interpretResource } from "../modules/resourceAccess.js";
import { ActionCheckSource, AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum } from "../types/Common.js";
import { randomUUID } from "crypto";
import dayjs from "dayjs";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,65 @@ await admin.resource('adminuser').get(Filters.EQ('id', '1234'));

Here we will show you how to use the Data API with simple examples.

## Access levels

Resource data can be reached at two levels, and the difference is who asked for
the operation.

```ts
const users = admin.resource('adminuser');

// a user asked for this
await users.asUser(adminUser, { meta }).create(record);

// plain data access the user did not ask for
await users.create(record);
```

`asUser()` is the level for anything that came from a request. It enforces the
resource ACL, applies the column access rules (`backendOnly`, `editReadonly`,
`showIn` and its `allowModifyWhenNotShowIn*` / `fillOnCreate` escapes), strips
columns the user may not read out of results, and runs the resource lifecycle
hooks — including the row-scoping `beforeDatasourceRequest` hooks that express
multi-tenancy. Use it in plugin endpoints: you do not have to remember the
individual checks, and you cannot forget one.

The bare methods are plain data access for internal bookkeeping: no permission
checks, no column access rules, no hooks. Writes are still normalized; the
connector remains responsible for its own constraints.

`admin.createResourceRecord`, `admin.updateResourceRecord` and
`admin.deleteResourceRecord` are the older entry points which this API replaces.
They still work and still run hooks, but they are deprecated: move calls to
`asUser()` when a user asked for the operation, and to the bare methods when
nothing did.

A denied or failed operation is always visible. `get`, `list`, `count`,
`aggregate` and `delete` throw, since their return value carries no room for an
error; `create` and `update` resolve to `{ ok: false, error }`. `delete` returns
`false` when the record simply did not exist:

```ts
const { ok, error } = await users.asUser(adminUser, { meta }).update(id, updates);

try {
const deleted = await users.asUser(adminUser, { meta }).delete('1234');
// deleted === false means there was no such record
} catch (e) {
// ACL denial, cascade failure, or a hook rejection
}
```

When the caller has already loaded the record, pass its snapshot to the save
hooks. ACL and row scope use the current record from a scoped lookup before
mutating it:

```ts
await users.asUser(adminUser, { meta, oldRecord }).update(recordId, updates);
await users.asUser(adminUser, { meta, record }).delete(recordId);
```


## Get one item from database


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ This is opt-in. It is especially important for the column configured as `auth.us

| Path | When `normalize` runs |
| --- | --- |
| AdminForth CRUD (`createResourceRecord`, `updateResourceRecord`) | Before validation and `beforeSave` hooks |
| Data API (`admin.resource(...).create()` and `.update()`) | Before the record reaches the connector |
| User-scoped Data API (`admin.resource(...).asUser(...)`) and `admin.createResourceRecord` | Before validation and `beforeSave` hooks |
| Bare Data API (`admin.resource(...).create(...)` and siblings) | Before the connector operation |
| Deprecated AdminForth CRUD (`createResourceRecord`, `updateResourceRecord`) | Before validation and `beforeSave` hooks |
| Core password login | On the submitted value of `auth.usernameField`, before the user lookup |
| Reads and filters | Never — this includes `get`, `list`, `count`, search, and `Filters.EQ` |

Expand Down
Loading