@jskit-ai/agent-docs 0.1.283 → 0.1.285
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/guide/agent/app-extras/assistant.md +14 -2
- package/guide/agent/app-extras/realtime.md +38 -1
- package/guide/agent/app-setup/authentication.md +76 -14
- package/guide/agent/app-setup/console.md +8 -0
- package/guide/agent/app-setup/database-layer.md +23 -103
- package/guide/agent/app-setup/existing-application-migration.md +134 -72
- package/guide/agent/app-setup/multi-homing.md +94 -32
- package/guide/agent/app-setup/quickstart.md +1 -1
- package/guide/agent/app-setup/run-an-accounts-hub.md +1 -1
- package/guide/agent/app-setup/upgrading-jskit.md +17 -3
- package/guide/agent/app-setup/users.md +8 -5
- package/guide/agent/framework/application-operations.md +1 -1
- package/guide/agent/framework/crud-operations.md +63 -45
- package/guide/agent/framework/data-access.md +1207 -0
- package/guide/agent/framework/sanity-checks.md +26 -27
- package/guide/agent/framework/typed-features.md +117 -248
- package/guide/agent/index.md +2 -0
- package/package.json +6 -3
- package/patterns/feature-package/PATTERN.md +25 -17
- package/patterns/feature-package/example/booking-engine/package.json +1 -1
- package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
- package/patterns/feature-package/example/variations/invoiceResource.js +15 -0
- package/patterns/feature-package/example/variations/invoiceRollupActions.js +4 -4
- package/patterns/feature-package/example/variations/ownedTableRepository.js +27 -0
- package/patterns/feature-package/example/variations/types.js +1 -1
- package/patterns/minimal-foundation/example/AGENTS.md +4 -0
- package/patterns/minimal-foundation/example/package.json +4 -4
- package/patterns/minimal-foundation/example/server.js +4 -1
- package/patterns/shell-foundation/example/AGENTS.md +4 -0
- package/patterns/shell-foundation/example/package.json +5 -5
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/patterns/shell-foundation/example/server.js +4 -1
- package/reference/autogen/KERNEL_MAP.md +3 -1
- package/reference/autogen/PATTERN_INDEX.md +70 -59
- package/reference/autogen/README.md +1 -1
- package/reference/autogen/packages/agent-docs.md +7 -3
- package/reference/autogen/packages/agent-runtime.md +48 -11
- package/reference/autogen/packages/architecture-inspector.md +6 -2
- package/reference/autogen/packages/assistant-core.md +4 -4
- package/reference/autogen/packages/assistant-runtime.md +33 -13
- package/reference/autogen/packages/auth-provider-local-core.md +3 -1
- package/reference/autogen/packages/auth-provider-local-db-core.md +69 -14
- package/reference/autogen/packages/auth-provider-oidc-core.md +5 -1
- package/reference/autogen/packages/auth-provider-supabase-core.md +1 -1
- package/reference/autogen/packages/connectors-core.md +22 -2
- package/reference/autogen/packages/console-core.md +9 -5
- package/reference/autogen/packages/crud-core.md +45 -175
- package/reference/autogen/packages/database-runtime-mysql.md +0 -26
- package/reference/autogen/packages/database-runtime.md +11 -33
- package/reference/autogen/packages/http-runtime.md +3 -3
- package/reference/autogen/packages/http-web.md +12 -0
- package/reference/autogen/packages/kernel.md +27 -2
- package/reference/autogen/packages/oidc-issuer-core.md +4 -0
- package/reference/autogen/packages/payments-core.md +28 -6
- package/reference/autogen/packages/realtime.md +15 -6
- package/reference/autogen/packages/resource-core.md +32 -5
- package/reference/autogen/packages/resource-crud-core.md +30 -34
- package/reference/autogen/packages/resource-runtime.md +220 -0
- package/reference/autogen/packages/rewarded-core.md +16 -10
- package/reference/autogen/packages/shell-web.md +1 -0
- package/reference/autogen/packages/users-core.md +33 -14
- package/reference/autogen/packages/workspaces-core.md +17 -10
- package/reference/autogen/tooling/config-eslint.md +74 -0
- package/reference/autogen/tooling/jskit-catalog.md +50 -1
- package/reference/autogen/tooling/jskit-release.md +5 -0
- package/skills/jskit/SKILL.md +4 -1
- package/skills/jskit/references/app-operations.md +1 -1
- package/skills/jskit/references/crud-operations.md +62 -44
- package/skills/jskit/references/data-access.md +1202 -0
- package/skills/jskit/references/database-layer.md +411 -0
- package/skills/jskit/references/existing-application-migration.md +134 -72
- package/skills/jskit/references/heuristics/INDEX.md +6 -4
- package/skills/jskit/references/heuristics/crud-authoring.md +26 -15
- package/skills/jskit/references/heuristics/data-access.md +19 -0
- package/skills/jskit/references/heuristics/filters.md +6 -6
- package/skills/jskit/references/heuristics/row-policies.md +40 -16
- package/skills/jskit/references/heuristics/server-search.md +30 -42
- package/skills/jskit/references/heuristics/table-resource-fields.md +78 -0
- package/skills/jskit/references/heuristics/typed-features.md +3 -3
- package/skills/jskit/references/multi-homing.md +94 -32
- package/skills/jskit/references/pattern-index.md +70 -59
- package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +4 -0
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +4 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +4 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +4 -1
- package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -2
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -2
- package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +15 -0
- package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
- package/skills/jskit/references/patterns/crud/crud-contract/PATTERN.md +133 -0
- package/skills/jskit/references/patterns/crud/crud-contract/example/bookResource.js +74 -0
- package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/PATTERN.md +69 -37
- package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/package.json +5 -4
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/server/BooksFeature.js +13 -0
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/bookResource.js +74 -0
- package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/index.js +1 -0
- package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +26 -8
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -2
- package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +3 -3
- package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +9 -4
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -1
- package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +8 -0
- package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +8 -4
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -1
- package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +8 -0
- package/skills/jskit/references/patterns/database/table-resource/PATTERN.md +120 -0
- package/skills/jskit/references/patterns/database/table-resource/example/orderResource.js +25 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md +7 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +1 -1
- package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +25 -17
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
- package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
- package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceResource.js +15 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +4 -4
- package/skills/jskit/references/patterns/server/feature-package/example/variations/ownedTableRepository.js +27 -0
- package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +63 -10
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +5 -4
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/UsersFeature.js +7 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/index.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userCrud.js +21 -0
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +11 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +14 -5
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +52 -0
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/index.js +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userCrud.js +21 -0
- package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -9
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +16 -0
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +2 -0
- package/skills/jskit/references/sanity-checks.md +27 -28
- package/skills/jskit/references/typed-features.md +117 -248
- package/patterns/feature-package/example/variations/customKnexRepository.js +0 -40
- package/reference/autogen/packages/json-rest-api-core.md +0 -66
- package/skills/jskit/references/heuristics/crud-repository-mapping.md +0 -140
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/BooksFeature.js +0 -13
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/bookResource.js +0 -83
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/index.js +0 -1
- package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +0 -155
- package/skills/jskit/references/patterns/crud/resource-contract/example/bookResource.js +0 -87
- package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +0 -40
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userResource.js +0 -69
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +0 -56
- /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/migrations/20260815000000_books.cjs +0 -0
- /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/src/server/types.js +0 -0
|
@@ -124,11 +124,16 @@ above. This section documents that existing surface's behavior and migration
|
|
|
124
124
|
constraints, not a second engine/tool implementation.
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
|
-
npm install @jskit-ai/assistant-runtime
|
|
127
|
+
npm install @jskit-ai/assistant-runtime @jskit-ai/resource-runtime
|
|
128
128
|
npm run db:migrate
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
The application registers `AssistantStoreFeature` (from
|
|
132
|
+
`@jskit-ai/assistant-runtime/server`) in its server feature list. It owns the
|
|
133
|
+
assistant's four tables as table resources and provides their repositories as
|
|
134
|
+
`assistant.repositories`, which `AssistantFeature` uses; without it, or an
|
|
135
|
+
`assistant.conversations` runtime, the assistant refuses to start. These
|
|
136
|
+
migration instructions apply only to an application selecting the database
|
|
132
137
|
integration. Its `assistant/assistant-surface` pattern composes the SQL surface;
|
|
133
138
|
there is no assistant generator.
|
|
134
139
|
|
|
@@ -205,6 +210,13 @@ user change the instructions; choose it only deliberately. A malformed value
|
|
|
205
210
|
stops the server from starting. Anyone else is refused with
|
|
206
211
|
`assistant.runtime.settings.updateForbidden` (403).
|
|
207
212
|
|
|
213
|
+
The settings the server returns say whether the signed-in person may change
|
|
214
|
+
them (`canUpdate`, from the same check that refuses the change), and the
|
|
215
|
+
settings pane follows it: people who may change them get **Save** in the app
|
|
216
|
+
bar, anyone else sees the instructions read-only, without Save. The browser
|
|
217
|
+
never guesses the permission, so a `settingsUpdatePermission` in server config
|
|
218
|
+
needs nothing on the client.
|
|
219
|
+
|
|
208
220
|
### Existing SQL conversations
|
|
209
221
|
|
|
210
222
|
The optional database integration uses the common conversation runtime for
|
|
@@ -29,7 +29,44 @@ The `event_scope` audience, the default for action entity-change and CRUD
|
|
|
29
29
|
example one written from a public route or by a job, reaches nobody and logs a
|
|
30
30
|
warning; `event_scope` never widens to all users. Broadcast to every signed-in
|
|
31
31
|
user only through the explicit `all_users` audience; a CRUD feature selects it
|
|
32
|
-
with `
|
|
32
|
+
with `defineCrudFeature({ realtimeAudience: "all_users" })`.
|
|
33
|
+
|
|
34
|
+
An audience entry may also reach users that only a record's owner knows, such
|
|
35
|
+
as the person an invitation was sent to. It provides
|
|
36
|
+
`audienceUsers({ event, options })`, which answers, or resolves to, a list of
|
|
37
|
+
user ids; realtime adds each one's user room. It asks the owners' APIs (for
|
|
38
|
+
example `activeMemberIds(workspaceId, options)` of `workspaces.memberships`, or
|
|
39
|
+
the record owner's repository) and never the database directly. It runs once
|
|
40
|
+
for the event, however many sockets receive it, and `options.context` is the
|
|
41
|
+
event's own scope (its workspace, user, or user in a workspace), so the owners
|
|
42
|
+
answer for that scope and not for a request's. An event written on a customer's
|
|
43
|
+
or a public surface has a global scope, which reaches no room by itself: the
|
|
44
|
+
framework adds the workspace the request resolved as its `workspaceId`, so the
|
|
45
|
+
audience reads there and `all_workspace_users` reaches it. The payload never
|
|
46
|
+
chooses the scope, since a payload may copy an input id: an `audienceUsers`
|
|
47
|
+
reads with `options`, never with a workspace the payload names. An event with
|
|
48
|
+
no workspace and no user reads with no context. Realtime needs no database, so
|
|
49
|
+
those contexts come from `RealtimeAudienceScopeFeature`
|
|
50
|
+
(`@jskit-ai/realtime/server/RealtimeAudienceScopeFeature`): an application whose
|
|
51
|
+
action events declare such an audience registers it beside its other features,
|
|
52
|
+
or realtime refuses to boot and names the actions. An audience that reads only
|
|
53
|
+
public tables (users, workspaces, memberships, invitations) says so with
|
|
54
|
+
`publicTablesOnly: true` and reads with no context; an owned table would throw
|
|
55
|
+
`MissingScopeError` there. An audience given as a function of the execution is
|
|
56
|
+
not known at boot, so realtime checks it when the event is published: without
|
|
57
|
+
the feature, an entry that reads in the event's scope is logged as an error and
|
|
58
|
+
skipped. An `audienceUsers` that fails is logged as an error and skipped too,
|
|
59
|
+
and the event still reaches the audience's other rooms:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
realtime: {
|
|
63
|
+
event: "users.bootstrap.changed",
|
|
64
|
+
audience: {
|
|
65
|
+
preset: "event_scope",
|
|
66
|
+
audienceUsers: ({ event, options }) => invitations.recipientUserIds(event.entityId, options)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
33
70
|
|
|
34
71
|
Client features register listeners through `@jskit-ai/realtime`. Keep query
|
|
35
72
|
invalidation close to the resource that owns the query keys.
|
|
@@ -114,10 +114,20 @@ A route name auth does not have, or a value that is neither
|
|
|
114
114
|
or text such as `"30 seconds"` or `"1 hour"`.
|
|
115
115
|
|
|
116
116
|
The address is Fastify's `request.ip`. Behind a reverse proxy every request
|
|
117
|
-
comes from the proxy, so all clients would share one count
|
|
118
|
-
`
|
|
119
|
-
`
|
|
120
|
-
|
|
117
|
+
comes from the proxy, so all clients would share one count. The app's
|
|
118
|
+
`server.js` creates Fastify with
|
|
119
|
+
`trustProxy: resolveTrustedProxies(runtimeEnv)` (from
|
|
120
|
+
`@jskit-ai/kernel/server/http`), which reads one setting, `TRUSTED_PROXIES`:
|
|
121
|
+
the addresses or ranges of the proxies in front of the server, comma-separated,
|
|
122
|
+
in Fastify's `trustProxy` syntax (`10.0.0.5`, `10.0.0.0/24`, `fd00::/8`, or
|
|
123
|
+
`loopback`, `linklocal` or `uniquelocal`), for example
|
|
124
|
+
`TRUSTED_PROXIES=loopback,10.0.0.5`. Only those proxies' `X-Forwarded-For` (and
|
|
125
|
+
`-Host` and `-Proto`) is believed, so each client behind them has its own count
|
|
126
|
+
and a client that sends the header itself changes nothing. Without the setting
|
|
127
|
+
only a proxy on the same machine (`loopback`) is believed. `true` and hop counts
|
|
128
|
+
stop startup, because they believe a header any client can send. Name the
|
|
129
|
+
narrowest addresses that are only your proxies: a private range shared with
|
|
130
|
+
other machines lets each of them choose its address.
|
|
121
131
|
|
|
122
132
|
**Per account (local auth).** After 5 failed sign-ins in a row for one email,
|
|
123
133
|
that email must wait 30 seconds before its next attempt; each further failure
|
|
@@ -154,23 +164,35 @@ password, in every environment, unless `AUTH_LOCAL_EMAIL_CONFIRMATION=false`.
|
|
|
154
164
|
Registering keeps the registration waiting, with no account, profile or
|
|
155
165
|
session, and answers `requiresEmailConfirmation: true`; the sign-in page then
|
|
156
166
|
shows "Confirm your email", with a button to send a new link and a field for
|
|
157
|
-
the link or its code.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
the link or its code. Confirming takes the link's code and the password the
|
|
168
|
+
registration was made with (`POST /api/register/confirm` with
|
|
169
|
+
`{ token, password }`): opening the link (`/auth/login?confirmation_token=...`)
|
|
170
|
+
asks for that password, and right after registering the page keeps the one
|
|
171
|
+
just typed. Confirming creates the account and signs the person in. The
|
|
172
|
+
`auth.local.user.registered` hook runs then, not on registering. A missing or
|
|
173
|
+
wrong password creates nothing and keeps the registration and its link, with
|
|
174
|
+
status 400 and `auth.local.register.confirmationPasswordInvalid`. Signing in
|
|
175
|
+
with the right password while the registration waits is refused with status
|
|
176
|
+
403 and `auth.login.emailUnconfirmed`, and the sign-in page shows how to
|
|
177
|
+
confirm; a wrong password is an ordinary failed sign-in.
|
|
162
178
|
|
|
163
179
|
A link's code is random, kept only as a hash, works once and expires after a
|
|
164
|
-
day. Registering the same email again
|
|
165
|
-
so a waiting registration never keeps the email from its owner.
|
|
166
|
-
new link
|
|
180
|
+
day. Registering the same email again replaces the waiting registration whole
|
|
181
|
+
and its link, so a waiting registration never keeps the email from its owner.
|
|
182
|
+
Asking for a new link replaces the link and keeps the registration, password
|
|
183
|
+
included; it answers the same whether or not a registration waits, and sends
|
|
184
|
+
nothing for an expired one. The link proves the email and the password proves
|
|
185
|
+
the registration: whoever registers an email, before its owner or after, the
|
|
186
|
+
account its link creates has the password of the person who confirms it. A
|
|
187
|
+
link its owner never asked for confirms nothing with the owner's password;
|
|
188
|
+
the owner registers instead.
|
|
167
189
|
|
|
168
190
|
The link is emailed by the optional `auth.local.confirmation-sender`
|
|
169
191
|
capability, an async function accepting `{ email, confirmationUrl }`, built
|
|
170
192
|
like the recovery sender below; with it, `APP_PUBLIC_URL` is required and the
|
|
171
193
|
link is never returned or logged. In development without one, the link is in
|
|
172
194
|
the registration's answer (`confirmationUrl`) and the log, and the sign-in page
|
|
173
|
-
shows it with a button that confirms it. In production (`NODE_ENV=production`)
|
|
195
|
+
shows it with a button that confirms it with the password typed. In production (`NODE_ENV=production`)
|
|
174
196
|
without one, while anyone may register, the server refuses to start, naming
|
|
175
197
|
the ways out: install a sender, set `AUTH_LOCAL_EMAIL_CONFIRMATION=false`, or
|
|
176
198
|
set `AUTH_LOCAL_SELF_REGISTRATION=false`. For direct library
|
|
@@ -203,7 +225,47 @@ upgrade wait. Waiting registrations are kept in the file store
|
|
|
203
225
|
the `auth_local_registrations` table (run `npm run db:migrate`). A custom
|
|
204
226
|
`auth.local.backend` implements the `registrations` collection. Tests that
|
|
205
227
|
register and expect a session either confirm with the answer's
|
|
206
|
-
`confirmationUrl` or set
|
|
228
|
+
`confirmationUrl` and the password they registered with, or set
|
|
229
|
+
`AUTH_LOCAL_EMAIL_CONFIRMATION=false`.
|
|
230
|
+
|
|
231
|
+
## Local accounts from application code (database backend)
|
|
232
|
+
|
|
233
|
+
Application code that works with local auth's records beside local auth, such
|
|
234
|
+
as an emailed sign-in link or a job that removes old sessions, requires their
|
|
235
|
+
owners' capabilities from `@jskit-ai/auth-provider-local-db-core` and never
|
|
236
|
+
reads or writes the `auth_local_*` tables. Every method takes the call's
|
|
237
|
+
`options` last and runs in `options.trx` when there is one. That transaction
|
|
238
|
+
must be a managed one: open it with one of the application's own table
|
|
239
|
+
resource handles, `handle.withTransaction(options, (txOptions) => …)`, and pass
|
|
240
|
+
`txOptions` on. Their writes refuse a transaction from `knex.transaction()`, and
|
|
241
|
+
a failed write fails the whole transaction even when the work catches its
|
|
242
|
+
error.
|
|
243
|
+
|
|
244
|
+
| Capability | Methods |
|
|
245
|
+
| --- | --- |
|
|
246
|
+
| `auth.local.accounts` | `create(account, options)`, `findById(userId, options)`, `findByEmail(email, options)`, `updatePassword(userId, passwordRecord, options)`, `updateProfile(userId, { displayName }, options)`, `markEmailVerified(userId, options)` |
|
|
247
|
+
| `auth.local.sessions` | `create(session, options)`, `update(sessionId, { purpose, expiresAt }, { ...options, onlyIf })`, `revoke(sessionId, options)`, `revokeForUser(userId, { ...options, exceptSessionId })`, `deleteExpired(now, options)` |
|
|
248
|
+
| `auth.local.recovery` | `create(token, options)`, `findByTokenHash(tokenHash, options)`, `consume(tokenId, options)`, `consumeForUser(userId, { ...options, idPrefix })` |
|
|
249
|
+
|
|
250
|
+
`markEmailVerified` records that the account proved it owns its email address,
|
|
251
|
+
for example by using an emailed link, in the `email_verified_at` column (run
|
|
252
|
+
`npm run db:migrate`); the first proof is kept, and the account answers it as
|
|
253
|
+
`emailVerifiedAt` (empty until then). `deleteExpired` deletes the sessions that
|
|
254
|
+
expired by `now` and answers how many.
|
|
255
|
+
|
|
256
|
+
A recovery token's kind is the start of its id: local sign-in's password resets
|
|
257
|
+
are `rec_…`, and an app's own kind (a sign-in link, say) takes a prefix of its
|
|
258
|
+
own, such as `magic_…`. `consumeForUser` consumes the user's open tokens of the
|
|
259
|
+
kind `idPrefix` names and no others, so a new sign-in link leaves an open
|
|
260
|
+
password reset usable, and a completed reset leaves sign-in links open.
|
|
261
|
+
`update` with `onlyIf` (`userId`, `purpose`, `revokedAt`) changes the session
|
|
262
|
+
only while it still has those values, compared in the UPDATE itself, and
|
|
263
|
+
answers null, writing nothing, when it does not: promote a sign-in link's
|
|
264
|
+
session with
|
|
265
|
+
`update(sessionId, { purpose: "normal", expiresAt }, { ...options, onlyIf: { userId, purpose: "recovery", revokedAt: null } })`
|
|
266
|
+
and treat null as a refused link. Their types are `AuthLocalAccounts`,
|
|
267
|
+
`AuthLocalSessions` and `AuthLocalRecovery` in
|
|
268
|
+
`@jskit-ai/auth-provider-local-db-core/server/types`.
|
|
207
269
|
|
|
208
270
|
## The first account in production (local auth)
|
|
209
271
|
|
|
@@ -33,6 +33,14 @@ Do not create a console merely to host one ordinary settings page.
|
|
|
33
33
|
The first-owner policy, if the product selects it, is a runtime product rule;
|
|
34
34
|
it is not a hard-coded seed account.
|
|
35
35
|
|
|
36
|
+
Server code that needs the console's settings, such as who owns the console,
|
|
37
|
+
requires `console.settings` and reads them with `getSingleton(options)`, which
|
|
38
|
+
answers `{ id, ownerUserId, settings, createdAt, updatedAt }`; it never reads
|
|
39
|
+
the `console_settings` table. Its type is `ConsoleSettings` in
|
|
40
|
+
`@jskit-ai/console-core/server/types`. To check the console's owner, use
|
|
41
|
+
`isConsoleOwnerUserId(userId, options)` of `console.core`'s
|
|
42
|
+
`services.access`.
|
|
43
|
+
|
|
36
44
|
## Verification
|
|
37
45
|
|
|
38
46
|
Test authorized and forbidden direct entry, first-owner behavior, switching,
|
|
@@ -12,16 +12,17 @@ genuinely chooses a driver at runtime.
|
|
|
12
12
|
For MySQL or MariaDB:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm install @jskit-ai/database-runtime-mysql
|
|
15
|
+
npm install @jskit-ai/database-runtime @jskit-ai/database-runtime-mysql
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
For PostgreSQL:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
npm install @jskit-ai/database-runtime-postgres
|
|
21
|
+
npm install @jskit-ai/database-runtime @jskit-ai/database-runtime-postgres
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
The application declares `@jskit-ai/database-runtime` itself: each driver takes
|
|
25
|
+
it as a peer, so the application holds its one copy, and brings its own Knex
|
|
25
26
|
driver through ordinary npm dependencies. There is no JSKIT install wizard.
|
|
26
27
|
|
|
27
28
|
## Connection environment
|
|
@@ -47,7 +48,7 @@ credentials. JSKIT never guesses or commits them.
|
|
|
47
48
|
|
|
48
49
|
The running database provider registers a `database` health check that runs
|
|
49
50
|
`select 1` within two seconds. The foundations' `GET /api/health` answers `503`
|
|
50
|
-
while it fails; see [Health endpoint](
|
|
51
|
+
while it fails; see [Health endpoint](/guide/app-setup/initial-scaffolding#health-endpoint).
|
|
51
52
|
|
|
52
53
|
## Migration configuration
|
|
53
54
|
|
|
@@ -162,7 +163,7 @@ pending migrations in this order, using the default `migrationsDirectory`:
|
|
|
162
163
|
2. Application `migrations/`.
|
|
163
164
|
3. The `constraints/` subdirectory of each discovered package migration
|
|
164
165
|
directory that has one, in the same order.
|
|
165
|
-
4. Application `migrations/constraints
|
|
166
|
+
4. Application `migrations/constraints/`, when it exists.
|
|
166
167
|
|
|
167
168
|
Knex sorts filenames within each directory, not across the whole graph; see
|
|
168
169
|
its [`sortDirsSeparately` option](https://knexjs.org/guide/migrations.html#migration-api).
|
|
@@ -257,10 +258,10 @@ database allocation, credentials, lifetime, and environment injection.
|
|
|
257
258
|
|
|
258
259
|
## Resource services and custom operations
|
|
259
260
|
|
|
260
|
-
Conventional persisted resources use `
|
|
261
|
-
`
|
|
262
|
-
service, action, permission, JSON API, and route
|
|
263
|
-
product CRUD behavior fixed.
|
|
261
|
+
Conventional persisted resources use `defineTableResource()`,
|
|
262
|
+
`defineCrudContract()` and `defineCrudFeature()` so the framework owns
|
|
263
|
+
repeated table ownership, service, action, permission, JSON API, and route
|
|
264
|
+
mechanics. This does not make product CRUD behavior fixed.
|
|
264
265
|
|
|
265
266
|
- `decorateRepository` adds resource-specific queries, locks, or writes.
|
|
266
267
|
- `decorateService` overrides a standard method or adds domain methods such as
|
|
@@ -268,108 +269,27 @@ product CRUD behavior fixed.
|
|
|
268
269
|
- `operationLifecycle` surrounds a standard operation with `before`, `execute`,
|
|
269
270
|
`after`, and mutation-only `afterCommit` phases. Create, update, and delete
|
|
270
271
|
phases before commit share one repository transaction, and `execute` receives
|
|
271
|
-
`standard(nextInput)` for retaining the normal framework write.
|
|
272
|
+
`standard(nextInput)` for retaining the normal framework write. A deadlock
|
|
273
|
+
runs that transaction again, so `before`, `execute` and `after` can run twice
|
|
274
|
+
and touch only the database; an outside effect (an email, an HTTP call) goes
|
|
275
|
+
in `afterCommit`, which runs once after commit, or in an outbox row.
|
|
272
276
|
- Named `actions` expose non-CRUD service methods through normal input,
|
|
273
277
|
permission, event, and optional HTTP route contracts.
|
|
274
278
|
|
|
275
|
-
|
|
276
|
-
|
|
279
|
+
The feature that owns a table reads and writes it through the handles its
|
|
280
|
+
`owns` entry gives its repository; services, hooks and other features call
|
|
281
|
+
that repository or the owner's API, never the table. External delivery belongs after commit; when it must be durable,
|
|
277
282
|
write an outbox record inside the transaction and deliver it separately.
|
|
278
283
|
A separate Feature is warranted when an operation belongs to another domain,
|
|
279
284
|
not merely because a useful resource has behavior beyond list and save. Write
|
|
280
285
|
it as a [typed feature](/guide/framework/typed-features).
|
|
281
286
|
|
|
282
|
-
## Writes
|
|
287
|
+
## Writes, versions, locks and deadlocks
|
|
283
288
|
|
|
284
|
-
|
|
285
|
-
`
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
does not use the result passes `returning: "minimal"`, which answers
|
|
289
|
-
`{ data: { type, id } }`, or `"none"`, which answers `undefined`; a missing or
|
|
290
|
-
out-of-scope record still patches to `null`. `createDocuments(payloads,
|
|
291
|
-
{ trx, context, returning })` creates several records in order in one
|
|
292
|
-
transaction, the caller's `trx` or one it owns, so they are all created or
|
|
293
|
-
none; its `returning` is `"minimal"` unless the caller asks for `"full"`, and
|
|
294
|
-
its results follow the payloads. Each record is still validated and inserted
|
|
295
|
-
by itself: a batch saves the read-backs and the transactions, not the inserts.
|
|
296
|
-
|
|
297
|
-
## Optimistic locking
|
|
298
|
-
|
|
299
|
-
A CRUD resource whose records carry a version number names it with
|
|
300
|
-
`lockVersionField`, an integer schema field over a non-null integer column
|
|
301
|
-
with a starting value, such as `0`:
|
|
302
|
-
|
|
303
|
-
```js
|
|
304
|
-
defineCrudResource({
|
|
305
|
-
namespace: "contracts",
|
|
306
|
-
tableName: "contracts",
|
|
307
|
-
autofilter: "workspace",
|
|
308
|
-
lockVersionField: "lockVersion",
|
|
309
|
-
schema: {
|
|
310
|
-
// ...
|
|
311
|
-
lockVersion: { type: "integer", min: 0, readOnly: true, column: "lock_version" }
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
The CRUD repository's
|
|
317
|
-
`patchDocumentIfLockVersion(recordId, expectedLockVersion, patch, { trx, context })`
|
|
318
|
-
then applies `patch` only while the record still holds `expectedLockVersion`, a
|
|
319
|
-
non-negative integer, and adds one to the lock version in the same
|
|
320
|
-
transaction: the caller's `trx`, or one it owns. It returns the patched document, which carries the new lock
|
|
321
|
-
version, or `null` when the record does not exist or is outside the caller's
|
|
322
|
-
scope. A record whose lock version has moved on rejects with a 409 coded
|
|
323
|
-
`crud.document.lockVersionConflict` ("This record changed. Reload and try
|
|
324
|
-
again."), and nothing is written. The patch leaves the lock field out; an app
|
|
325
|
-
that wants its own wording for the conflict catches that code. A resource
|
|
326
|
-
without `lockVersionField` cannot call it, and `defineCrudResource()` rejects
|
|
327
|
-
a `lockVersionField` that names no integer schema field.
|
|
328
|
-
|
|
329
|
-
## Row locks
|
|
330
|
-
|
|
331
|
-
A check-then-write that must not race another request (is the drier empty?
|
|
332
|
-
then load it) reads the record with
|
|
333
|
-
`getDocumentById(recordId, query, { trx, context, forUpdate: true })` inside
|
|
334
|
-
`withTransaction`. It locks the record's row in that transaction before
|
|
335
|
-
reading it, so another `forUpdate` read or a write of the record waits until
|
|
336
|
-
the transaction ends, and the read sees the latest version on PostgreSQL; on
|
|
337
|
-
MySQL and MariaDB it does when the lock is the transaction's first read of
|
|
338
|
-
that row. Without a transaction `forUpdate` throws, and a value other than
|
|
339
|
-
`true` or `false` throws too. SQLite runs one transaction at a time on its
|
|
340
|
-
single connection, so there is no row lock to take. The CRUD service's
|
|
341
|
-
`getDocumentById` passes `forUpdate` through.
|
|
342
|
-
|
|
343
|
-
## Deadlock retries
|
|
344
|
-
|
|
345
|
-
When the database aborts a transaction as a deadlock victim (MySQL and MariaDB
|
|
346
|
-
`ER_LOCK_DEADLOCK`, errno 1213; PostgreSQL `40P01`), it rolls the whole
|
|
347
|
-
transaction back, so JSKIT runs the whole transaction callback again, up to
|
|
348
|
-
three more times with a short random pause. The person sees one successful
|
|
349
|
-
request instead of a 500. This applies to `runInTransaction()` and
|
|
350
|
-
`createWithTransaction()` from `@jskit-ai/database-runtime/shared`, to
|
|
351
|
-
`transactionManager.inTransaction()`, to the seed transaction, to every JSON
|
|
352
|
-
REST repository's `withTransaction()` (CRUD, users and workspaces), and to a
|
|
353
|
-
CRUD `createDocument`, `patchDocumentById`, `patchDocumentIfLockVersion` or
|
|
354
|
-
`deleteDocumentById` given no `trx`, which owns its own transaction. A lock wait timeout (errno 1205) is not
|
|
355
|
-
retried.
|
|
356
|
-
|
|
357
|
-
Only the outermost transaction retries. A call given an existing `trx`, or a
|
|
358
|
-
`runInTransaction()` on a transaction, which opens a savepoint, fails at once
|
|
359
|
-
and leaves the retry to the transaction that owns it.
|
|
360
|
-
|
|
361
|
-
A retried callback must be safe to run again: keep HTTP calls, email, file
|
|
362
|
-
uploads and other external effects out of it, before or after the transaction
|
|
363
|
-
or in an outbox, as above. CRUD lifecycle `before`, `execute` and `after`
|
|
364
|
-
phases are inside the transaction; `afterCommit` is not. A transaction whose
|
|
365
|
-
callback cannot be repeated passes `{ retries: 0 }`:
|
|
366
|
-
`runInTransaction(knex, work, { retries: 0 })`.
|
|
367
|
-
|
|
368
|
-
Application code that opens transactions directly uses the same behavior through
|
|
369
|
-
`runInTransaction(knex, work)` instead of `knex.transaction(work)`, or wraps any
|
|
370
|
-
other transaction opener in `retryDeadlocks(() => open(work))`. `isDeadlockError(error)`
|
|
371
|
-
follows `error.cause` and is false for an error that reports
|
|
372
|
-
`transactionOutcome: "committed"`, because that transaction must not run again.
|
|
289
|
+
Writes that skip their read-back (`returning`), optimistic locking
|
|
290
|
+
(`lockVersionField`), row locks and deadlock retries belong to the table
|
|
291
|
+
resource's handle: see [Data access](/guide/framework/data-access), Layers 1
|
|
292
|
+
and 4 and [Transactions](/guide/framework/data-access#transactions).
|
|
373
293
|
|
|
374
294
|
## Verification
|
|
375
295
|
|
|
@@ -456,7 +376,7 @@ Endpoints and batch operations get their own database tests at realistic row
|
|
|
456
376
|
counts that compare their query counts, counted with `countQueries()` from
|
|
457
377
|
`@jskit-ai/database-runtime/server/testDatabases`, or `countRuntimeQueries()`
|
|
458
378
|
for a route of the server the test boots; see the typed features guide's
|
|
459
|
-
[
|
|
379
|
+
[data access tests](/guide/framework/data-access#tests).
|
|
460
380
|
|
|
461
381
|
## Browser test preparation
|
|
462
382
|
|