@jskit-ai/agent-docs 0.1.130 → 0.1.132
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 +29 -605
- package/guide/agent/app-extras/mobile-capacitor.md +29 -362
- package/guide/agent/app-extras/realtime.md +29 -277
- package/guide/agent/app-setup/a-more-interesting-shell.md +44 -815
- package/guide/agent/app-setup/authentication.md +43 -1073
- package/guide/agent/app-setup/console.md +26 -298
- package/guide/agent/app-setup/database-layer.md +110 -790
- package/guide/agent/app-setup/initial-scaffolding.md +50 -784
- package/guide/agent/app-setup/multi-homing.md +39 -712
- package/guide/agent/app-setup/quickstart.md +43 -179
- package/guide/agent/app-setup/users.md +34 -353
- package/guide/agent/index.md +16 -23
- package/package.json +2 -2
- package/patterns/INDEX.md +7 -7
- package/patterns/child-cruds.md +3 -3
- package/patterns/client-requests.md +6 -6
- package/patterns/crud-authoring.md +94 -0
- package/patterns/crud-links.md +1 -1
- package/patterns/feature-package/PATTERN.md +108 -0
- package/patterns/feature-package/example/booking-engine/package.json +48 -0
- package/patterns/feature-package/example/booking-engine/src/server/BookingEngineProvider.js +33 -0
- package/patterns/feature-package/example/booking-engine/src/server/actions.js +26 -0
- package/patterns/feature-package/example/booking-engine/src/server/inputSchemas.js +19 -0
- package/patterns/feature-package/example/variations/AvailabilityEngineProvider.js +31 -0
- package/patterns/feature-package/example/variations/InvoiceRollupProvider.js +36 -0
- package/patterns/feature-package/example/variations/customKnexRepository.js +34 -0
- package/patterns/feature-package/example/variations/orchestratorService.js +23 -0
- package/patterns/filters.md +8 -8
- package/patterns/live-actions.md +5 -18
- package/patterns/minimal-foundation/PATTERN.md +98 -0
- package/patterns/minimal-foundation/example/.nvmrc +1 -0
- package/patterns/minimal-foundation/example/AGENTS.md +17 -0
- package/patterns/minimal-foundation/example/Procfile +2 -0
- package/patterns/minimal-foundation/example/app.json +14 -0
- package/patterns/minimal-foundation/example/bin/develop.js +71 -0
- package/patterns/minimal-foundation/example/bin/server.js +8 -0
- package/patterns/minimal-foundation/example/config/public.js +40 -0
- package/patterns/minimal-foundation/example/config/server.js +1 -0
- package/patterns/minimal-foundation/example/config/surfaceAccessPolicies.js +3 -0
- package/patterns/minimal-foundation/example/eslint.config.mjs +19 -0
- package/patterns/minimal-foundation/example/favicon.svg +7 -0
- package/patterns/minimal-foundation/example/gitignore +9 -0
- package/patterns/minimal-foundation/example/index.html +13 -0
- package/patterns/minimal-foundation/example/jsconfig.json +8 -0
- package/patterns/minimal-foundation/example/package.json +57 -0
- package/patterns/minimal-foundation/example/packages/main/package.json +42 -0
- package/patterns/minimal-foundation/example/packages/main/src/shared/index.js +10 -0
- package/patterns/minimal-foundation/example/packages/main/src/shared/schemas/index.js +22 -0
- package/patterns/minimal-foundation/example/playwright.config.mjs +31 -0
- package/patterns/minimal-foundation/example/server/lib/runtimeEnv.js +45 -0
- package/patterns/minimal-foundation/example/server/lib/surfaceRuntime.js +10 -0
- package/patterns/minimal-foundation/example/server.js +195 -0
- package/patterns/minimal-foundation/example/src/App.vue +13 -0
- package/patterns/minimal-foundation/example/src/main.js +85 -0
- package/patterns/minimal-foundation/example/src/pages/home/index.vue +48 -0
- package/patterns/minimal-foundation/example/src/pages/home.vue +13 -0
- package/patterns/minimal-foundation/example/src/views/NotFound.vue +13 -0
- package/patterns/minimal-foundation/example/tests/client/smoke.vitest.js +7 -0
- package/patterns/minimal-foundation/example/tests/e2e/base-shell.spec.ts +23 -0
- package/patterns/minimal-foundation/example/tests/server/smoke.test.js +16 -0
- package/patterns/minimal-foundation/example/vite.config.mjs +81 -0
- package/patterns/page-scaffolding.md +20 -17
- package/patterns/placements.md +17 -15
- package/patterns/row-policies.md +4 -5
- package/patterns/server-search.md +3 -3
- package/patterns/shell-foundation/PATTERN.md +104 -0
- package/patterns/shell-foundation/example/.nvmrc +1 -0
- package/patterns/shell-foundation/example/AGENTS.md +17 -0
- package/patterns/shell-foundation/example/Procfile +2 -0
- package/patterns/shell-foundation/example/app.json +14 -0
- package/patterns/shell-foundation/example/bin/develop.js +71 -0
- package/patterns/shell-foundation/example/bin/server.js +8 -0
- package/patterns/shell-foundation/example/config/public.js +40 -0
- package/patterns/shell-foundation/example/config/server.js +1 -0
- package/patterns/shell-foundation/example/config/surfaceAccessPolicies.js +3 -0
- package/patterns/shell-foundation/example/eslint.config.mjs +19 -0
- package/patterns/shell-foundation/example/favicon.svg +7 -0
- package/patterns/shell-foundation/example/gitignore +9 -0
- package/patterns/shell-foundation/example/index.html +13 -0
- package/patterns/shell-foundation/example/jsconfig.json +8 -0
- package/patterns/shell-foundation/example/package.json +59 -0
- package/patterns/shell-foundation/example/packages/main/package.json +56 -0
- package/patterns/shell-foundation/example/packages/main/src/client/index.js +9 -0
- package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +18 -0
- package/patterns/shell-foundation/example/packages/main/src/shared/index.js +10 -0
- package/patterns/shell-foundation/example/packages/main/src/shared/schemas/index.js +22 -0
- package/patterns/shell-foundation/example/playwright.config.mjs +31 -0
- package/patterns/shell-foundation/example/server/lib/runtimeEnv.js +45 -0
- package/patterns/shell-foundation/example/server/lib/surfaceRuntime.js +10 -0
- package/patterns/shell-foundation/example/server.js +195 -0
- package/patterns/shell-foundation/example/src/App.vue +11 -0
- package/patterns/shell-foundation/example/src/components/ShellLayout.vue +12 -0
- package/patterns/shell-foundation/example/src/components/menus/MenuLinkItem.vue +30 -0
- package/patterns/shell-foundation/example/src/components/menus/SurfaceAwareMenuLinkItem.vue +42 -0
- package/patterns/shell-foundation/example/src/components/menus/TabLinkItem.vue +42 -0
- package/patterns/shell-foundation/example/src/error.js +19 -0
- package/patterns/shell-foundation/example/src/main.js +85 -0
- package/patterns/shell-foundation/example/src/pages/home/index.vue +116 -0
- package/patterns/shell-foundation/example/src/pages/home/settings/general/index.vue +40 -0
- package/patterns/shell-foundation/example/src/pages/home/settings/index.vue +7 -0
- package/patterns/shell-foundation/example/src/pages/home/settings.vue +109 -0
- package/patterns/shell-foundation/example/src/pages/home.vue +20 -0
- package/patterns/shell-foundation/example/src/placement.js +56 -0
- package/patterns/shell-foundation/example/src/placementTopology.js +149 -0
- package/patterns/shell-foundation/example/src/views/NotFound.vue +13 -0
- package/patterns/shell-foundation/example/tests/client/smoke.vitest.js +7 -0
- package/patterns/shell-foundation/example/tests/e2e/adaptive-shell.spec.ts +10 -0
- package/patterns/shell-foundation/example/tests/e2e/base-shell.spec.ts +23 -0
- package/patterns/shell-foundation/example/tests/server/smoke.test.js +16 -0
- package/patterns/shell-foundation/example/vite.config.mjs +81 -0
- package/patterns/ui-contract.md +56 -0
- package/patterns/ui-testing.md +10 -12
- package/reference/autogen/KERNEL_MAP.md +29 -107
- package/reference/autogen/PATTERN_INDEX.md +230 -0
- package/reference/autogen/README.md +4 -8
- package/reference/autogen/packages/agent-docs.md +259 -0
- package/reference/autogen/packages/assistant-core.md +3 -3
- package/reference/autogen/packages/assistant-runtime.md +32 -17
- package/reference/autogen/packages/auth-core.md +31 -33
- package/reference/autogen/packages/auth-provider-local-core.md +4 -12
- package/reference/autogen/packages/auth-provider-local-db-core.md +4 -4
- package/reference/autogen/packages/auth-provider-supabase-core.md +14 -18
- package/reference/autogen/packages/auth-web.md +42 -22
- package/reference/autogen/packages/console-core.md +8 -25
- package/reference/autogen/packages/console-web.md +5 -5
- package/reference/autogen/packages/crud-core.md +61 -17
- package/reference/autogen/packages/database-runtime-mysql.md +12 -2
- package/reference/autogen/packages/database-runtime-postgres.md +12 -2
- package/reference/autogen/packages/database-runtime.md +26 -25
- package/reference/autogen/packages/google-rewarded-core.md +19 -104
- package/reference/autogen/packages/http-runtime.md +4 -8
- package/reference/autogen/packages/http-web.md +32 -0
- package/reference/autogen/packages/json-rest-api-core.md +4 -6
- package/reference/autogen/packages/kernel.md +109 -390
- package/reference/autogen/packages/mobile-capacitor.md +2 -13
- package/reference/autogen/packages/realtime.md +29 -26
- package/reference/autogen/packages/resource-crud-core.md +6 -0
- package/reference/autogen/packages/shell-web.md +69 -54
- package/reference/autogen/packages/storage-runtime.md +3 -3
- package/reference/autogen/packages/uploads-image-web.md +0 -1
- package/reference/autogen/packages/uploads-runtime.md +3 -3
- package/reference/autogen/packages/users-core.md +45 -90
- package/reference/autogen/packages/users-web.md +5 -7
- package/reference/autogen/packages/workspaces-core.md +53 -74
- package/reference/autogen/packages/workspaces-web.md +15 -16
- package/reference/autogen/tooling/jskit-catalog.md +34 -0
- package/reference/autogen/tooling/testUtils.md +4 -4
- package/skills/jskit/SKILL.md +36 -28
- package/skills/jskit/agents/openai.yaml +2 -2
- package/skills/jskit/references/app-operations.md +68 -53
- package/skills/jskit/references/crud-operations.md +58 -106
- package/skills/jskit/references/material-3.md +105 -0
- package/skills/jskit/references/ui-operations.md +41 -44
- package/templates/app/AGENTS.md +7 -3
- package/guide/agent/app-setup/upgrade-beta-1-to-final.md +0 -252
- package/guide/agent/app-setup/working-with-the-jskit-cli.md +0 -325
- package/guide/agent/generators/advanced-cruds.md +0 -1935
- package/guide/agent/generators/crud-generators.md +0 -948
- package/guide/agent/generators/intro.md +0 -65
- package/guide/agent/generators/row-policies.md +0 -537
- package/guide/agent/generators/ui-generators.md +0 -690
- package/patterns/crud-scaffolding.md +0 -198
- package/patterns/generated-ui-contract-tracking.md +0 -66
- package/reference/autogen/packages/assistant.md +0 -68
- package/reference/autogen/packages/crud-server-generator.md +0 -215
- package/reference/autogen/packages/crud-ui-generator.md +0 -192
- package/reference/autogen/packages/feature-server-generator.md +0 -65
- package/reference/autogen/packages/ui-generator.md +0 -127
- package/reference/autogen/tooling/create-app.md +0 -317
- package/reference/autogen/tooling/jskit-cli.md +0 -933
- package/reference/autogen/tooling/test-support.md +0 -27
|
@@ -2,632 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
# Database layer
|
|
4
4
|
|
|
5
|
-
|
|
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.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
## Install the selected driver
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
From inside `exampleapp`, run:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
DB_HOST=127.0.0.1
|
|
15
|
-
DB_PORT=3306
|
|
16
|
-
DB_NAME=exampleapp
|
|
17
|
-
DB_USER=exampleapp
|
|
18
|
-
DB_PASSWORD=secret
|
|
19
|
-
|
|
20
|
-
npx jskit add package database-runtime-mysql \
|
|
21
|
-
--db-host "$DB_HOST" \
|
|
22
|
-
--db-port "$DB_PORT" \
|
|
23
|
-
--db-name "$DB_NAME" \
|
|
24
|
-
--db-user "$DB_USER" \
|
|
25
|
-
--db-password "$DB_PASSWORD"
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
The first command adds the MySQL driver package and its generic JSKIT database runtime dependency, using the explicit connection values from those `DB_*` variables:
|
|
29
|
-
|
|
30
|
-
- database host
|
|
31
|
-
- database port
|
|
32
|
-
- database name
|
|
33
|
-
- database user
|
|
34
|
-
- database password
|
|
35
|
-
|
|
36
|
-
The second command downloads the newly referenced runtime packages and the underlying Node dependencies, especially `knex` and `mysql2`.
|
|
37
|
-
|
|
38
|
-
If you open the app in the browser after this install, it still looks the same. That is expected.
|
|
39
|
-
|
|
40
|
-
- `/home` still renders the same shell
|
|
41
|
-
- `/auth/login` still renders the same login screen
|
|
42
|
-
|
|
43
|
-
This chapter changes the server-side infrastructure of the app, not the visible shell.
|
|
44
|
-
|
|
45
|
-
## What changes immediately
|
|
46
|
-
|
|
47
|
-
Installing `database-runtime-mysql` gives the app three important things right away.
|
|
48
|
-
|
|
49
|
-
### A real database runtime
|
|
50
|
-
|
|
51
|
-
The server can build a real Knex client from environment variables. That gives later packages a standard way to ask for a database connection instead of each package inventing its own wiring.
|
|
52
|
-
|
|
53
|
-
### A migration workflow
|
|
54
|
-
|
|
55
|
-
The app gets three database scripts in `package.json`:
|
|
56
|
-
|
|
57
|
-
```json
|
|
58
|
-
{
|
|
59
|
-
"scripts": {
|
|
60
|
-
"db:migrations:sync": "jskit migrations sync",
|
|
61
|
-
"db:migrate": "npm run db:migrations:sync && knex --knexfile ./knexfile.js migrate:latest",
|
|
62
|
-
"db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
|
|
63
|
-
"db:migrate:status": "npm run db:migrations:sync && knex --knexfile ./knexfile.js migrate:list"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
That is the first time the scaffold can talk about schema migrations in a standard way.
|
|
69
|
-
|
|
70
|
-
If you run the status command immediately after this chapter:
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
npm run db:migrate:status
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
you should still see that there are no completed migrations and no pending migration files yet. The runtime and the Knex wiring exist at this point, but no package has added real schema files until the next chapter.
|
|
77
|
-
|
|
78
|
-
### A place for future schema files
|
|
79
|
-
|
|
80
|
-
The app also gets a top-level `migrations/` directory. Right now it only contains `.gitkeep`, which can look underwhelming at first, but that empty directory is actually useful. It means the migration toolchain is ready even before any package installs real schema files.
|
|
81
|
-
|
|
82
|
-
That is the key idea of this chapter:
|
|
83
|
-
|
|
84
|
-
- the database runtime provides the **infrastructure**
|
|
85
|
-
- later packages provide the **actual schema**
|
|
86
|
-
|
|
87
|
-
## Two migration steps, two different tools
|
|
88
|
-
|
|
89
|
-
This chapter is the right place to make one distinction very explicit.
|
|
90
|
-
|
|
91
|
-
The app has two different migration-related layers:
|
|
92
|
-
|
|
93
|
-
- JSKIT-managed migration files on disk
|
|
94
|
-
- Knex actually applying those files to the database
|
|
95
|
-
|
|
96
|
-
Those are **not** the same step.
|
|
97
|
-
|
|
98
|
-
There is also an important ownership distinction:
|
|
99
|
-
|
|
100
|
-
- a CRUD generator owns the installed baseline migration for the table it
|
|
101
|
-
scaffolds
|
|
102
|
-
- the table's app-local package owns later additive schema evolution
|
|
103
|
-
|
|
104
|
-
Never modify or replace a generator-owned baseline migration. Later schema
|
|
105
|
-
evolution must use a new immutable, package-owned additive migration in the
|
|
106
|
-
table's app-local package, declared through `install-migration`.
|
|
107
|
-
|
|
108
|
-
The npm scripts run `npm run db:migrations:sync` first, then run Knex. That means newly installed package migrations are present before Knex checks what is pending.
|
|
109
|
-
|
|
110
|
-
### `jskit migrations sync` writes package migration files
|
|
111
|
-
|
|
112
|
-
If you run the sync script directly:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
npm run db:migrations:sync
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
JSKIT reads `install-migration` entries from `package.json.jskit` across the installed npm graph and writes any missing immutable migration files into `migrations/`.
|
|
119
|
-
|
|
120
|
-
That command is about the app scaffold on disk.
|
|
121
|
-
|
|
122
|
-
It does **not** run Knex against MySQL or Postgres.
|
|
123
|
-
|
|
124
|
-
At this point in the guide, that command will usually touch nothing yet, because `database-runtime-mysql` gives you the migration **tooling** and `migrations/.gitkeep`, but it does not add real schema files of its own.
|
|
125
|
-
|
|
126
|
-
### `npm run db:migrate` runs Knex against the database
|
|
127
|
-
|
|
128
|
-
When you run:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
npm run db:migrate
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
the Knex CLI reads `knexfile.js`, connects to the real database, and applies any pending migration files it finds.
|
|
135
|
-
|
|
136
|
-
That command is about the actual database.
|
|
137
|
-
|
|
138
|
-
So the clean mental model is:
|
|
139
|
-
|
|
140
|
-
1. JSKIT writes or refreshes managed migration files into `migrations/`
|
|
141
|
-
2. Knex applies those files to MySQL or Postgres
|
|
142
|
-
|
|
143
|
-
### When you need each step
|
|
144
|
-
|
|
145
|
-
In normal `jskit add package ...` flows, JSKIT synchronizes package migration files after installation.
|
|
146
|
-
|
|
147
|
-
That means the most common flow is still:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
npx jskit add package users-web
|
|
151
|
-
npm run db:migrate
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
You can also synchronize explicitly:
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
npx jskit migrations sync
|
|
158
|
-
npm run db:migrate
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
So:
|
|
162
|
-
|
|
163
|
-
- use `jskit migrations sync` when you need JSKIT to write package migration files
|
|
164
|
-
- use `npm run db:migrate` when you need Knex to apply pending migration files to the real database
|
|
165
|
-
- sometimes you need only `npm run db:migrate`
|
|
166
|
-
- sometimes, after repair or re-materialization work, you need **both**
|
|
167
|
-
|
|
168
|
-
### Authoring a later app-owned schema change
|
|
169
|
-
|
|
170
|
-
When an existing CRUD-owned table needs a new column, constraint, index, or
|
|
171
|
-
other compatible evolution, keep the generated baseline unchanged. Ask JSKIT
|
|
172
|
-
to create a new migration source in the app-local package that owns the table:
|
|
12
|
+
For MySQL or MariaDB:
|
|
173
13
|
|
|
174
14
|
```bash
|
|
175
|
-
|
|
176
|
-
--package @local/workflow-record-report-values \
|
|
177
|
-
--id extend-report-value-field-types
|
|
15
|
+
npm install @jskit-ai/database-runtime-mysql
|
|
178
16
|
```
|
|
179
17
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
1. verifies that the owner is an installed app-local package
|
|
183
|
-
2. rejects duplicate or unsafe migration ids
|
|
184
|
-
3. creates an editable template under the package's
|
|
185
|
-
`templates/migrations/` directory
|
|
186
|
-
4. adds the matching `install-migration` mutation to `package.json.jskit`
|
|
187
|
-
5. leaves the migration unmaterialized so its implementation can still be
|
|
188
|
-
completed
|
|
189
|
-
|
|
190
|
-
Implement and test the template first. It intentionally fails if someone tries
|
|
191
|
-
to apply the untouched scaffold. Then materialize and apply it:
|
|
18
|
+
For PostgreSQL:
|
|
192
19
|
|
|
193
20
|
```bash
|
|
194
|
-
|
|
195
|
-
npm run db:migrate
|
|
21
|
+
npm install @jskit-ai/database-runtime-postgres
|
|
196
22
|
```
|
|
197
23
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
SQL inside the source-controlled migration is supported when Knex does not
|
|
202
|
-
express the required schema operation directly. Ad-hoc SQL applied only to a
|
|
203
|
-
development or live database is not a migration and must not be used: it
|
|
204
|
-
creates schema drift, breaks fresh reconstruction, and leaves deployment
|
|
205
|
-
history incomplete.
|
|
206
|
-
|
|
207
|
-
Before completion, exercise the complete migration chain against a fresh
|
|
208
|
-
disposable database as well as the intended upgrade path. A down migration
|
|
209
|
-
must refuse safely when narrowing the schema would invalidate existing data;
|
|
210
|
-
it must never delete or silently transform valuable rows merely to make a
|
|
211
|
-
rollback pass.
|
|
212
|
-
|
|
213
|
-
### Shared database helpers
|
|
214
|
-
|
|
215
|
-
The database layer also gives later server code a shared helper surface:
|
|
216
|
-
|
|
217
|
-
```js
|
|
218
|
-
import {
|
|
219
|
-
applyVisibility,
|
|
220
|
-
applyVisibilityOwners,
|
|
221
|
-
toIsoString,
|
|
222
|
-
toInsertDateTime,
|
|
223
|
-
toNullableDateTime,
|
|
224
|
-
isDuplicateEntryError,
|
|
225
|
-
whereJsonTextEquals,
|
|
226
|
-
createWithTransaction
|
|
227
|
-
} from "@jskit-ai/database-runtime/shared";
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
This is worth calling out here because the database layer is not only "Knex plus migrations".
|
|
231
|
-
|
|
232
|
-
It also gives your later repositories and services a standard persistence toolbox so every package does not have to solve the same problems differently. The main point is consistency:
|
|
233
|
-
|
|
234
|
-
- one way to format timestamps for database writes
|
|
235
|
-
- one way to format timestamps for API output
|
|
236
|
-
- one way to recognize duplicate-entry errors across databases
|
|
237
|
-
- one way to apply ownership-aware visibility filters to queries
|
|
238
|
-
- one way to stamp owner columns onto new rows from the current visibility context
|
|
239
|
-
- one way to build some dialect-aware JSON filters
|
|
240
|
-
- one small transaction helper pattern that packages can reuse
|
|
241
|
-
|
|
242
|
-
That helper layer comes from `@jskit-ai/database-runtime/shared`. The MySQL package mainly contributes the actual driver and dialect registration. The shared helper surface comes from the generic runtime package.
|
|
243
|
-
|
|
244
|
-
#### Visibility and ownership helpers
|
|
245
|
-
|
|
246
|
-
This is the main helper pair that matters once repositories start dealing with owned records.
|
|
247
|
-
|
|
248
|
-
In JSKIT persistence code, **visibility** means "which rows should this request be allowed to see or create?" It is the data-layer version of the same ownership model used by routes and CRUD resources:
|
|
249
|
-
|
|
250
|
-
- `public`
|
|
251
|
-
- the record is not scoped by owner columns
|
|
252
|
-
- `workspace`
|
|
253
|
-
- the record belongs to one workspace through the exact reserved column `workspace_id`
|
|
254
|
-
- `user`
|
|
255
|
-
- the record belongs to one user through the exact reserved column `user_id`
|
|
256
|
-
- `workspace_user`
|
|
257
|
-
- the record belongs to one workspace and one user through both reserved columns
|
|
258
|
-
|
|
259
|
-
Only `workspace_id` and `user_id` carry this standard ownership contract. Specifically named foreign keys such as `recipient_user_id`, `created_by_user_id`, and `assignee_user_id` describe domain relationships; they are not alternate owner columns. Keep both fields when a row has an owner and a separate related actor, and never rename the relationship to an owner column merely to make a tool accept the schema.
|
|
260
|
-
|
|
261
|
-
The selected ownership filter must match the direct reserved columns exactly. A table with only `workspace_id` is `workspace`; a table with only `user_id` is `user`; and a table with both is `workspace_user`. A declaration cannot override or ignore either column.
|
|
262
|
-
|
|
263
|
-
That is why the shared helpers exist. Repositories should not have to re-implement the same ownership rules by hand every time they filter a query or build an insert payload.
|
|
264
|
-
|
|
265
|
-
The expected input is a **visibility context**. In practice this usually arrives from route/action execution context and gets passed down as `queryOptions.visibilityContext` or `callOptions.visibilityContext`.
|
|
266
|
-
|
|
267
|
-
The important shape looks like this:
|
|
268
|
-
|
|
269
|
-
```js
|
|
270
|
-
const visibilityContext = {
|
|
271
|
-
visibility: "workspace_user",
|
|
272
|
-
scopeOwnerId: "42",
|
|
273
|
-
userId: "7"
|
|
274
|
-
};
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
Those are the fields these helpers care about:
|
|
278
|
-
|
|
279
|
-
- `visibility`
|
|
280
|
-
- one of `public`, `workspace`, `user`, `workspace_user`
|
|
281
|
-
- `scopeOwnerId`
|
|
282
|
-
- the current workspace owner id
|
|
283
|
-
- `userId`
|
|
284
|
-
- the current user id
|
|
285
|
-
|
|
286
|
-
There are a couple of extra normalized visibility fields elsewhere in JSKIT, but for `applyVisibility(...)` and `applyVisibilityOwners(...)`, those three values are the main contract.
|
|
287
|
-
|
|
288
|
-
Use `applyVisibility(...)` when you are building a **read/update/delete query** that should only operate on rows visible to the current request.
|
|
289
|
-
|
|
290
|
-
```js
|
|
291
|
-
import { applyVisibility } from "@jskit-ai/database-runtime/shared";
|
|
292
|
-
|
|
293
|
-
function listWorkspaceContacts(knex, queryOptions = {}) {
|
|
294
|
-
const query = knex("contacts").select("*").orderBy("created_at", "desc");
|
|
295
|
-
return applyVisibility(query, queryOptions.visibilityContext);
|
|
296
|
-
}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
If the current request is workspace-scoped:
|
|
300
|
-
|
|
301
|
-
```js
|
|
302
|
-
const visibilityContext = {
|
|
303
|
-
visibility: "workspace",
|
|
304
|
-
scopeOwnerId: "12"
|
|
305
|
-
};
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
the helper turns that into the equivalent of:
|
|
309
|
-
|
|
310
|
-
```js
|
|
311
|
-
query.where("workspace_id", "12");
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
That is the normal helper for workspace-owned rows.
|
|
315
|
-
|
|
316
|
-
The same pattern works for user-owned rows:
|
|
317
|
-
|
|
318
|
-
```js
|
|
319
|
-
const query = knex("saved_views").select("*");
|
|
320
|
-
applyVisibility(query, {
|
|
321
|
-
visibility: "user",
|
|
322
|
-
userId: "7"
|
|
323
|
-
});
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
which applies:
|
|
327
|
-
|
|
328
|
-
```js
|
|
329
|
-
query.where("user_id", "7");
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
And if a `workspace_user` context is required but only one owner id is available, the helper intentionally makes the query match nothing rather than accidentally leaking records. That is why these helpers are safer than scattering raw `where(...)` calls by hand.
|
|
333
|
-
|
|
334
|
-
Use `applyVisibilityOwners(...)` when you are building a **write payload** for a new owned row and you want JSKIT to stamp the owner columns from the current visibility context.
|
|
335
|
-
|
|
336
|
-
```js
|
|
337
|
-
import { applyVisibilityOwners, toInsertDateTime } from "@jskit-ai/database-runtime/shared";
|
|
338
|
-
|
|
339
|
-
async function createWorkspaceNote(knex, payload, queryOptions = {}) {
|
|
340
|
-
const insertPayload = applyVisibilityOwners(
|
|
341
|
-
{
|
|
342
|
-
title: payload.title,
|
|
343
|
-
body: payload.body,
|
|
344
|
-
created_at: toInsertDateTime(),
|
|
345
|
-
updated_at: toInsertDateTime()
|
|
346
|
-
},
|
|
347
|
-
queryOptions.visibilityContext
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
await knex("notes").insert(insertPayload);
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
If the request is workspace-scoped, the helper adds `workspace_id` automatically. If it is user-scoped, it adds `user_id`. If it is `workspace_user`, it adds both. That means repository code does **not** need to manually copy `workspace_id` and `user_id` out of the execution context for every insert.
|
|
355
|
-
|
|
356
|
-
That is especially useful for owned records such as:
|
|
357
|
-
|
|
358
|
-
- workspace-owned rows like `contacts`, `workspace_invites`, or `assistant_config`
|
|
359
|
-
- user-owned rows like personal settings or saved views
|
|
360
|
-
- combined workspace-and-user rows where both ids define ownership
|
|
361
|
-
|
|
362
|
-
So a `workspace_user` create can stay small:
|
|
363
|
-
|
|
364
|
-
```js
|
|
365
|
-
const insertPayload = applyVisibilityOwners(
|
|
366
|
-
{
|
|
367
|
-
name: payload.name
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
visibility: "workspace_user",
|
|
371
|
-
scopeOwnerId: "4",
|
|
372
|
-
userId: "9"
|
|
373
|
-
}
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
// Result:
|
|
377
|
-
// {
|
|
378
|
-
// name: "...",
|
|
379
|
-
// workspace_id: "4",
|
|
380
|
-
// user_id: "9"
|
|
381
|
-
// }
|
|
382
|
-
```
|
|
24
|
+
Each driver brings `@jskit-ai/database-runtime` and the appropriate Knex
|
|
25
|
+
driver through ordinary npm dependencies. There is no JSKIT install wizard.
|
|
383
26
|
|
|
384
|
-
|
|
27
|
+
## Connection environment
|
|
385
28
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
- `applyVisibilityOwners(...)`
|
|
389
|
-
- stamps write payloads, and throws when required owner ids are missing
|
|
390
|
-
|
|
391
|
-
That split is deliberate. For reads, "match nothing" is the safe default. For writes, silently inserting an incorrectly owned row would be worse, so the helper fails fast instead.
|
|
392
|
-
|
|
393
|
-
If you want a slightly more structured repository shape, `createRepositoryScope(...)` from the same shared surface wraps these helpers into one small repository-scoping toolkit. But even when you write your own repositories manually, `applyVisibility(...)` and `applyVisibilityOwners(...)` are the key ownership helpers to reuse.
|
|
394
|
-
|
|
395
|
-
#### Date helpers
|
|
396
|
-
|
|
397
|
-
The date helpers are probably the first ones you are likely to reach for.
|
|
398
|
-
|
|
399
|
-
They exist because application code usually deals with dates in two different shapes:
|
|
400
|
-
|
|
401
|
-
- **database write shape**, such as a `DATETIME(3)`-style value
|
|
402
|
-
- **API/read shape**, usually an ISO timestamp string
|
|
403
|
-
|
|
404
|
-
Those are not the same thing, and it is easy for packages to become inconsistent if every repository hand-rolls the conversion.
|
|
405
|
-
|
|
406
|
-
Typical usage looks like this:
|
|
407
|
-
|
|
408
|
-
```js
|
|
409
|
-
import {
|
|
410
|
-
toIsoString,
|
|
411
|
-
toInsertDateTime,
|
|
412
|
-
toNullableDateTime
|
|
413
|
-
} from "@jskit-ai/database-runtime/shared";
|
|
414
|
-
|
|
415
|
-
function mapRow(row) {
|
|
416
|
-
return {
|
|
417
|
-
id: String(row.id),
|
|
418
|
-
createdAt: toIsoString(row.created_at),
|
|
419
|
-
updatedAt: toIsoString(row.updated_at),
|
|
420
|
-
expiresAt: row.expires_at ? toIsoString(row.expires_at) : null
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
async function insertInvite(knex, payload) {
|
|
425
|
-
await knex("workspace_invites").insert({
|
|
426
|
-
email: payload.email,
|
|
427
|
-
expires_at: toNullableDateTime(payload.expiresAt),
|
|
428
|
-
created_at: toInsertDateTime(),
|
|
429
|
-
updated_at: toInsertDateTime()
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
The reasoning behind each helper is:
|
|
435
|
-
|
|
436
|
-
- `toIsoString(...)`
|
|
437
|
-
- use this when data is coming **out** of the database and you want a stable ISO timestamp for application code or API responses
|
|
438
|
-
- `toInsertDateTime(...)`
|
|
439
|
-
- use this when you need "now" in the database's datetime text format for insert/update timestamps
|
|
440
|
-
- `toNullableDateTime(...)`
|
|
441
|
-
- use this when a field is optional and should become either a normalized database datetime or `null`
|
|
442
|
-
|
|
443
|
-
There is also a lower-level helper:
|
|
444
|
-
|
|
445
|
-
```js
|
|
446
|
-
toDatabaseDateTimeUtc(value)
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
That one is useful when you already know you are converting one specific date value into the database write format and you want to be explicit about it.
|
|
450
|
-
|
|
451
|
-
The main benefit is not convenience by itself. The real benefit is that packages stop inventing slightly different timestamp formats and timezone assumptions.
|
|
452
|
-
|
|
453
|
-
#### Duplicate-entry helpers
|
|
454
|
-
|
|
455
|
-
Another practical problem is unique-constraint handling.
|
|
456
|
-
|
|
457
|
-
MySQL and Postgres do not report duplicate-entry errors in exactly the same way. If every package checks raw driver error codes by hand, the code becomes repetitive and easy to get wrong.
|
|
458
|
-
|
|
459
|
-
The shared helper gives you one check:
|
|
460
|
-
|
|
461
|
-
```js
|
|
462
|
-
import { isDuplicateEntryError } from "@jskit-ai/database-runtime/shared";
|
|
463
|
-
|
|
464
|
-
try {
|
|
465
|
-
await knex("users").insert({
|
|
466
|
-
email: payload.email
|
|
467
|
-
});
|
|
468
|
-
} catch (error) {
|
|
469
|
-
if (!isDuplicateEntryError(error)) {
|
|
470
|
-
throw error;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Handle the "already exists" case here.
|
|
474
|
-
}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
Why this is useful:
|
|
478
|
-
|
|
479
|
-
- it keeps repository code cleaner
|
|
480
|
-
- it avoids scattering vendor-specific numeric/string error codes everywhere
|
|
481
|
-
- it makes later database portability less painful
|
|
482
|
-
|
|
483
|
-
#### JSON query helpers
|
|
484
|
-
|
|
485
|
-
Some databases expose different SQL syntax for reading JSON fields.
|
|
486
|
-
|
|
487
|
-
The shared helpers smooth over a small but useful part of that difference. For example:
|
|
488
|
-
|
|
489
|
-
```js
|
|
490
|
-
import { whereJsonTextEquals } from "@jskit-ai/database-runtime/shared";
|
|
491
|
-
|
|
492
|
-
const query = knex("contacts");
|
|
493
|
-
whereJsonTextEquals(query, {
|
|
494
|
-
column: "metadata",
|
|
495
|
-
path: "status",
|
|
496
|
-
value: "active"
|
|
497
|
-
});
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
That helper uses the right JSON text expression for the active client instead of forcing every package to write separate MySQL and Postgres raw SQL.
|
|
501
|
-
|
|
502
|
-
This is not a full ORM abstraction layer, and it should not be described that way. It is just a small set of helpers for the database differences JSKIT packages actually care about.
|
|
503
|
-
|
|
504
|
-
#### Transaction and repository helpers
|
|
505
|
-
|
|
506
|
-
The shared surface also includes a few small helpers for transaction-friendly repository code:
|
|
507
|
-
|
|
508
|
-
```js
|
|
509
|
-
import { createWithTransaction } from "@jskit-ai/database-runtime/shared";
|
|
510
|
-
|
|
511
|
-
function createRepository(knex) {
|
|
512
|
-
const withTransaction = createWithTransaction(knex);
|
|
513
|
-
|
|
514
|
-
return {
|
|
515
|
-
withTransaction
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
That pattern shows up in JSKIT packages because it gives repositories a simple standard way to say, "run this unit of work inside a transaction".
|
|
521
|
-
|
|
522
|
-
You do not need to memorize all of these helpers yet. The important thing to understand in this chapter is just that the database layer gives you:
|
|
523
|
-
|
|
524
|
-
- runtime wiring
|
|
525
|
-
- migration tooling
|
|
526
|
-
- a shared persistence utility surface for later packages and app code
|
|
527
|
-
|
|
528
|
-
## What this still does not change yet
|
|
529
|
-
|
|
530
|
-
Installing the database runtime is important, but it is not the same thing as installing the full users/account data model.
|
|
531
|
-
|
|
532
|
-
Right now, after this chapter:
|
|
533
|
-
|
|
534
|
-
- the app can resolve database settings from `.env`
|
|
535
|
-
- the server can create a Knex client
|
|
536
|
-
- the app can run migration commands
|
|
537
|
-
- later packages are allowed to depend on `runtime.database`
|
|
538
|
-
|
|
539
|
-
But the app still does **not** have:
|
|
540
|
-
|
|
541
|
-
- JSKIT user tables
|
|
542
|
-
- JSKIT user settings tables
|
|
543
|
-
- persistent account/profile rows on the JSKIT side
|
|
544
|
-
- workspace tables
|
|
545
|
-
- CRUD tables of its own
|
|
546
|
-
|
|
547
|
-
That means the app's account model still is not database-backed.
|
|
548
|
-
|
|
549
|
-
- local auth is still the real source of truth for auth users and sessions.
|
|
550
|
-
- JSKIT still has a database runtime available.
|
|
551
|
-
- But JSKIT still has **no installed package yet** that projects auth identities into persistent users/account tables.
|
|
552
|
-
|
|
553
|
-
So this chapter is an infrastructure step. It makes the database layer available, but it does not yet install the package that uses that layer for persistent JSKIT-side user data.
|
|
554
|
-
|
|
555
|
-
**Important: Auth Is Not Users-Backed Yet**
|
|
556
|
-
|
|
557
|
-
This is the most important thing to keep straight:
|
|
558
|
-
|
|
559
|
-
- adding `database-runtime-mysql` does **not** automatically change where auth stores credentials or sessions
|
|
560
|
-
- it also does **not** create JSKIT user rows yet
|
|
561
|
-
|
|
562
|
-
That only changes later, when a package such as `users-core` is installed and registers the persistent users-backed `auth.profile.projector`.
|
|
563
|
-
|
|
564
|
-
So after this chapter the app has a database layer, but authentication still behaves like:
|
|
565
|
-
|
|
566
|
-
- real local auth
|
|
567
|
-
- provider identity in the auth session
|
|
568
|
-
|
|
569
|
-
not yet:
|
|
570
|
-
|
|
571
|
-
- real local auth plus a persistent JSKIT-side users layer
|
|
572
|
-
|
|
573
|
-
## Under the hood
|
|
574
|
-
|
|
575
|
-
The interesting files for this chapter are mostly at the top level:
|
|
576
|
-
|
|
577
|
-
```text
|
|
578
|
-
exampleapp/
|
|
579
|
-
.env
|
|
580
|
-
knexfile.js
|
|
581
|
-
migrations/
|
|
582
|
-
.gitkeep
|
|
583
|
-
package.json
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
This is the first chapter where the new behavior is mostly about server infrastructure rather than pages or client layouts.
|
|
587
|
-
|
|
588
|
-
### `package.json` gains database dependencies and scripts
|
|
589
|
-
|
|
590
|
-
After installing the MySQL runtime, the important new pieces in `package.json` look like this:
|
|
591
|
-
|
|
592
|
-
```json
|
|
593
|
-
{
|
|
594
|
-
"dependencies": {
|
|
595
|
-
"@jskit-ai/database-runtime": "0.1.148",
|
|
596
|
-
"@jskit-ai/database-runtime-mysql": "0.1.146",
|
|
597
|
-
"knex": "^3.1.0",
|
|
598
|
-
"mysql2": "^3.11.2"
|
|
599
|
-
},
|
|
600
|
-
"scripts": {
|
|
601
|
-
"db:migrations:sync": "jskit migrations sync",
|
|
602
|
-
"db:migrate": "npm run db:migrations:sync && knex --knexfile ./knexfile.js migrate:latest",
|
|
603
|
-
"db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
|
|
604
|
-
"db:migrate:status": "npm run db:migrations:sync && knex --knexfile ./knexfile.js migrate:list"
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
Those new dependencies divide into two roles:
|
|
610
|
-
|
|
611
|
-
- `@jskit-ai/database-runtime` is the generic JSKIT database runtime
|
|
612
|
-
- `@jskit-ai/database-runtime-mysql` is the MySQL-specific driver package
|
|
613
|
-
- `knex` is the database toolkit used by both runtime code and migration commands
|
|
614
|
-
- `mysql2` is the actual Node driver that speaks to MySQL
|
|
615
|
-
|
|
616
|
-
The migration scripts are also worth reading carefully:
|
|
617
|
-
|
|
618
|
-
- `db:migrations:sync` writes missing immutable package migration files in `migrations/`
|
|
619
|
-
- `db:migrate` syncs JSKIT-managed migration files, then applies all pending Knex migrations
|
|
620
|
-
- `db:migrate:rollback` rolls back the last migration batch
|
|
621
|
-
- `db:migrate:status` syncs JSKIT-managed migration files, then lists applied and pending migrations
|
|
622
|
-
|
|
623
|
-
They are not special JSKIT commands. They are ordinary project scripts, which makes them easy to run in any environment.
|
|
624
|
-
|
|
625
|
-
### `.env` owns the database connection settings
|
|
626
|
-
|
|
627
|
-
The package install also writes the database settings into `.env`:
|
|
29
|
+
Keep credentials outside Git. Supply either `DATABASE_URL` or the individual
|
|
30
|
+
values:
|
|
628
31
|
|
|
629
32
|
```dotenv
|
|
630
|
-
DB_CLIENT=mysql2
|
|
631
33
|
DB_HOST=127.0.0.1
|
|
632
34
|
DB_PORT=3306
|
|
633
35
|
DB_NAME=exampleapp
|
|
@@ -635,209 +37,127 @@ DB_USER=exampleapp
|
|
|
635
37
|
DB_PASSWORD=secret
|
|
636
38
|
```
|
|
637
39
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
40
|
+
Use port `5432` for PostgreSQL. A single-driver app fixes its dialect in
|
|
41
|
+
`knexfile.js`, so it does not need `DB_CLIENT`. The running database provider
|
|
42
|
+
also derives its dialect from the installed driver and rejects a conflicting
|
|
43
|
+
`DB_CLIENT` value when one is supplied.
|
|
642
44
|
|
|
643
|
-
|
|
45
|
+
The application or hosting environment creates the database and provides its
|
|
46
|
+
credentials. JSKIT never guesses or commits them.
|
|
644
47
|
|
|
645
|
-
|
|
48
|
+
## Migration configuration
|
|
646
49
|
|
|
647
|
-
|
|
50
|
+
Use the fixed-dialect pattern owned by the selected driver. The important
|
|
51
|
+
application file is small:
|
|
648
52
|
|
|
649
53
|
```js
|
|
650
|
-
import
|
|
651
|
-
|
|
652
|
-
import {
|
|
653
|
-
normalizeText,
|
|
654
|
-
toKnexClientId,
|
|
655
|
-
resolveDatabaseClientFromEnvironment,
|
|
656
|
-
resolveKnexConnectionFromEnvironment
|
|
657
|
-
} from "@jskit-ai/database-runtime/shared";
|
|
658
|
-
|
|
659
|
-
const appRoot = process.cwd();
|
|
660
|
-
dotenv.config({
|
|
661
|
-
path: path.join(appRoot, ".env"),
|
|
662
|
-
quiet: true
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
const dialectId = resolveDatabaseClientFromEnvironment(process.env);
|
|
666
|
-
const client = toKnexClientId(dialectId);
|
|
667
|
-
const defaultPort = dialectId === "pg" ? 5432 : 3306;
|
|
668
|
-
const migrationsDirectory = path.resolve(appRoot, normalizeText(process.env.DB_MIGRATIONS_DIR) || "migrations");
|
|
669
|
-
const deferredConstraintsDirectory = path.join(migrationsDirectory, "constraints");
|
|
670
|
-
|
|
671
|
-
export default {
|
|
672
|
-
client,
|
|
673
|
-
connection: resolveKnexConnectionFromEnvironment(process.env, {
|
|
674
|
-
client: dialectId,
|
|
675
|
-
defaultPort,
|
|
676
|
-
context: "knex migrations"
|
|
677
|
-
}),
|
|
678
|
-
migrations: {
|
|
679
|
-
directory: [migrationsDirectory, deferredConstraintsDirectory],
|
|
680
|
-
extension: "cjs",
|
|
681
|
-
sortDirsSeparately: true
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
The important thing to understand is what this file is **for**.
|
|
54
|
+
import { createKnexMigrationConfigFromApp } from
|
|
55
|
+
"@jskit-ai/database-runtime/server/knexMigrationConfig";
|
|
687
56
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
```bash
|
|
691
|
-
npm run db:migrate
|
|
57
|
+
export default await createKnexMigrationConfigFromApp({ client: "mysql2" });
|
|
692
58
|
```
|
|
693
59
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
- `knexfile.js` for migration commands
|
|
697
|
-
- the JSKIT server provider runtime for application code
|
|
698
|
-
|
|
699
|
-
That separation is good. It keeps the operational CLI workflow and the app runtime wiring clear.
|
|
60
|
+
Use `client: "pg"` for PostgreSQL. The complete examples are in:
|
|
700
61
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
`migrations/constraints/`. CRUD scaffolding uses that second phase for foreign
|
|
704
|
-
keys, so two tables may validly reference one another without either
|
|
705
|
-
table-creation migration depending on a table that has not been created yet.
|
|
706
|
-
Rollback reverses the order and removes those constraints before dropping
|
|
707
|
-
tables.
|
|
62
|
+
- `database/mysql-application`
|
|
63
|
+
- `database/postgres-application`
|
|
708
64
|
|
|
709
|
-
|
|
65
|
+
The config discovers migrations directly from the current installed package
|
|
66
|
+
graph. Application migrations live in the app's `migrations/` directory;
|
|
67
|
+
package-owned migrations live in directories declared by the installed
|
|
68
|
+
package's `package.json#jskit.migrations.directories`.
|
|
710
69
|
|
|
711
|
-
|
|
70
|
+
There is no migration sync command and no copied migration projection. Knex
|
|
71
|
+
runs the authoritative files where their owners ship them.
|
|
712
72
|
|
|
713
|
-
|
|
73
|
+
## What runs migrations
|
|
714
74
|
|
|
715
|
-
|
|
716
|
-
class DatabaseRuntimeMysqlServiceProvider {
|
|
717
|
-
static id = "runtime.database.driver.mysql";
|
|
75
|
+
The application owns normal npm scripts:
|
|
718
76
|
|
|
719
|
-
|
|
720
|
-
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"scripts": {
|
|
80
|
+
"db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
|
|
81
|
+
"db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
|
|
82
|
+
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
|
|
721
83
|
}
|
|
722
84
|
}
|
|
723
85
|
```
|
|
724
86
|
|
|
725
|
-
|
|
87
|
+
`npm run db:migrate` runs the Knex CLI. Knex loads `knexfile.js`, discovers the
|
|
88
|
+
application and installed-package migration directories, connects using the
|
|
89
|
+
environment, and applies pending migrations.
|
|
726
90
|
|
|
727
|
-
|
|
91
|
+
Deployment or a managed development environment may invoke this app-owned
|
|
92
|
+
script as a release step. JSKIT itself does not maintain a background migration
|
|
93
|
+
service.
|
|
728
94
|
|
|
729
|
-
|
|
730
|
-
class DatabaseRuntimeServiceProvider {
|
|
731
|
-
static id = "runtime.database";
|
|
732
|
-
|
|
733
|
-
register(app) {
|
|
734
|
-
app.singleton("runtime.database", () => DATABASE_RUNTIME_SERVER_API);
|
|
95
|
+
## Authoring schema changes
|
|
735
96
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
97
|
+
Write a new immutable migration in the package that owns the schema. Never edit
|
|
98
|
+
an already-applied migration and never change a live database without recording
|
|
99
|
+
the equivalent source-controlled migration.
|
|
739
100
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
101
|
+
Package migrations are normal `.cjs` Knex migrations and are declared in that
|
|
102
|
+
package's metadata:
|
|
743
103
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"jskit": {
|
|
107
|
+
"migrations": {
|
|
108
|
+
"directories": ["migrations"]
|
|
749
109
|
}
|
|
750
110
|
}
|
|
751
111
|
}
|
|
752
112
|
```
|
|
753
113
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
- `
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
So the auth layer keeps behaving the same way it did before:
|
|
805
|
-
|
|
806
|
-
- local auth still owns the auth user and session
|
|
807
|
-
- JSKIT can still display the provider identity from the auth session
|
|
808
|
-
- there is still no persistent JSKIT users/account model yet
|
|
809
|
-
|
|
810
|
-
The database runtime is ready, but the users layer that will actually use it has not been installed yet.
|
|
811
|
-
|
|
812
|
-
### Why the empty `migrations/` directory is important
|
|
813
|
-
|
|
814
|
-
The new `migrations/` directory can look almost silly at first because it only contains `.gitkeep`. But that empty directory is the cleanest signal of what this chapter really does.
|
|
815
|
-
|
|
816
|
-
It means:
|
|
817
|
-
|
|
818
|
-
- the app has a migration system
|
|
819
|
-
- the app does **not** yet have a schema of its own
|
|
820
|
-
|
|
821
|
-
That is exactly the right state at this stage of the guide.
|
|
822
|
-
|
|
823
|
-
The database runtime chapter should give the app a database foundation first. The next data-heavy chapters can then install actual schema migrations on top of that foundation.
|
|
824
|
-
|
|
825
|
-
## Summary
|
|
826
|
-
|
|
827
|
-
This chapter did not make the app feel dramatically different in the browser, but it changed the server foundation in an important way.
|
|
828
|
-
|
|
829
|
-
- the app has a real JSKIT database runtime
|
|
830
|
-
- the app has a standard Knex migration workflow
|
|
831
|
-
- the app has a place for future schema files
|
|
832
|
-
|
|
833
|
-
But just as importantly, this chapter also defined what has **not** changed yet:
|
|
834
|
-
|
|
835
|
-
- auth still uses the local provider's own backend
|
|
836
|
-
- JSKIT still has no persistent users layer of its own
|
|
837
|
-
- no feature package has started storing real app data yet
|
|
838
|
-
|
|
839
|
-
So the right mental model at the end of this chapter is:
|
|
840
|
-
|
|
841
|
-
- local auth already handles real authentication
|
|
842
|
-
- MySQL is wired up and ready
|
|
843
|
-
- the persistent JSKIT-side user model arrives in the next chapter
|
|
114
|
+
Names must remain unique across the effective migration directories. Keep
|
|
115
|
+
constraints in a later migration when ordering matters.
|
|
116
|
+
|
|
117
|
+
## Seed data is not a migration
|
|
118
|
+
|
|
119
|
+
Migrations establish schema and invariant framework data. Product fixtures,
|
|
120
|
+
sample accounts, catalog content, and other environment-specific starting data
|
|
121
|
+
belong in an explicit, idempotent application seed operation that runs after
|
|
122
|
+
migrations. Do not hide product seeding in schema migrations.
|
|
123
|
+
|
|
124
|
+
Managed editors must be able to create one isolated database per development
|
|
125
|
+
session, apply the full migration graph, then invoke that explicit seed
|
|
126
|
+
operation. JSKIT supplies portable migration and seed seams; the editor owns
|
|
127
|
+
database allocation, credentials, lifetime, and environment injection.
|
|
128
|
+
|
|
129
|
+
## Resource services and custom operations
|
|
130
|
+
|
|
131
|
+
Conventional persisted resources use `defineCrudResource()` and
|
|
132
|
+
`defineCrudJsonApiFeature()` so the framework owns repeated repository,
|
|
133
|
+
service, action, permission, JSON API, and route mechanics. This does not make
|
|
134
|
+
product CRUD behavior fixed.
|
|
135
|
+
|
|
136
|
+
- `decorateRepository` adds resource-specific queries, locks, or writes.
|
|
137
|
+
- `decorateService` overrides a standard method or adds domain methods such as
|
|
138
|
+
`confirm`, `publish`, `cancel`, or `sendReminder`.
|
|
139
|
+
- `operationLifecycle` surrounds a standard operation with `before`, `execute`,
|
|
140
|
+
`after`, and mutation-only `afterCommit` phases. Create, update, and delete
|
|
141
|
+
phases before commit share one repository transaction, and `execute` receives
|
|
142
|
+
`standard(nextInput)` for retaining the normal framework write.
|
|
143
|
+
- Named `actions` expose non-CRUD service methods through normal input,
|
|
144
|
+
permission, audit, event, and optional HTTP route contracts.
|
|
145
|
+
|
|
146
|
+
Repositories own database access. Services and lifecycle hooks orchestrate
|
|
147
|
+
repositories. External delivery belongs after commit; when it must be durable,
|
|
148
|
+
write an outbox record inside the transaction and deliver it separately.
|
|
149
|
+
A separate Feature is warranted when an operation belongs to another domain,
|
|
150
|
+
not merely because a useful resource has behavior beyond list and save.
|
|
151
|
+
|
|
152
|
+
## Verification
|
|
153
|
+
|
|
154
|
+
- Rebuild a disposable database from the complete migration graph.
|
|
155
|
+
- Run `npm run db:migrate:status` after migration.
|
|
156
|
+
- Exercise a real transaction and one invalid-connection case.
|
|
157
|
+
- When a seed operation exists, run it twice and require the second run to be
|
|
158
|
+
safe.
|
|
159
|
+
- Test MySQL and PostgreSQL patterns independently.
|
|
160
|
+
|
|
161
|
+
Do not add migration receipts, sync ledgers, generator provenance, or dialect
|
|
162
|
+
questionnaires. The installed graph, migration source, environment, and
|
|
163
|
+
database migration table are sufficient.
|