@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
|
@@ -101,31 +101,33 @@ application's normal verification script, and Playwright.
|
|
|
101
101
|
|
|
102
102
|
## 3. Choose the narrowest server seam that owns each behavior
|
|
103
103
|
|
|
104
|
-
Conventional resources use `
|
|
105
|
-
`
|
|
106
|
-
permission, action, JSON API, and route mechanics. Extend that contract instead
|
|
104
|
+
Conventional resources use `defineTableResource()`, `defineCrudContract()`
|
|
105
|
+
and `defineCrudFeature()` so JSKIT owns the repeated repository,
|
|
106
|
+
service, permission, action, JSON API, and route mechanics. Extend that contract instead
|
|
107
107
|
of copying it.
|
|
108
108
|
|
|
109
109
|
| Product need | Normal owner |
|
|
110
110
|
| --- | --- |
|
|
111
111
|
| Behavior before, around, or after one standard CRUD operation | `operationLifecycle` |
|
|
112
112
|
| A resource-specific domain method or override | `decorateService` |
|
|
113
|
-
| A resource-specific query, lock, or
|
|
113
|
+
| A resource-specific query, lock, or many-row write | `decorateRepository`, on the table's handle |
|
|
114
114
|
| A named non-CRUD command on the resource | a named CRUD `action` backed by the service |
|
|
115
115
|
| A separate aggregate, workflow, or capability domain | `defineFeature()` with named actions |
|
|
116
|
-
|
|
|
116
|
+
| A report the handle cannot express | one `scopedQuery()` repository method, under `// Knex: <kind> - <reason>` ([data access](/guide/framework/data-access#layer-5-reports-the-resource-cannot-express)) |
|
|
117
117
|
|
|
118
118
|
For mutations, lifecycle phases before commit share one repository
|
|
119
119
|
transaction. An `execute` hook that only normalizes input calls
|
|
120
|
-
`standard(nextInput)` rather than reimplementing the write.
|
|
121
|
-
|
|
120
|
+
`standard(nextInput)` rather than reimplementing the write. The feature that
|
|
121
|
+
owns a table reads and writes it through the handles its `owns` entry gives
|
|
122
|
+
its repository; services, hooks and other features call that repository or
|
|
123
|
+
the owner's API, never the table. External work
|
|
122
124
|
runs after commit, or a durable outbox record is written inside the transaction
|
|
123
125
|
and delivered separately.
|
|
124
126
|
|
|
125
127
|
Give commands product names such as `confirm`, `publish`, or `cancel`, with
|
|
126
128
|
explicit input, permissions, routes where needed, audit behavior, events, and
|
|
127
129
|
tests. Do not hide unrelated workflows behind a generic `execute(anything)`
|
|
128
|
-
action, and do not
|
|
130
|
+
action, and do not write Knex merely because SQL is familiar.
|
|
129
131
|
|
|
130
132
|
### Reuse response serializers
|
|
131
133
|
|
|
@@ -148,9 +150,11 @@ upgrading JSKIT and declaring the directly used
|
|
|
148
150
|
```js
|
|
149
151
|
import Fastify from "fastify";
|
|
150
152
|
import SerializerSelector from "@fastify/fast-json-stringify-compiler";
|
|
151
|
-
import { createCachedResponseSerializerFactory } from "@jskit-ai/kernel/server/http";
|
|
153
|
+
import { createCachedResponseSerializerFactory, resolveTrustedProxies } from "@jskit-ai/kernel/server/http";
|
|
152
154
|
|
|
153
155
|
const app = Fastify({
|
|
156
|
+
// TRUSTED_PROXIES: the proxies whose X-Forwarded-For is believed (loopback without it).
|
|
157
|
+
trustProxy: resolveTrustedProxies(runtimeEnv),
|
|
154
158
|
schemaController: {
|
|
155
159
|
compilersFactory: {
|
|
156
160
|
buildSerializer: createCachedResponseSerializerFactory(SerializerSelector())
|
|
@@ -160,84 +164,142 @@ const app = Fastify({
|
|
|
160
164
|
```
|
|
161
165
|
|
|
162
166
|
Keep the application's other Fastify options, including serializer options and
|
|
163
|
-
validator configuration
|
|
167
|
+
validator configuration, but replace an application's own `trustProxy` setting
|
|
168
|
+
with `resolveTrustedProxies(runtimeEnv)` (`runtimeEnv` is the environment the
|
|
169
|
+
server starts with, `process.env` if it has no other), and its environment
|
|
170
|
+
variable with `TRUSTED_PROXIES`; see
|
|
171
|
+
[sign-in protection](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/authentication#sign-in-protection)
|
|
172
|
+
for the setting. This setup wraps the native default serializer builder;
|
|
164
173
|
retain any deliberately chosen custom compiler instead of replacing or wrapping
|
|
165
174
|
it. Native route-level compiler overrides continue to apply. Update the lockfile
|
|
166
175
|
through the application's normal installation flow and restart the backend to
|
|
167
176
|
activate the new setup. Updating packages or starter examples alone does not
|
|
168
177
|
change an existing application's startup source.
|
|
169
178
|
|
|
170
|
-
###
|
|
179
|
+
### Data access on table resources (json-rest-api 3)
|
|
171
180
|
|
|
172
|
-
Applications using `@jskit-ai/
|
|
181
|
+
Applications using `@jskit-ai/resource-runtime` require Node.js 24 or newer.
|
|
173
182
|
JSKIT's repository and application default is Node.js 26. Upgrade dependencies,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
The standard `defineCrudJsonApiFeature()` API keeps `queryDocuments`,
|
|
178
|
-
`getDocumentById`, `createDocument`, `patchDocumentById`, and
|
|
179
|
-
`deleteDocumentById`, with `{ trx, context }` repository/service options.
|
|
180
|
-
Read, create, and patch services still return tagged JSON:API documents.
|
|
181
|
-
Application-owned hosts use `JsonRestApi` from `json-rest-api`; the JSKIT host
|
|
182
|
-
already owns that setup.
|
|
183
|
-
|
|
184
|
-
For direct resource calls, replace `inputRecord` with exactly one of `data`
|
|
185
|
-
(plain values) or `document` (a JSON:API document). `format` selects output
|
|
186
|
-
independently: `"plain"` or `"jsonapi"`. Replace `simplified` and
|
|
187
|
-
`returnFullRecord` with `format` and `returning: "full" | "minimal" | "none"`.
|
|
188
|
-
The JSKIT host defaults to plain output and full write responses; standard
|
|
189
|
-
document repositories explicitly request JSON:API output and full create/patch
|
|
190
|
-
responses. A plain collection is still `{ data: [...], meta?, links? }`; use
|
|
191
|
-
`extractJsonRestCollectionRows()` when a custom repository needs its rows.
|
|
192
|
-
A query without a page returns only the resource's first 20 rows; a
|
|
193
|
-
server-side read that needs every row uses `queryAllJsonRestCollectionRows()`
|
|
194
|
-
([every row](/guide/framework/typed-features#every-row)).
|
|
183
|
+
resource calls and hooks together; rebuild native database dependencies after
|
|
184
|
+
changing Node.
|
|
195
185
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
data: { title: "Revised title" },
|
|
200
|
-
format: "jsonapi",
|
|
201
|
-
returning: "full",
|
|
202
|
-
transaction: trx
|
|
203
|
-
}, createJsonRestContext(context));
|
|
204
|
-
```
|
|
186
|
+
Every table has one table resource and one owner feature, and every read and
|
|
187
|
+
write goes through that table's handle
|
|
188
|
+
([data access](/guide/framework/data-access)). Rename as you go:
|
|
205
189
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
`
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
`
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
`
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
`
|
|
225
|
-
|
|
226
|
-
`
|
|
227
|
-
`
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
190
|
+
| Before | Now |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| `defineCrudResource()` | `defineTableResource()` plus `defineCrudContract()` |
|
|
193
|
+
| `defineCrudJsonApiFeature()`, `createCrudResourceRuntime()` | `defineCrudFeature({ crud })` |
|
|
194
|
+
| the CRUD runtime's `listByIds()`, `listByForeignIds()` | `queryAll({ filters: { [key]: ids } })` on a filter declared `{ type: "array", filterOperator: "in" }`; a page's children with `createHooksToCollectChildren()` |
|
|
195
|
+
| the CRUD runtime's lookup hydration | `include` on the CRUD list or view |
|
|
196
|
+
| the CRUD runtime's `virtualFields` | `queryFields` in the `owns` entry, or `decorateService` for a value SQL cannot compute |
|
|
197
|
+
| the CRUD runtime's `createdAt`/`updatedAt` stamping | every handle write stamps them; only a system context sets its own |
|
|
198
|
+
| the CRUD runtime's output validation | the table resource's schema, whose types json-rest-api reads each field as |
|
|
199
|
+
| `defineCrudFeature`'s `ownershipFilter`, `searchSchema` | `visibility`; an `owns` entry |
|
|
200
|
+
| a lifecycle hook's `trx`; a decorator's or CRUD capability's `resource` | `options`; `crud` |
|
|
201
|
+
| `decorateRepository`'s `database`, `http`, `jsonRestApi` | its `repository` and `resources`, each owned table's handle |
|
|
202
|
+
| `@jskit-ai/crud-core/server/fieldAccess`, `/server/lookups` | removed (nothing called them); restrict fields in the CRUD contract and its `decorateService` |
|
|
203
|
+
| `runtime.json-rest-api`, `addResourceIfMissing()`, `createJsonRestResourceScopeOptions()` | `defineFeature({ owns })`; a row policy, search schema or query fields go in the `owns` entry |
|
|
204
|
+
| `queryDocuments()`, `getDocumentById()` | `query({ page: { size } })`, `get(id)` |
|
|
205
|
+
| `queryAllJsonRestCollectionRows()`, `queryEveryDocument()` | `queryAll(params)` |
|
|
206
|
+
| `createDocument()`, `createDocuments()` | `create()`, `createMany()` |
|
|
207
|
+
| `patchDocumentById()`, `patchDocumentIfLockVersion()` | `patch(id, changes, options)`, `patch(id, changes, { ...options, lockVersion })` |
|
|
208
|
+
| `deleteDocumentById()` | `delete(id)` |
|
|
209
|
+
| `getDocumentById(…, { forUpdate: true })`, `applyForUpdate()`, `lockScopedRecordId()` | `lock(id)` inside `withTransaction` |
|
|
210
|
+
| `runInTransaction()`, `createWithTransaction()`, `joinOrOpenTransaction()`, `resolveTransaction()`, `resolveQueryOptions()`, `resolveRepoClient()` | `withTransaction(options, (txOptions) => …)` |
|
|
211
|
+
| `returning: "full" \| "minimal"` | `returning: "record" \| "id"` (`"none"` is unchanged) |
|
|
212
|
+
| a hand-built `visibilityContext` or scope values, `applyVisibility()`, `applyVisibilityOwners()` | the caller's context, or `systemContext(reason).forWorkspace(id)` |
|
|
213
|
+
| a field's `default:` | `defaultTo:` (`default` is refused, since json-rest-api never read it) |
|
|
214
|
+
| a `createdAt` or `updatedAt` default of now | nothing: drop it, since every handle write stamps them |
|
|
215
|
+
| a CRUD contract that showed every column | a contract shows only its `fields`, so list every field the API showed |
|
|
216
|
+
| reading every row | `queryAll`, which refuses above 10,000 rows, so page with `query()` |
|
|
217
|
+
| an app's own wrappers around old calls | the handle method they wrapped, then delete the wrapper |
|
|
218
|
+
|
|
219
|
+
Handles answer records, `{ id, ...fields }`, with a belongs-to field as the
|
|
220
|
+
related id; the CRUD service alone answers JSON:API documents. Every call takes
|
|
221
|
+
`options` last: the caller's context, and inside a transaction the `txOptions`
|
|
222
|
+
`withTransaction` handed its work. A call without a context throws on a table
|
|
223
|
+
that is not public.
|
|
224
|
+
|
|
225
|
+
json-rest-api 3 changes what a failed write means: any failed write inside a
|
|
226
|
+
managed transaction fails the whole transaction. "Insert, catch the duplicate,
|
|
227
|
+
then read" inside a transaction no longer works; use
|
|
228
|
+
`createMany(rows, { ...options, onConflict: { keys, action: "ignore" } })` and then read,
|
|
229
|
+
or read first. A `get`, `patch`, `delete` or `lock` of a missing or hidden
|
|
230
|
+
record throws json-rest-api's not-found error (a 404); with `missing: "skip"`
|
|
231
|
+
it answers `null`, without failing the caller's transaction. Upsert rows carry no
|
|
232
|
+
`id`, never repeat their conflict keys, and name the ownership field among
|
|
233
|
+
those keys; only a table with no owners and no row policy may key the upsert on
|
|
234
|
+
the id alone, `onConflict: { keys: ["id"] }`, with an id on every row. `patchWhere` and `deleteWhere` need filters, or `all: true`.
|
|
231
235
|
|
|
232
236
|
Preserve `error.transactionOutcome` and its cause when translating failures:
|
|
233
237
|
`none` means no transaction was acquired or accepted, `pending` leaves
|
|
234
238
|
completion to the owner, `rolledBack` confirms rollback, `committed` confirms
|
|
235
239
|
commit, and `unknown` means the outcome is unresolved. These describe database
|
|
236
|
-
transaction evidence, not external effects. A failed
|
|
237
|
-
`
|
|
238
|
-
not
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
transaction evidence, not external effects. A failed `afterCommit` hook raises
|
|
241
|
+
`RestApiWriteError` with outcome `committed` and does not undo the write. Do
|
|
242
|
+
not turn pending, committed, or unknown write failures into successful
|
|
243
|
+
missing-record results, or automatically replay a committed or unresolved
|
|
244
|
+
write.
|
|
245
|
+
|
|
246
|
+
### Moving hand-written SQL behind resources
|
|
247
|
+
|
|
248
|
+
An application with Knex in its repositories moves it behind table resources
|
|
249
|
+
one table at a time:
|
|
250
|
+
|
|
251
|
+
1. **Inventory.** List every statement that reads or writes a table: the
|
|
252
|
+
repository methods, raw queries, migrations' seed data, jobs and scripts.
|
|
253
|
+
`jskit check` and the data-access lint rules (`jskit/no-direct-knex`,
|
|
254
|
+
`jskit/no-hand-scope`) find most of them.
|
|
255
|
+
2. **One owner per table.** Pick the feature that owns each table and give the
|
|
256
|
+
table one table resource with its `ownership`. Two features writing one
|
|
257
|
+
table means one of them asks the other.
|
|
258
|
+
3. **Owner APIs.** Code outside the owner calls the owner's service or
|
|
259
|
+
repository capability. JSKIT's own tables already work this way: users
|
|
260
|
+
(`users.profiles`, `users.settings`), workspaces, local sign-in and console
|
|
261
|
+
settings.
|
|
262
|
+
4. **Map each statement to a layer**
|
|
263
|
+
([data access](/guide/framework/data-access)): one record or page →
|
|
264
|
+
Layer 1; screens over HTTP → Layer 2; a loop of writes → a many-row write;
|
|
265
|
+
a `forUpdate` read → a lock; totals and reports → `aggregate`, a window
|
|
266
|
+
include, or one `scopedQuery()` method; work over every workspace → a job
|
|
267
|
+
with `systemScope`.
|
|
268
|
+
5. **Two tests per path.** `expectScoped` proves the path never reads or
|
|
269
|
+
changes a second owner's rows; `expectFlatQueries` proves its statements do
|
|
270
|
+
not grow with its rows. Both come from `@jskit-ai/resource-runtime/test`.
|
|
271
|
+
|
|
272
|
+
Keep each move behaviour-preserving
|
|
273
|
+
([refactors](/guide/framework/sanity-checks#refactors)).
|
|
274
|
+
|
|
275
|
+
A table keyed by several columns, or by its owner's id, gets an integer `id`
|
|
276
|
+
first ([Layer 1](/guide/framework/data-access#layer-1-the-table-resource)).
|
|
277
|
+
One new migration does it on MySQL, MariaDB and PostgreSQL: the key becomes a
|
|
278
|
+
unique index before the primary key goes, so a foreign key on its first column
|
|
279
|
+
keeps an index, and adding the `id` numbers the existing rows. `down` undoes it
|
|
280
|
+
in reverse.
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
const KEY = ["workspace_id", "day"];
|
|
284
|
+
|
|
285
|
+
export async function up(knex) {
|
|
286
|
+
await knex.schema.alterTable("day_slots", (table) => table.unique(KEY, { indexName: "day_slots_key" }));
|
|
287
|
+
await knex.schema.alterTable("day_slots", (table) => table.dropPrimary());
|
|
288
|
+
await knex.schema.alterTable("day_slots", (table) => table.increments("id"));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function down(knex) {
|
|
292
|
+
await knex.schema.alterTable("day_slots", (table) => table.dropColumn("id"));
|
|
293
|
+
await knex.schema.alterTable("day_slots", (table) => table.primary(KEY));
|
|
294
|
+
await knex.schema.alterTable("day_slots", (table) => table.dropUnique(KEY, "day_slots_key"));
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
MySQL and MariaDB commit each statement on its own, so a run that fails
|
|
299
|
+
part-way resumes as the [partial-failure
|
|
300
|
+
rules](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/database-layer#directory-order-comes-before-filenames)
|
|
301
|
+
say. Code that wrote by the old key now upserts on it, with the ownership
|
|
302
|
+
column among the keys.
|
|
241
303
|
|
|
242
304
|
### Workspace invitation transaction compatibility (workspaces-core 0.1.198)
|
|
243
305
|
|
|
@@ -110,14 +110,25 @@ The package migrations create and own these tables:
|
|
|
110
110
|
| `workspace_invites` | `workspace_id`, `email`, `role_sid`, `status`, `token_hash`, `invited_by_user_id`, `expires_at`, `accepted_at`, `revoked_at` |
|
|
111
111
|
|
|
112
112
|
Application records that belong to a workspace use a non-null `workspace_id`
|
|
113
|
-
foreign key to `workspaces.id`. Read and change these tables through
|
|
114
|
-
`workspaces.
|
|
113
|
+
foreign key to `workspaces.id`. Read and change these tables through their
|
|
114
|
+
owners' capabilities (`workspaces`, `workspaces.memberships` and
|
|
115
|
+
`workspaces.core`, below), not directly.
|
|
115
116
|
|
|
116
117
|
## Server composition
|
|
117
118
|
|
|
118
119
|
Use `workspaces/workspace-server` for role vocabulary, creation policy,
|
|
119
120
|
invitations, settings, and app-owned invitation email rendering.
|
|
120
121
|
|
|
122
|
+
Two server settings are required, and the server refuses to start without
|
|
123
|
+
them: whether new workspaces accept invitations, and how long an invitation
|
|
124
|
+
lasts.
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
// config/server.js
|
|
128
|
+
config.workspaceSettings = { defaults: { invitesEnabled: true } };
|
|
129
|
+
config.workspaceMembers = { defaults: { inviteExpiresInMs: 7 * 24 * 60 * 60 * 1000 } };
|
|
130
|
+
```
|
|
131
|
+
|
|
121
132
|
Workspace scope must reach repository queries. Parsing a workspace slug or
|
|
122
133
|
hiding an admin link is not authorization. Membership and role checks happen
|
|
123
134
|
before an operation discloses records or record existence.
|
|
@@ -125,14 +136,12 @@ before an operation discloses records or record existence.
|
|
|
125
136
|
### Workspace-scoped features and routes
|
|
126
137
|
|
|
127
138
|
For a standard CRUD resource owned by a workspace, give its table the
|
|
128
|
-
`workspace_id` column, set
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
(`@jskit-ai/json-rest-api-core/server/jsonRestApiHost`), which throws when the
|
|
135
|
-
context names two workspaces.
|
|
139
|
+
`workspace_id` column, set its table resource's `ownership: "workspace"`, and bind
|
|
140
|
+
`defineCrudFeature()` with `scope: WORKSPACE_CRUD_SCOPE` (from
|
|
141
|
+
`@jskit-ai/workspaces-core/server/support/workspaceCrud`) on a workspace
|
|
142
|
+
surface. JSKIT then filters `workspaceId` by the workspace in the route. A
|
|
143
|
+
repository method you add passes the call's `options` on to the table's handle,
|
|
144
|
+
which scopes every read and write to the context's workspace.
|
|
136
145
|
`workspace_user` also filters by the signed-in user. See the CRUD operations
|
|
137
146
|
guide.
|
|
138
147
|
|
|
@@ -175,9 +184,29 @@ capability provides:
|
|
|
175
184
|
| `workspaces.services.members` | Members, roles and invitations |
|
|
176
185
|
| `workspaces.services.pendingInvitations` | Invitation resolution, redemption and acceptance participants |
|
|
177
186
|
| `workspaces.services.settings` | Workspace settings |
|
|
178
|
-
| `workspaces.repositories.
|
|
187
|
+
| `workspaces.repositories.workspaceSettings`, `.workspaceInvites` | Settings and invitation records inside the application's own transactions |
|
|
179
188
|
| `workspaces.tenancyProfile` | The resolved tenancy mode and workspace policy |
|
|
180
189
|
|
|
190
|
+
The workspaces and their memberships are their own capabilities, for code that
|
|
191
|
+
reads or changes them inside its own transactions, typed `Workspaces` and
|
|
192
|
+
`WorkspacesMemberships` in `@jskit-ai/workspaces-core/server/types`. Every
|
|
193
|
+
method takes the call's `options` last and joins the transaction of the
|
|
194
|
+
`txOptions` it is given.
|
|
195
|
+
|
|
196
|
+
| Capability | Methods |
|
|
197
|
+
| --- | --- |
|
|
198
|
+
| `workspaces` | `findById(id, options)`, `findBySlug(slug, options)` (a deleted workspace is not found, though its slug stays taken), `listSlugsIn`, `findPersonalByOwnerUserId`, `listForUserId(userId, options)`, `insert`, `updateById`, `lock(workspaceId, options)` |
|
|
199
|
+
| `workspaces.memberships` | `findByWorkspaceIdAndUserId`, `ensureOwnerMembership`, `upsertMembership(workspaceId, userId, patch, options)`, `addMember({ workspaceId, userId, roleSid }, options)`, `activeMemberIds(workspaceId, options)`, `listActiveByWorkspaceId`, `findActiveByWorkspaceIdAndUserId`, `listActiveWorkspaceIdsByUserId`, `listActiveWorkspaceIdsByUserIds` |
|
|
200
|
+
|
|
201
|
+
`addMember` makes the user an active member with the role (`member` when none
|
|
202
|
+
is given), adding the membership or reactivating it. `activeMemberIds` answers
|
|
203
|
+
the active members' user ids at the same cost however many members there
|
|
204
|
+
are, without including their profiles. `lock` takes
|
|
205
|
+
the workspace's row lock until the caller's transaction ends, so work that must
|
|
206
|
+
not interleave for one workspace takes turns; it runs only inside a
|
|
207
|
+
transaction, given its `txOptions`, and answers `{ id }`, or `null` when there
|
|
208
|
+
is no such workspace.
|
|
209
|
+
|
|
181
210
|
To check one person's membership, for example a notification recipient, call
|
|
182
211
|
`workspaces.services.members.getMember(workspace, userId, options)`. It answers
|
|
183
212
|
that member's `listMembers` entry, or `null` for a removed or unknown member,
|
|
@@ -225,8 +254,8 @@ workspace routes, `workspaces-web` switches Vuetify to the `workspace-light` or
|
|
|
225
254
|
colours. Other routes use the application's themes.
|
|
226
255
|
|
|
227
256
|
New workspaces start from JSKIT's blue palette unless `config/server.js` sets
|
|
228
|
-
the application's own
|
|
229
|
-
stops the server at startup:
|
|
257
|
+
the application's own beside the required `invitesEnabled`. Missing colours
|
|
258
|
+
keep JSKIT's values; an invalid colour stops the server at startup:
|
|
230
259
|
|
|
231
260
|
```js
|
|
232
261
|
// config/server.js
|
|
@@ -301,11 +330,11 @@ authorization and role checks. Do not pass arbitrary HTTP input as options.
|
|
|
301
330
|
|
|
302
331
|
| Capability method | Contract |
|
|
303
332
|
| --- | --- |
|
|
304
|
-
| `workspaces.repositories.workspaceInvites.withTransaction(
|
|
305
|
-
| `workspaces.
|
|
306
|
-
| `workspaces.services.members.prepareInvite(workspace, actor, payload,
|
|
307
|
-
| `workspaces.services.members.sendInvite(workspace, actor, prepared, options)` |
|
|
308
|
-
| `workspaces.services.members.createInvite(workspace, actor, payload, options)` | Owns preparation and commit, then calls `sendInvite`, and answers with `sendInvite`'s result as JSON:API data (`returnJsonApiData`, read it from `.value`), as the invite action sends it.
|
|
333
|
+
| `workspaces.repositories.workspaceInvites.withTransaction(options, (txOptions) => …)` | The invitations handle's `withTransaction`: joins the caller's transaction or opens one and hands `work` the `txOptions` to pass on; awaits commit and completion hooks before resolving. Runs `work` again when the database aborts it as a deadlock victim (see [data access](https://mobily-enterprises.github.io/jskit-ai/guide/framework/data-access#transactions)). `{ ...options, outermost: true }` refuses a call that already runs inside one. |
|
|
334
|
+
| `workspaces.lock(workspaceId, txOptions)` (capability `workspaces`) | Locks the workspace before application receipt/draft rows when composing preparation. Requires an active transaction. |
|
|
335
|
+
| `workspaces.services.members.prepareInvite(workspace, actor, payload, txOptions)` | Runs only inside a transaction, given its `txOptions`. Validates the invitation role, replaces pending invitations for that workspace/email, and returns `{ createdInviteId, inviteTokenPreview }`. Never sends. |
|
|
336
|
+
| `workspaces.services.members.sendInvite(workspace, actor, prepared, options)` | Refuses to run inside a transaction: it delivers after the preparation's transaction commits. Reloads the committed pending invitation and workspace display data, validates expiry and token correspondence, and sends that invitation without replacement. |
|
|
337
|
+
| `workspaces.services.members.createInvite(workspace, actor, payload, options)` | Owns preparation and commit, then calls `sendInvite`, and answers with `sendInvite`'s result as JSON:API data (`returnJsonApiData`, read it from `.value`), as the invite action sends it. Refuses to run inside a transaction. |
|
|
309
338
|
| `workspaces.invite-acceptance-participant` (optional capability) | One application function receiving `({ invite, user }, options)` inside the acceptance transaction. Without it, acceptance activates the membership only. |
|
|
310
339
|
|
|
311
340
|
`sendInvite` accepts `{ createdInviteId, inviteTokenPreview }` from preparation or
|
|
@@ -344,21 +373,20 @@ setup: ({ reviewedPeople, people, scopes, audit }) => ({
|
|
|
344
373
|
})
|
|
345
374
|
|
|
346
375
|
// Inside an authorized application command, in a feature that requires
|
|
347
|
-
// workspaces: "workspaces.core":
|
|
376
|
+
// workspaces: "workspaces.core" and workspaceRecords: "workspaces":
|
|
348
377
|
const { members } = workspaces.services;
|
|
349
378
|
const invites = workspaces.repositories.workspaceInvites;
|
|
350
|
-
const prepared = await invites.withTransaction(async (
|
|
351
|
-
const options = { trx, context };
|
|
379
|
+
const prepared = await invites.withTransaction({ context }, async (txOptions) => {
|
|
352
380
|
// Match acceptance's order: workspace, then application draft/receipt rows.
|
|
353
|
-
await
|
|
354
|
-
const reviewed = await reviewedPeople.lockAuthorizedDraft(draftId, workspace.id, actor,
|
|
355
|
-
const receipt = await reviewedPeople.findMatchingReceipt(reviewed, requestId,
|
|
381
|
+
await workspaceRecords.lock(workspace.id, txOptions);
|
|
382
|
+
const reviewed = await reviewedPeople.lockAuthorizedDraft(draftId, workspace.id, actor, txOptions);
|
|
383
|
+
const receipt = await reviewedPeople.findMatchingReceipt(reviewed, requestId, txOptions);
|
|
356
384
|
if (receipt) return { createdInviteId: receipt.inviteId };
|
|
357
385
|
const created = await members.prepareInvite(workspace, actor, {
|
|
358
386
|
email: reviewed.email,
|
|
359
387
|
roleSid: reviewed.roleSid
|
|
360
|
-
},
|
|
361
|
-
await reviewedPeople.associateInvitationAndSaveReceipt(reviewed, requestId, created.createdInviteId,
|
|
388
|
+
}, txOptions);
|
|
389
|
+
await reviewedPeople.associateInvitationAndSaveReceipt(reviewed, requestId, created.createdInviteId, txOptions);
|
|
362
390
|
return { createdInviteId: created.createdInviteId };
|
|
363
391
|
});
|
|
364
392
|
|
|
@@ -401,8 +429,8 @@ exactly once. Revocation after sending starts cannot recall a message.
|
|
|
401
429
|
Register one callback during feature setup to call the required application
|
|
402
430
|
services. Non-functions throw `TypeError`; duplicate registration throws, and
|
|
403
431
|
registration closes when acceptance/refusal first begins. The callback's invite,
|
|
404
|
-
user and options are shallow frozen. JSKIT owns the transaction and passes
|
|
405
|
-
`
|
|
432
|
+
user and options are shallow frozen. JSKIT owns the transaction and passes the
|
|
433
|
+
callback the `txOptions` that carry it and the request's `context`.
|
|
406
434
|
|
|
407
435
|
Acceptance checks authentication, recipient email, pending state and expiry under
|
|
408
436
|
workspace/invitation locks. Before calling the participant, it activates membership
|
|
@@ -420,8 +448,8 @@ callback must not commit, roll back, create transactions/savepoints or perform
|
|
|
420
448
|
external effects. Participant and later invitation-status failures roll back
|
|
421
449
|
membership (including its previous role/status), person/training links, scopes,
|
|
422
450
|
audit and accepted status. Invalid invitations and refusals do not call the
|
|
423
|
-
participant. Acceptance
|
|
424
|
-
|
|
451
|
+
participant. Acceptance and refusal refuse to run inside the caller's
|
|
452
|
+
transaction; revocation can join one or open its own.
|
|
425
453
|
|
|
426
454
|
Acceptance/refusal return 401 for unauthenticated access, 403 for a recipient
|
|
427
455
|
email mismatch and 404 for a missing or handled invitation. A matching recipient's
|
|
@@ -435,7 +463,7 @@ Preserve `error.transactionOutcome` and `error.cause`: `rolledBack` confirms
|
|
|
435
463
|
rollback, `committed` can accompany a failed completion hook, and `unknown`
|
|
436
464
|
requires resolving the outcome before retrying. Errors after commit cannot undo
|
|
437
465
|
persistence. See
|
|
438
|
-
[managed transaction guidance](./existing-application-migration.md#json-rest-
|
|
466
|
+
[managed transaction guidance](./existing-application-migration.md#data-access-on-table-resources-json-rest-api-3).
|
|
439
467
|
|
|
440
468
|
Invitation mutations lock workspace before invitation rows. A losing acceptance,
|
|
441
469
|
refusal, revocation or replacement cannot overwrite accepted/revoked state.
|
|
@@ -494,7 +522,41 @@ Workspace surfaces put the slug in their pages root, such as
|
|
|
494
522
|
`w/[workspaceSlug]/admin` for administration. Keep the default surface
|
|
495
523
|
(`surfaceDefaultId`, usually `home`) free of a workspace requirement: `/`,
|
|
496
524
|
invitation links and other unscoped pages belong to it. With
|
|
497
|
-
`webRootAllowed: "no"`, `/` opens that surface's root.
|
|
525
|
+
`webRootAllowed: "no"`, `/` opens that surface's root. The foundation
|
|
526
|
+
defines `home` and its `public` access policy, and the `auth/auth-surface`
|
|
527
|
+
pattern the `auth` surface; the workspace surfaces add a policy and two
|
|
528
|
+
definitions:
|
|
529
|
+
|
|
530
|
+
```js
|
|
531
|
+
// config/surfaceAccessPolicies.js
|
|
532
|
+
surfaceAccessPolicies.workspace_member = { requireAuth: true, requireWorkspaceMembership: true };
|
|
533
|
+
|
|
534
|
+
// config/public.js
|
|
535
|
+
config.surfaceDefinitions.app = {
|
|
536
|
+
id: "app",
|
|
537
|
+
label: "App",
|
|
538
|
+
pagesRoot: "w/[workspaceSlug]",
|
|
539
|
+
requiresAuth: true,
|
|
540
|
+
requiresWorkspace: true,
|
|
541
|
+
accessPolicyId: "workspace_member"
|
|
542
|
+
};
|
|
543
|
+
config.surfaceDefinitions.admin = {
|
|
544
|
+
id: "admin",
|
|
545
|
+
label: "Admin",
|
|
546
|
+
pagesRoot: "w/[workspaceSlug]/admin",
|
|
547
|
+
requiresAuth: true,
|
|
548
|
+
requiresWorkspace: true,
|
|
549
|
+
accessPolicyId: "workspace_member"
|
|
550
|
+
};
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
An access policy takes `requireAuth`, `requireWorkspaceMembership`,
|
|
554
|
+
`requireAnyPermissions` and `requireAllPermissions` (permission lists), and
|
|
555
|
+
`requireFlagsAny` and `requireFlagsAll` (flags the server's bootstrap sets in
|
|
556
|
+
`surfaceAccess`). Without `requireAuth` or `requireWorkspaceMembership` it
|
|
557
|
+
takes the surface's `requiresAuth` or `requiresWorkspace`. The browser applies
|
|
558
|
+
the whole policy; the server reads only `requireWorkspaceMembership: false`
|
|
559
|
+
from it, and a route's own `auth` and `permission` authorize it there.
|
|
498
560
|
|
|
499
561
|
`workspaces-web` remembers the last workspace each person opened in this
|
|
500
562
|
browser. The **Your workspaces** page, `WorkspacesClientElement` from
|
|
@@ -58,7 +58,7 @@ questions and must not be copied into committed source.
|
|
|
58
58
|
## Build the first product operation
|
|
59
59
|
|
|
60
60
|
Use the narrow relevant pattern and public APIs. For database CRUD, author an
|
|
61
|
-
immutable migration and
|
|
61
|
+
immutable migration, its table resource and, for screens, a CRUD contract; use framework APIs
|
|
62
62
|
for standard mechanics and app code for product-specific behavior. Do not
|
|
63
63
|
invoke a source generator or reconstruct one as prompt options.
|
|
64
64
|
|
|
@@ -9,7 +9,7 @@ hub shows its sign-in page, the app receives an ID token, and the app keeps its
|
|
|
9
9
|
own session.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install @jskit-ai/oidc-issuer-core @jskit-ai/database-runtime-mysql
|
|
12
|
+
npm install @jskit-ai/oidc-issuer-core @jskit-ai/database-runtime @jskit-ai/database-runtime-mysql
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Start from the "JSKIT · Accounts hub" template (seed-jskit branch
|
|
@@ -62,7 +62,10 @@ npm run jskit:update
|
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
The updater rejects unknown JSKIT packages and private JSKIT overrides during
|
|
65
|
-
preflight, before changing any manifest.
|
|
65
|
+
preflight, before changing any manifest. It removes a package a release retired
|
|
66
|
+
(`@jskit-ai/json-rest-api-core`, replaced by `@jskit-ai/resource-runtime`) and
|
|
67
|
+
says what replaced it; while the application's code still imports a retired
|
|
68
|
+
package, it refuses and names the import to use instead.
|
|
66
69
|
|
|
67
70
|
`jskit:update` reads the latest published catalog, discovers the root manifest
|
|
68
71
|
and npm workspaces, and preflights every declared `@jskit-ai/*` package. It then
|
|
@@ -78,6 +81,15 @@ installation, or validation fails, it restores the previous manifests,
|
|
|
78
81
|
lockfile, and installed tree before reporting the failure. Resolve the reported
|
|
79
82
|
registry or real peer problem and rerun `npm run jskit:update`.
|
|
80
83
|
|
|
84
|
+
When the install brings package migrations the application has not had, the
|
|
85
|
+
updater lists each one (`<package>: <file>`) and says to run
|
|
86
|
+
`npm run db:migrate`. Run it, on every database the application uses, before
|
|
87
|
+
the new version serves requests: a package's new version reads what its
|
|
88
|
+
migration creates, so until then those reads fail, such as every local sign-in
|
|
89
|
+
with an unknown `email_verified_at` column. Read each package's CHANGELOG
|
|
90
|
+
**Upgrading** section too, for the changes the application itself must make,
|
|
91
|
+
such as a feature to register.
|
|
92
|
+
|
|
81
93
|
## Separately composed packages
|
|
82
94
|
|
|
83
95
|
Pass each additional manifest explicitly to both commands:
|
|
@@ -210,8 +222,10 @@ application:
|
|
|
210
222
|
`defineConfig(browserTestConfig({ testDir: "./tests/e2e", prepare: "tests/e2e/databases.js" }))`
|
|
211
223
|
from `@jskit-ai/jskit-catalog/browser-tests`, with `server`, `build`, `env`
|
|
212
224
|
or `port` only where its own differ from `node ./bin/server.js`,
|
|
213
|
-
`npm run build` and 4173,
|
|
214
|
-
need more than 20 minutes
|
|
225
|
+
`npm run build` and 4173, `globalTimeout` only for a suite measured to
|
|
226
|
+
need more than 20 minutes, and `actionTimeout` or `navigationTimeout` only
|
|
227
|
+
for an action or page measured to need more than 15 or 30 seconds (a stale
|
|
228
|
+
locator should fail in seconds, not minutes). Settings a config adds (projects, a globalSetup
|
|
215
229
|
that proves the server's identity, `timezoneId`) spread over the result:
|
|
216
230
|
`defineConfig({ ...base, projects, use: { ...base.use, timezoneId } })`. A
|
|
217
231
|
second local Playwright config and any `npm run develop` or `vite`
|
|
@@ -44,11 +44,14 @@ in-page banner that moves the form.
|
|
|
44
44
|
|
|
45
45
|
Use `users/user-administration-server` only when the product needs global or
|
|
46
46
|
workspace-scoped administration beyond self-service settings. It demonstrates
|
|
47
|
-
an app-owned feature package built with `
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
an app-owned feature package built with `defineCrudFeature()` on a CRUD
|
|
48
|
+
contract over users-core's `userProfileTableResource`, while keeping profile
|
|
49
|
+
persistence, and the `users` table's one table resource, in `users-core`: the
|
|
50
|
+
feature names `ownerRepository: "users.profiles"` and serves the table through
|
|
51
|
+
users-core's repository instead of owning it.
|
|
52
|
+
|
|
53
|
+
Do not expose password hashes, provider credentials, or session data through
|
|
54
|
+
the CRUD contract. Apply permission and workspace scope before repository access so
|
|
52
55
|
record existence is not leaked.
|
|
53
56
|
|
|
54
57
|
## Verification
|
|
@@ -101,7 +101,7 @@ normal order is:
|
|
|
101
101
|
|
|
102
102
|
1. confirm the product resource, ownership, operations, and fields
|
|
103
103
|
2. author a migration as normal application source
|
|
104
|
-
3.
|
|
104
|
+
3. define the table resource (`defineTableResource`) and, for screens, its CRUD contract (`defineCrudContract`)
|
|
105
105
|
4. use framework APIs for standard repository/service/action/route mechanics
|
|
106
106
|
5. author product-specific screens from the relevant UI pattern
|
|
107
107
|
6. run migrations and direct verification
|