@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
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
<!-- Generated from `packages/agent-docs/site/guide/app-setup/database-layer.md`. -->
|
|
2
|
+
|
|
3
|
+
# Database layer
|
|
4
|
+
|
|
5
|
+
JSKIT applications use Knex through one deliberately selected database driver.
|
|
6
|
+
Install either `@jskit-ai/database-runtime-mysql` or
|
|
7
|
+
`@jskit-ai/database-runtime-postgres`; do not install both unless the product
|
|
8
|
+
genuinely chooses a driver at runtime.
|
|
9
|
+
|
|
10
|
+
## Install the selected driver
|
|
11
|
+
|
|
12
|
+
For MySQL or MariaDB:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @jskit-ai/database-runtime @jskit-ai/database-runtime-mysql
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For PostgreSQL:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @jskit-ai/database-runtime @jskit-ai/database-runtime-postgres
|
|
22
|
+
```
|
|
23
|
+
|
|
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
|
|
26
|
+
driver through ordinary npm dependencies. There is no JSKIT install wizard.
|
|
27
|
+
|
|
28
|
+
## Connection environment
|
|
29
|
+
|
|
30
|
+
Keep credentials outside Git. Supply either `DATABASE_URL` or the individual
|
|
31
|
+
values:
|
|
32
|
+
|
|
33
|
+
```dotenv
|
|
34
|
+
DB_HOST=127.0.0.1
|
|
35
|
+
DB_PORT=3306
|
|
36
|
+
DB_NAME=exampleapp
|
|
37
|
+
DB_USER=exampleapp
|
|
38
|
+
DB_PASSWORD=secret
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use port `5432` for PostgreSQL. A single-driver app fixes its dialect in
|
|
42
|
+
`knexfile.js`, so it does not need `DB_CLIENT`. The running database provider
|
|
43
|
+
also derives its dialect from the installed driver and rejects a conflicting
|
|
44
|
+
`DB_CLIENT` value when one is supplied.
|
|
45
|
+
|
|
46
|
+
The application or hosting environment creates the database and provides its
|
|
47
|
+
credentials. JSKIT never guesses or commits them.
|
|
48
|
+
|
|
49
|
+
The running database provider registers a `database` health check that runs
|
|
50
|
+
`select 1` within two seconds. The foundations' `GET /api/health` answers `503`
|
|
51
|
+
while it fails; see [Health endpoint](/guide/app-setup/initial-scaffolding#health-endpoint).
|
|
52
|
+
|
|
53
|
+
## Migration configuration
|
|
54
|
+
|
|
55
|
+
Use the fixed-dialect pattern owned by the selected driver. The important
|
|
56
|
+
application file is small:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import { createKnexMigrationConfigFromApp } from
|
|
60
|
+
"@jskit-ai/database-runtime/server/knexMigrationConfig";
|
|
61
|
+
|
|
62
|
+
export default await createKnexMigrationConfigFromApp({ client: "mysql2", environment: process.env });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use `client: "pg"` for PostgreSQL. The environment is always passed: JSKIT reads
|
|
66
|
+
the connection values from what the application gives it, never from
|
|
67
|
+
`process.env` on its own. The complete examples are in:
|
|
68
|
+
|
|
69
|
+
- `database/mysql-application`
|
|
70
|
+
- `database/postgres-application`
|
|
71
|
+
|
|
72
|
+
Each example is complete for its own engine. Copy files only from the selected
|
|
73
|
+
driver's pattern and never merge the two; for example, the CI workflow must
|
|
74
|
+
contain only that engine's database service.
|
|
75
|
+
|
|
76
|
+
The config discovers migrations directly from the current installed package
|
|
77
|
+
graph. Application migrations live in the app's `migrations/` directory;
|
|
78
|
+
package-owned migrations live in directories declared by the installed
|
|
79
|
+
package's `package.json#jskit.migrations.directories`.
|
|
80
|
+
|
|
81
|
+
There is no migration sync command and no copied migration projection. Knex
|
|
82
|
+
runs the authoritative files where their owners ship them.
|
|
83
|
+
|
|
84
|
+
## What runs migrations
|
|
85
|
+
|
|
86
|
+
The application owns normal npm scripts:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"scripts": {
|
|
91
|
+
"db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
|
|
92
|
+
"db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
|
|
93
|
+
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`npm run db:migrate` runs the Knex CLI. Knex loads `knexfile.js`, discovers the
|
|
99
|
+
application and installed-package migration directories, connects using the
|
|
100
|
+
environment, and applies pending migrations.
|
|
101
|
+
|
|
102
|
+
Deployment or a managed development environment may invoke this app-owned
|
|
103
|
+
script as a release step. JSKIT itself does not maintain a background migration
|
|
104
|
+
service.
|
|
105
|
+
|
|
106
|
+
## Authoring schema changes
|
|
107
|
+
|
|
108
|
+
Write a new immutable migration in the package that owns the schema. Never edit
|
|
109
|
+
an already-applied migration and never change a live database without recording
|
|
110
|
+
the equivalent source-controlled migration.
|
|
111
|
+
|
|
112
|
+
Package migrations are normal `.cjs` Knex migrations and are declared in that
|
|
113
|
+
package's metadata:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"jskit": {
|
|
118
|
+
"migrations": {
|
|
119
|
+
"directories": ["migrations"]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A `constraints/` subdirectory of a declared directory, such as
|
|
126
|
+
`migrations/constraints/`, holds the package's constraints migrations; it runs
|
|
127
|
+
later, as described below.
|
|
128
|
+
|
|
129
|
+
A package whose table migrations use another package's tables names that
|
|
130
|
+
package in `after`, and its migrations, and its constraints, then run after
|
|
131
|
+
that package's:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"jskit": {
|
|
136
|
+
"migrations": {
|
|
137
|
+
"directories": ["migrations"],
|
|
138
|
+
"after": ["@jskit-ai/users-core"]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Each name in `after` must be another installed JSKIT package; a name that is
|
|
145
|
+
not installed, the package's own name, or packages that each name the other
|
|
146
|
+
stop discovery with an error. A named package without migrations orders
|
|
147
|
+
nothing.
|
|
148
|
+
|
|
149
|
+
Names must remain unique across the effective migration directories. Preserve
|
|
150
|
+
the basename when relocating a migration: Knex records that name in its ledger.
|
|
151
|
+
Do not rename it to force execution or edit an already-applied migration's body;
|
|
152
|
+
changed schema behavior requires a new migration.
|
|
153
|
+
|
|
154
|
+
### Directory order comes before filenames
|
|
155
|
+
|
|
156
|
+
`createKnexMigrationConfigFromApp()` sets `sortDirsSeparately: true` and runs
|
|
157
|
+
pending migrations in this order, using the default `migrationsDirectory`:
|
|
158
|
+
|
|
159
|
+
1. Discovered package migration directories, sorted by their absolute paths
|
|
160
|
+
using `localeCompare`, except that a package's directories follow those of
|
|
161
|
+
every package its `after` names, which follow theirs in turn, each in the
|
|
162
|
+
order listed. Without `after` the path order is unchanged.
|
|
163
|
+
2. Application `migrations/`.
|
|
164
|
+
3. The `constraints/` subdirectory of each discovered package migration
|
|
165
|
+
directory that has one, in the same order.
|
|
166
|
+
4. Application `migrations/constraints/`, when it exists.
|
|
167
|
+
|
|
168
|
+
Knex sorts filenames within each directory, not across the whole graph; see
|
|
169
|
+
its [`sortDirsSeparately` option](https://knexjs.org/guide/migrations.html#migration-api).
|
|
170
|
+
Package discovery does not read tables or foreign keys; only `after` changes
|
|
171
|
+
the package order. A newer timestamp in an earlier package still runs before
|
|
172
|
+
every file in a later package.
|
|
173
|
+
Inspect the effective `knexfile.js` configuration if the app customizes it.
|
|
174
|
+
|
|
175
|
+
Every package table exists before the application's own migrations run, so a
|
|
176
|
+
root `migrations/` file can reference a package table directly, for example a
|
|
177
|
+
`books.user_id` foreign key to `users.id`. A package's table migrations must
|
|
178
|
+
never depend on an application table.
|
|
179
|
+
|
|
180
|
+
Keep table and column creation with the owning package. Put foreign keys
|
|
181
|
+
between two packages, and other constraints that need the complete schema, in a
|
|
182
|
+
constraints phase: the referencing package's own `constraints/` subdirectory
|
|
183
|
+
when it ships the key, or application `migrations/constraints/`. A package
|
|
184
|
+
constraints migration runs after the application's `migrations/`, so it can also
|
|
185
|
+
reference a table that may belong to the application, such as `users`; for
|
|
186
|
+
example, `@jskit-ai/assistant-runtime` adds its `users` and `workspaces` keys
|
|
187
|
+
from `migrations/constraints/`. Similarly, a `pet_notes.product_id` column can be
|
|
188
|
+
added by the pet-notes package, but its foreign key to the products package's
|
|
189
|
+
`products.id` belongs in a constraints phase. Do not rely on the
|
|
190
|
+
package-directory path order to make a cross-package reference work; when a
|
|
191
|
+
package's table migrations need another package's table, declare `after`.
|
|
192
|
+
|
|
193
|
+
Until `@jskit-ai/database-runtime` 0.1.252, root `migrations/` ran before the
|
|
194
|
+
package directories, so a fresh database failed on any application migration
|
|
195
|
+
that referenced a package table. Applied migrations stay in the Knex ledger;
|
|
196
|
+
only pending ones follow the new order. An application migration that still
|
|
197
|
+
creates a package-owned table belongs in that package.
|
|
198
|
+
|
|
199
|
+
The assistant-runtime, console-core and rewarded-core table migrations used to
|
|
200
|
+
add their keys to `users`, `workspaces` and later tables inline. A fresh MySQL
|
|
201
|
+
or MariaDB database failed when the parent table did not exist yet (errno 150),
|
|
202
|
+
and the assistant tables silently skipped their `workspaces` keys. They now add
|
|
203
|
+
them from `migrations/constraints/`. A database migrated earlier keeps the keys
|
|
204
|
+
it already has, and gains any that are missing.
|
|
205
|
+
|
|
206
|
+
`@jskit-ai/database-runtime/server/foreignKeyMigrations` serves constraints
|
|
207
|
+
migrations that must run on fresh and already-migrated databases alike.
|
|
208
|
+
`addMissingForeignKey(knex, key)` adds a single-column key only when no key
|
|
209
|
+
already links that column to the parent column. It fails, naming the table,
|
|
210
|
+
column and count, when rows have no parent, and it deletes nothing; repair
|
|
211
|
+
those rows and migrate again. `dropForeignKeyIfPresent(knex, key)` serves
|
|
212
|
+
`down()`.
|
|
213
|
+
|
|
214
|
+
Before adding the key, check that both tables and columns exist, the referenced
|
|
215
|
+
key is indexed appropriately, column types match (including integer signedness),
|
|
216
|
+
existing values have valid parents, and `ON DELETE SET NULL` uses a nullable
|
|
217
|
+
column. An incorrectly formed foreign key is not always an ordering failure.
|
|
218
|
+
Do not suppress the failure, skip a missing parent, or disable foreign-key
|
|
219
|
+
checks to make preparation appear successful.
|
|
220
|
+
|
|
221
|
+
### MySQL and MariaDB failures can leave partial schema
|
|
222
|
+
|
|
223
|
+
MySQL and MariaDB DDL can commit implicitly. If adding a column succeeds and a
|
|
224
|
+
later statement adding its foreign key fails, the column can remain even though
|
|
225
|
+
Knex has not recorded that migration as completed. A Knex transaction does not
|
|
226
|
+
make a multi-statement DDL migration atomic. See the
|
|
227
|
+
[MySQL implicit-commit rules](https://dev.mysql.com/doc/refman/8.4/en/implicit-commit.html)
|
|
228
|
+
and [MariaDB implicit-commit rules](https://mariadb.com/docs/server/reference/sql-statements/transactions/sql-statements-that-cause-an-implicit-commit).
|
|
229
|
+
|
|
230
|
+
Design new multi-step DDL migrations for their concrete partial states. Inspect
|
|
231
|
+
the column and foreign key independently; `hasColumn()` alone cannot prove the
|
|
232
|
+
constraint exists. Verify the definition of an existing object before skipping
|
|
233
|
+
its creation, complete only missing work, and fail clearly on incompatible
|
|
234
|
+
schema. Do not catch arbitrary SQL errors as success or mark incomplete work
|
|
235
|
+
as applied. Keep recovery local to the affected migration or an explicit repair;
|
|
236
|
+
do not add schema repair to request handlers or normal server startup.
|
|
237
|
+
|
|
238
|
+
For an existing failure, inspect the ledger and actual schema read-only first.
|
|
239
|
+
Preserve applied migration bodies and use a new corrective migration for
|
|
240
|
+
databases that already recorded them. If an earlier pending migration prevents
|
|
241
|
+
that correction from running, describe the required backed-up, explicit repair
|
|
242
|
+
before retrying; a later file alone cannot repair a blocked earlier step. Any
|
|
243
|
+
relocation must retain its basename and be checked against both fresh and
|
|
244
|
+
existing ledgers. Never drop valuable data or delete ledger entries to force
|
|
245
|
+
a replay.
|
|
246
|
+
|
|
247
|
+
## Seed data is not a migration
|
|
248
|
+
|
|
249
|
+
Migrations establish schema and invariant framework data. Product fixtures,
|
|
250
|
+
sample accounts, catalog content, and other environment-specific starting data
|
|
251
|
+
belong in an explicit, idempotent application seed operation that runs after
|
|
252
|
+
migrations. Do not hide product seeding in schema migrations.
|
|
253
|
+
|
|
254
|
+
Managed editors must be able to create one isolated database per development
|
|
255
|
+
session, apply the full migration graph, then invoke that explicit seed
|
|
256
|
+
operation. JSKIT supplies portable migration and seed seams; the editor owns
|
|
257
|
+
database allocation, credentials, lifetime, and environment injection.
|
|
258
|
+
|
|
259
|
+
## Resource services and custom operations
|
|
260
|
+
|
|
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.
|
|
265
|
+
|
|
266
|
+
- `decorateRepository` adds resource-specific queries, locks, or writes.
|
|
267
|
+
- `decorateService` overrides a standard method or adds domain methods such as
|
|
268
|
+
`confirm`, `publish`, `cancel`, or `sendReminder`.
|
|
269
|
+
- `operationLifecycle` surrounds a standard operation with `before`, `execute`,
|
|
270
|
+
`after`, and mutation-only `afterCommit` phases. Create, update, and delete
|
|
271
|
+
phases before commit share one repository transaction, and `execute` receives
|
|
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.
|
|
276
|
+
- Named `actions` expose non-CRUD service methods through normal input,
|
|
277
|
+
permission, event, and optional HTTP route contracts.
|
|
278
|
+
|
|
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,
|
|
282
|
+
write an outbox record inside the transaction and deliver it separately.
|
|
283
|
+
A separate Feature is warranted when an operation belongs to another domain,
|
|
284
|
+
not merely because a useful resource has behavior beyond list and save. Write
|
|
285
|
+
it as a [typed feature](typed-features.md).
|
|
286
|
+
|
|
287
|
+
## Writes, versions, locks and deadlocks
|
|
288
|
+
|
|
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](data-access.md), Layers 1
|
|
292
|
+
and 4 and [Transactions](data-access.md#transactions).
|
|
293
|
+
|
|
294
|
+
## Verification
|
|
295
|
+
|
|
296
|
+
- For every schema change, run the normal preparation entrypoint against a
|
|
297
|
+
fresh disposable database on the selected engine with the complete package
|
|
298
|
+
graph. An existing development database can hide ordering failures.
|
|
299
|
+
- In a separate disposable fixture at the previous schema and ledger, apply
|
|
300
|
+
the upgrade and prove representative existing rows survive.
|
|
301
|
+
- For multi-step MySQL or MariaDB DDL, reproduce partial completion (for
|
|
302
|
+
example, column present, foreign key absent, migration still pending), retry
|
|
303
|
+
preparation, and verify the final column and constraint definitions.
|
|
304
|
+
- Run preparation again and `npm run db:migrate:status`; require no pending
|
|
305
|
+
migrations. Prove invalid references are rejected and the declared delete
|
|
306
|
+
behavior works. A stubbed Knex test or successful process exit is insufficient.
|
|
307
|
+
- Exercise a real transaction and one invalid-connection case.
|
|
308
|
+
- When a seed operation exists, run it twice and require the second run to be
|
|
309
|
+
safe.
|
|
310
|
+
- Use the application's selected engine; exercise each supported engine when
|
|
311
|
+
changing a shared pattern. Report unavailable database verification explicitly.
|
|
312
|
+
|
|
313
|
+
Do not add migration receipts, sync ledgers, generator provenance, or dialect
|
|
314
|
+
questionnaires. The installed graph, migration source, environment, and
|
|
315
|
+
database migration table are sufficient.
|
|
316
|
+
|
|
317
|
+
## Integration test databases
|
|
318
|
+
|
|
319
|
+
Disposable integration and migration tests use `TEST_DB_NAME`, never the
|
|
320
|
+
application's `DB_NAME`. A test run prepares that schema once and restores it
|
|
321
|
+
from a stored snapshot. `jskit test --databases` (the database patterns' `test`
|
|
322
|
+
script) does this with `@jskit-ai/database-runtime/server/testDatabases`, from
|
|
323
|
+
the application's `knexfile.js`:
|
|
324
|
+
|
|
325
|
+
- Each test file running at once gets a database of its own for as long as it
|
|
326
|
+
runs, as both `DB_NAME` and `TEST_DB_NAME`: `TEST_DB_NAME` itself, then
|
|
327
|
+
copies named `<TEST_DB_NAME>_1`, `_2`, ... up to the machine's test budget,
|
|
328
|
+
as many as the database user may create. With no usable `TEST_DB_NAME`
|
|
329
|
+
(unset, the connected database itself, or one the database user may not
|
|
330
|
+
prepare), the files run one at a time on the connected database. A copy
|
|
331
|
+
named like the application's database or `PROD_DB_NAME`, or a server that
|
|
332
|
+
cannot be reached, fails the run instead.
|
|
333
|
+
- The snapshot holds the schema as the engine reports it and the rows the
|
|
334
|
+
migrations wrote, including the migration ledger. On MySQL and MariaDB it is
|
|
335
|
+
each table's `SHOW CREATE TABLE` and rows, in
|
|
336
|
+
`node_modules/.cache/jskit-test-databases/`; on PostgreSQL it is the database
|
|
337
|
+
`<TEST_DB_NAME>_snapshot`, which `CREATE DATABASE <name> TEMPLATE <snapshot>`
|
|
338
|
+
copies while nothing is connected to it (the user needs `CREATEDB`), file by
|
|
339
|
+
file (`STRATEGY FILE_COPY`) from PostgreSQL 15. Its key
|
|
340
|
+
is a digest of the dialect, of the name and contents of every migration file
|
|
341
|
+
that the application's `migrations/` and its installed packages supply, and
|
|
342
|
+
of `package-lock.json`, since an upgraded package can change a helper its
|
|
343
|
+
migrations import. MySQL test databases hold tables only: a schema with
|
|
344
|
+
views, triggers, routines or events is refused.
|
|
345
|
+
- Each test file gets the environment `jskit test` was given, plus its
|
|
346
|
+
database's connection (`DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`); the
|
|
347
|
+
rest of the `.env` that `knexfile.js` loads is read for the databases only.
|
|
348
|
+
The run's settings, connection password included, are in a file only the
|
|
349
|
+
user can read; the runner's environment names only its directory.
|
|
350
|
+
- When that digest changes, the harness replays the migrations into the
|
|
351
|
+
designated database once, outside the timed tests, and stores a new
|
|
352
|
+
snapshot. Otherwise it restores the stored one.
|
|
353
|
+
- A schema the migrations alone do not build, such as migrations that start
|
|
354
|
+
from a baseline schema file, or reference rows every test reads, comes from
|
|
355
|
+
the application's own build: `knexfile.js` also exports
|
|
356
|
+
`testDatabaseSchema = { files, build }`, and a browser `prepare` module passes
|
|
357
|
+
the same `schema` to `prepareTestDatabases()`. `build(knex)` runs in the empty
|
|
358
|
+
designated database instead of `knex.migrate.latest()` (the baseline, then
|
|
359
|
+
`knex.migrate.latest()`), and `files` lists every file it reads, its own
|
|
360
|
+
module included, relative to the application root: their contents join the
|
|
361
|
+
digest, so changing one takes a new snapshot.
|
|
362
|
+
- A test that runs migrations up or down, or otherwise changes the tables,
|
|
363
|
+
leaves its database dirty: its tables or migration ledger differ from the
|
|
364
|
+
snapshot's. The harness restores the snapshot before another test file uses
|
|
365
|
+
that database, instead of running `migrate.latest()` over the whole history.
|
|
366
|
+
- A browser run's seed is not part of the snapshot: it runs in each restored
|
|
367
|
+
database, in one transaction ([Browser test preparation](#browser-test-preparation)).
|
|
368
|
+
- Only the fresh-schema check in [Verification](#verification) replays the
|
|
369
|
+
whole history, as its own step.
|
|
370
|
+
- The harness reports each phase (digest, restore or rebuild, seed) with its
|
|
371
|
+
duration, and those durations are read, so slow preparation is visible
|
|
372
|
+
instead of hiding inside the next test that waits for it. A slow phase is
|
|
373
|
+
fixed, never absorbed by a longer time limit.
|
|
374
|
+
|
|
375
|
+
Endpoints and batch operations get their own database tests at realistic row
|
|
376
|
+
counts that compare their query counts, counted with `countQueries()` from
|
|
377
|
+
`@jskit-ai/database-runtime/server/testDatabases`, or `countRuntimeQueries()`
|
|
378
|
+
for a route of the server the test boots; see the typed features guide's
|
|
379
|
+
[data access tests](data-access.md#tests).
|
|
380
|
+
|
|
381
|
+
## Browser test preparation
|
|
382
|
+
|
|
383
|
+
A browser run gives every Playwright worker its own server and its own copy of
|
|
384
|
+
`BROWSER_TEST_DB_NAME`. The database patterns' `example/tests/e2e/databases.js`
|
|
385
|
+
is the `prepare` module of `browserTestConfig({ prepare: "tests/e2e/databases.js" })`
|
|
386
|
+
from `@jskit-ai/jskit-catalog/browser-tests`: it calls `prepareTestDatabases()`
|
|
387
|
+
with `variable: "BROWSER_TEST_DB_NAME"`, the worker count and the app's fixture
|
|
388
|
+
seed, and returns each worker's `DB_NAME`. The worker's own process gets the
|
|
389
|
+
same environment as its server, so a test that opens the database itself (with
|
|
390
|
+
the app's `knexfile.js`) reaches that worker's copy. The copies (`<name>`, `<name>_1`,
|
|
391
|
+
...) are restored from the same stored snapshot as the Node test databases
|
|
392
|
+
([Integration test databases](#integration-test-databases)), then seeded in one
|
|
393
|
+
transaction each, so stale rows never survive a run and the migrations are
|
|
394
|
+
replayed only when one changes. It refuses the application's database,
|
|
395
|
+
`PROD_DB_NAME`, `TEST_DB_NAME` and their copies.
|
|
396
|
+
|
|
397
|
+
Give disposable integration and migration tests their own `TEST_DB_NAME`;
|
|
398
|
+
the two names, and their copies, must differ, and the application account
|
|
399
|
+
needs grants for each (a `<app>_%` grant covers the copies). Each preparation
|
|
400
|
+
reports how many databases it made, whether it replayed the migrations for a
|
|
401
|
+
new snapshot, and its restore and seed durations, so a cold rebuild is visible
|
|
402
|
+
to the caller.
|
|
403
|
+
|
|
404
|
+
Run related checks in one suite invocation and prepare only their required
|
|
405
|
+
fixtures. A local browser run serves the production build, never the
|
|
406
|
+
development launcher; fresh-schema migration proofs are separate. A managed
|
|
407
|
+
suite (`PLAYWRIGHT_BASE_URL`) uses the host's server URL and identity, as the
|
|
408
|
+
host serves it, starts no second server, and restores normal Preview after the
|
|
409
|
+
batch. Database isolation alone does not
|
|
410
|
+
disable email, payments or other external effects: configure those test controls
|
|
411
|
+
and verify the actual server identity before destructive requests.
|