@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.
Files changed (152) hide show
  1. package/guide/agent/app-extras/assistant.md +14 -2
  2. package/guide/agent/app-extras/realtime.md +38 -1
  3. package/guide/agent/app-setup/authentication.md +76 -14
  4. package/guide/agent/app-setup/console.md +8 -0
  5. package/guide/agent/app-setup/database-layer.md +23 -103
  6. package/guide/agent/app-setup/existing-application-migration.md +134 -72
  7. package/guide/agent/app-setup/multi-homing.md +94 -32
  8. package/guide/agent/app-setup/quickstart.md +1 -1
  9. package/guide/agent/app-setup/run-an-accounts-hub.md +1 -1
  10. package/guide/agent/app-setup/upgrading-jskit.md +17 -3
  11. package/guide/agent/app-setup/users.md +8 -5
  12. package/guide/agent/framework/application-operations.md +1 -1
  13. package/guide/agent/framework/crud-operations.md +63 -45
  14. package/guide/agent/framework/data-access.md +1207 -0
  15. package/guide/agent/framework/sanity-checks.md +26 -27
  16. package/guide/agent/framework/typed-features.md +117 -248
  17. package/guide/agent/index.md +2 -0
  18. package/package.json +6 -3
  19. package/patterns/feature-package/PATTERN.md +25 -17
  20. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  21. package/patterns/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
  22. package/patterns/feature-package/example/variations/invoiceResource.js +15 -0
  23. package/patterns/feature-package/example/variations/invoiceRollupActions.js +4 -4
  24. package/patterns/feature-package/example/variations/ownedTableRepository.js +27 -0
  25. package/patterns/feature-package/example/variations/types.js +1 -1
  26. package/patterns/minimal-foundation/example/AGENTS.md +4 -0
  27. package/patterns/minimal-foundation/example/package.json +4 -4
  28. package/patterns/minimal-foundation/example/server.js +4 -1
  29. package/patterns/shell-foundation/example/AGENTS.md +4 -0
  30. package/patterns/shell-foundation/example/package.json +5 -5
  31. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  32. package/patterns/shell-foundation/example/server.js +4 -1
  33. package/reference/autogen/KERNEL_MAP.md +3 -1
  34. package/reference/autogen/PATTERN_INDEX.md +70 -59
  35. package/reference/autogen/README.md +1 -1
  36. package/reference/autogen/packages/agent-docs.md +7 -3
  37. package/reference/autogen/packages/agent-runtime.md +48 -11
  38. package/reference/autogen/packages/architecture-inspector.md +6 -2
  39. package/reference/autogen/packages/assistant-core.md +4 -4
  40. package/reference/autogen/packages/assistant-runtime.md +33 -13
  41. package/reference/autogen/packages/auth-provider-local-core.md +3 -1
  42. package/reference/autogen/packages/auth-provider-local-db-core.md +69 -14
  43. package/reference/autogen/packages/auth-provider-oidc-core.md +5 -1
  44. package/reference/autogen/packages/auth-provider-supabase-core.md +1 -1
  45. package/reference/autogen/packages/connectors-core.md +22 -2
  46. package/reference/autogen/packages/console-core.md +9 -5
  47. package/reference/autogen/packages/crud-core.md +45 -175
  48. package/reference/autogen/packages/database-runtime-mysql.md +0 -26
  49. package/reference/autogen/packages/database-runtime.md +11 -33
  50. package/reference/autogen/packages/http-runtime.md +3 -3
  51. package/reference/autogen/packages/http-web.md +12 -0
  52. package/reference/autogen/packages/kernel.md +27 -2
  53. package/reference/autogen/packages/oidc-issuer-core.md +4 -0
  54. package/reference/autogen/packages/payments-core.md +28 -6
  55. package/reference/autogen/packages/realtime.md +15 -6
  56. package/reference/autogen/packages/resource-core.md +32 -5
  57. package/reference/autogen/packages/resource-crud-core.md +30 -34
  58. package/reference/autogen/packages/resource-runtime.md +220 -0
  59. package/reference/autogen/packages/rewarded-core.md +16 -10
  60. package/reference/autogen/packages/shell-web.md +1 -0
  61. package/reference/autogen/packages/users-core.md +33 -14
  62. package/reference/autogen/packages/workspaces-core.md +17 -10
  63. package/reference/autogen/tooling/config-eslint.md +74 -0
  64. package/reference/autogen/tooling/jskit-catalog.md +50 -1
  65. package/reference/autogen/tooling/jskit-release.md +5 -0
  66. package/skills/jskit/SKILL.md +4 -1
  67. package/skills/jskit/references/app-operations.md +1 -1
  68. package/skills/jskit/references/crud-operations.md +62 -44
  69. package/skills/jskit/references/data-access.md +1202 -0
  70. package/skills/jskit/references/database-layer.md +411 -0
  71. package/skills/jskit/references/existing-application-migration.md +134 -72
  72. package/skills/jskit/references/heuristics/INDEX.md +6 -4
  73. package/skills/jskit/references/heuristics/crud-authoring.md +26 -15
  74. package/skills/jskit/references/heuristics/data-access.md +19 -0
  75. package/skills/jskit/references/heuristics/filters.md +6 -6
  76. package/skills/jskit/references/heuristics/row-policies.md +40 -16
  77. package/skills/jskit/references/heuristics/server-search.md +30 -42
  78. package/skills/jskit/references/heuristics/table-resource-fields.md +78 -0
  79. package/skills/jskit/references/heuristics/typed-features.md +3 -3
  80. package/skills/jskit/references/multi-homing.md +94 -32
  81. package/skills/jskit/references/pattern-index.md +70 -59
  82. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +4 -0
  83. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  84. package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +4 -1
  85. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +4 -0
  86. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  87. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  88. package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +4 -1
  89. package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -2
  90. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -2
  91. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +15 -0
  92. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  93. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  94. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  95. package/skills/jskit/references/patterns/crud/crud-contract/PATTERN.md +133 -0
  96. package/skills/jskit/references/patterns/crud/crud-contract/example/bookResource.js +74 -0
  97. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/PATTERN.md +69 -37
  98. package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/packages/books/package.json +5 -4
  99. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/server/BooksFeature.js +13 -0
  100. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/bookResource.js +74 -0
  101. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/src/shared/index.js +1 -0
  102. package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +26 -8
  103. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -2
  104. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +2 -2
  105. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -2
  106. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +3 -3
  107. package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +9 -4
  108. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -1
  109. package/skills/jskit/references/patterns/database/mysql-application/example/scripts/prepare-database.js +8 -0
  110. package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +8 -4
  111. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -1
  112. package/skills/jskit/references/patterns/database/postgres-application/example/scripts/prepare-database.js +8 -0
  113. package/skills/jskit/references/patterns/database/table-resource/PATTERN.md +120 -0
  114. package/skills/jskit/references/patterns/database/table-resource/example/orderResource.js +25 -0
  115. package/skills/jskit/references/patterns/realtime/realtime-application/PATTERN.md +7 -0
  116. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  117. package/skills/jskit/references/patterns/rewards/google-rewarded/PATTERN.md +1 -1
  118. package/skills/jskit/references/patterns/server/feature-package/PATTERN.md +25 -17
  119. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  120. package/skills/jskit/references/patterns/server/feature-package/example/variations/InvoiceRollupFeature.js +6 -7
  121. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceResource.js +15 -0
  122. package/skills/jskit/references/patterns/server/feature-package/example/variations/invoiceRollupActions.js +4 -4
  123. package/skills/jskit/references/patterns/server/feature-package/example/variations/ownedTableRepository.js +27 -0
  124. package/skills/jskit/references/patterns/server/feature-package/example/variations/types.js +1 -1
  125. package/skills/jskit/references/patterns/users/user-administration-server/PATTERN.md +63 -10
  126. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +5 -4
  127. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/server/UsersFeature.js +7 -5
  128. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/index.js +1 -1
  129. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userCrud.js +21 -0
  130. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +11 -5
  131. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +14 -5
  132. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/server/workspaceMembersOnly.js +52 -0
  133. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/index.js +1 -1
  134. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userCrud.js +21 -0
  135. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +9 -9
  136. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +16 -0
  137. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +2 -0
  138. package/skills/jskit/references/sanity-checks.md +27 -28
  139. package/skills/jskit/references/typed-features.md +117 -248
  140. package/patterns/feature-package/example/variations/customKnexRepository.js +0 -40
  141. package/reference/autogen/packages/json-rest-api-core.md +0 -66
  142. package/skills/jskit/references/heuristics/crud-repository-mapping.md +0 -140
  143. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/server/BooksFeature.js +0 -13
  144. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/bookResource.js +0 -83
  145. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/src/shared/index.js +0 -1
  146. package/skills/jskit/references/patterns/crud/resource-contract/PATTERN.md +0 -155
  147. package/skills/jskit/references/patterns/crud/resource-contract/example/bookResource.js +0 -87
  148. package/skills/jskit/references/patterns/server/feature-package/example/variations/customKnexRepository.js +0 -40
  149. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/src/shared/userResource.js +0 -69
  150. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +0 -56
  151. /package/skills/jskit/references/patterns/crud/{json-api-resource-package → crud-feature-package}/example/migrations/20260815000000_books.cjs +0 -0
  152. /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.