@jskit-ai/agent-docs 0.1.131 → 0.1.133
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/existing-application-migration.md +273 -0
- 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 +17 -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 +38 -31
- 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 +1 -1
- 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
|
@@ -1,806 +1,72 @@
|
|
|
1
1
|
<!-- Generated by `npm run agent-docs:build` from `packages/agent-docs/site/guide/app-setup/initial-scaffolding.md`. Do not edit manually. -->
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
---
|
|
4
|
+
title: Application foundations
|
|
5
|
+
description: Understand and adapt JSKIT's tested minimal and adaptive-shell source patterns.
|
|
6
|
+
---
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
# Application foundations
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
JSKIT publishes tested source patterns instead of constructing an application
|
|
11
|
+
through a questionnaire. A foundation is a concrete reference application an
|
|
12
|
+
agent can copy, adapt, compose, or use as evidence.
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
npx @jskit-ai/create-app exampleapp --tenancy-mode none
|
|
11
|
-
cd exampleapp
|
|
12
|
-
npm install
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
The first command creates a new folder called `exampleapp` and fills it with JSKIT's default shell-web app template. The `exampleapp` name is used in a few template replacements, such as the package name and the browser title. The `--tenancy-mode none` flag tells JSKIT to start with the smallest routing model. In this mode, the app is not workspace-aware (more of this later in the guide, when multihoming is introduced). That keeps the first scaffold easier to read because there is no workspace slug handling yet.
|
|
16
|
-
|
|
17
|
-
If you are working with an AI agent and want the agent to drive the initial JSKIT setup conversation, use the dedicated seed path:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npx @jskit-ai/create-app exampleapp --template ai-seed
|
|
21
|
-
cd exampleapp
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
That seed writes only `AGENTS.md`. It is not a runnable app yet. The agent should use that file to ask the Stage 1 platform questions first, make sure the chosen MySQL or Postgres database already exists or can be created with the developer's local admin access, and then promote the same directory into the real scaffold with:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npx @jskit-ai/create-app exampleapp --target . --force --tenancy-mode <mode>
|
|
28
|
-
npm install
|
|
29
|
-
npx jskit migrations sync
|
|
30
|
-
npx jskit ci generate
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
After that promotion, the overwritten app `AGENTS.md` stays deliberately small. Use it with the distributed JSKIT agent docs when planning or implementing app changes. The durable app memory lives in `.jskit/APP_BLUEPRINT.md` and should describe product and architecture decisions, not become an implementation task list.
|
|
34
|
-
|
|
35
|
-
After creating the real app scaffolding (the default shell-web app, not the seed wrapper), you will need to run `npm install` to install dependencies.
|
|
36
|
-
|
|
37
|
-
If you deliberately need the bare scaffold, use `--minimal` or `--template minimal-shell`. That is useful for package-development flows, but it is not the normal starting point for a JSKIT app:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npx @jskit-ai/create-app exampleapp --minimal --tenancy-mode none
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Minimal apps can still install the standard shell later with
|
|
44
|
-
`npx --no-install jskit add package shell-web`, as long as the starter files it
|
|
45
|
-
claims have not been edited first. After the initial `npm install`, prefer
|
|
46
|
-
`npx --no-install jskit` for every local JSKIT command so a missing local CLI
|
|
47
|
-
fails clearly.
|
|
48
|
-
|
|
49
|
-
When a minimal app's first feature is generated CRUD, do not pre-install the
|
|
50
|
-
shell as a workaround. Add the database runtime, create the live disposable
|
|
51
|
-
table, run `crud-server-generator scaffold`, and then run
|
|
52
|
-
`crud-ui-generator crud`. The server
|
|
53
|
-
generator installs the shell/realtime dependency closure in the correct order.
|
|
54
|
-
The complete Notes command lane is in [CRUD Generators](/guide/generators/crud-generators#fresh-minimal-notes-app-the-complete-command-order).
|
|
55
|
-
|
|
56
|
-
If you already know you want a small non-workspace baseline right after the scaffold, this is the shortest reproducible path:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
DB_HOST=127.0.0.1
|
|
60
|
-
DB_PORT=3306
|
|
61
|
-
DB_NAME=exampleapp
|
|
62
|
-
DB_USER=...
|
|
63
|
-
DB_PASSWORD=...
|
|
64
|
-
|
|
65
|
-
npx @jskit-ai/create-app exampleapp --tenancy-mode none
|
|
66
|
-
cd exampleapp
|
|
67
|
-
npm install
|
|
68
|
-
|
|
69
|
-
npx jskit add package auth-provider-local-core
|
|
70
|
-
npx jskit add package auth-web
|
|
71
|
-
|
|
72
|
-
npx jskit add package database-runtime-mysql \
|
|
73
|
-
--db-host "$DB_HOST" \
|
|
74
|
-
--db-port "$DB_PORT" \
|
|
75
|
-
--db-name "$DB_NAME" \
|
|
76
|
-
--db-user "$DB_USER" \
|
|
77
|
-
--db-password "$DB_PASSWORD"
|
|
78
|
-
|
|
79
|
-
npx jskit add package users-web
|
|
80
|
-
npx jskit add package console-web
|
|
81
|
-
|
|
82
|
-
npm run db:migrate
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
The default auth install is intentionally local and simple. Do not start a new app by adding Supabase, OAuth, OTP, provider linking, or a users/profile projection unless that complexity is already part of the app you are building.
|
|
86
|
-
|
|
87
|
-
If you want the larger workspace-enabled stack with the first assistant already configured, use [Quickstart](/guide/app-setup/quickstart) instead.
|
|
88
|
-
|
|
89
|
-
**Try Bash Completion!**
|
|
90
|
-
|
|
91
|
-
Once `npm install` has finished, you can enable Bash completion for the JSKIT CLI. If you only want it for the current shell session, run:
|
|
14
|
+
## The adaptive-shell foundation
|
|
92
15
|
|
|
93
|
-
|
|
94
|
-
source <(npx jskit completion bash)
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
If you want JSKIT completion to keep working in future Bash sessions as well, run:
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
npx jskit completion bash --install
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
That writes a small loader file into your home directory and updates `~/.bashrc` for you. To activate it in the current shell immediately, run `source ~/.bashrc`.
|
|
104
|
-
|
|
105
|
-
Once completion is loaded, you can test it immediately.
|
|
106
|
-
|
|
107
|
-
If you type:
|
|
16
|
+
`app/shell-foundation` is the normal browser-product baseline. It includes:
|
|
108
17
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
18
|
+
- Fastify server and health endpoint
|
|
19
|
+
- Vue/Vite client bootstrap
|
|
20
|
+
- app-local provider package
|
|
21
|
+
- responsive JSKIT shell
|
|
22
|
+
- surfaces, placements, topology, and settings
|
|
23
|
+
- lint, unit, client, build, and browser configuration
|
|
24
|
+
- managed-preview identity support when the host uses it
|
|
114
25
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
npx jskit add p
|
|
119
|
-
```
|
|
26
|
+
Use it when navigation and settings are real product requirements. Replace its
|
|
27
|
+
home content and vocabulary; do not preserve the concrete `reading-room` name
|
|
28
|
+
when it is wrong for the product.
|
|
120
29
|
|
|
121
|
-
|
|
30
|
+
## The minimal foundation
|
|
122
31
|
|
|
123
|
-
|
|
32
|
+
`app/minimal-foundation` keeps the same server/client/package boundaries but
|
|
33
|
+
does not install the full adaptive shell. Use it for deliberately small or
|
|
34
|
+
unusual products, not as a temporary hurdle before installing what was already
|
|
35
|
+
known to be required.
|
|
124
36
|
|
|
125
|
-
|
|
37
|
+
## Safe realization inside an existing repository
|
|
126
38
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
npm run server
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Then open `http://localhost:5173/` in the browser. The starter screen is intentionally small. That is a good thing. It proves the shell is wired correctly before we start adding packages.
|
|
136
|
-
|
|
137
|
-
`npm run server` starts the Fastify server on port `3000`. The default home page already uses the Vite proxy to request `/api/health`, so keep the backend running when you want the starter status to be fully green. A good habit is to treat `npm run dev` as the browser-facing process and `npm run server` as the app runtime behind it.
|
|
138
|
-
|
|
139
|
-
If you want a fast sanity check that the backend is alive, open `http://localhost:3000/api/health` or request it from the terminal:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
curl http://localhost:3000/api/health
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
You should get a small JSON response with `ok: true`.
|
|
146
|
-
|
|
147
|
-
## Reading the scaffold
|
|
148
|
-
|
|
149
|
-
A fresh app has more structure than a plain Vue starter because JSKIT is preparing both a web shell and an application runtime from the beginning. The top-level layout looks roughly like this:
|
|
39
|
+
Read the entire selected `PATTERN.md` first. Copy or author files directly in
|
|
40
|
+
the initialized project. Always preserve `.git`; when present, also preserve
|
|
41
|
+
existing project and agent context, including roots such as:
|
|
150
42
|
|
|
151
43
|
```text
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
packages/main/
|
|
156
|
-
server/
|
|
157
|
-
src/
|
|
158
|
-
tests/
|
|
159
|
-
package.json
|
|
160
|
-
server.js
|
|
161
|
-
vite.config.mjs
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
The first file most people should read is `package.json`. It is the command center for the app. It tells you how to run the frontend (`npm run dev`), the backend (`npm run server`), the test suite, and the build. It also shows the most important dependencies that make the starter shell work: Vue, Vite, Fastify, the JSKIT kernel, and the HTTP runtime.
|
|
165
|
-
|
|
166
|
-
The most important parts look like this:
|
|
167
|
-
|
|
168
|
-
```json
|
|
169
|
-
{
|
|
170
|
-
"engines": {
|
|
171
|
-
"node": "26.x"
|
|
172
|
-
},
|
|
173
|
-
"scripts": {
|
|
174
|
-
"server": "node ./bin/server.js",
|
|
175
|
-
"server:all": "node ./bin/server.js",
|
|
176
|
-
"server:home": "SERVER_SURFACE=home node ./bin/server.js",
|
|
177
|
-
"dev": "vite",
|
|
178
|
-
"dev:all": "vite",
|
|
179
|
-
"dev:home": "VITE_SURFACE=home vite",
|
|
180
|
-
"build": "vite build",
|
|
181
|
-
"build:all": "vite build",
|
|
182
|
-
"build:home": "VITE_SURFACE=home vite build",
|
|
183
|
-
"preview": "vite preview",
|
|
184
|
-
"lint": "eslint .",
|
|
185
|
-
"test": "node --test",
|
|
186
|
-
"test:client": "vitest run tests/client",
|
|
187
|
-
"test:e2e": "playwright test tests/e2e",
|
|
188
|
-
"verify": "jskit app verify && npm run --if-present verify:app",
|
|
189
|
-
"release": "jskit app release",
|
|
190
|
-
"jskit:update": "jskit app update-packages"
|
|
191
|
-
},
|
|
192
|
-
"dependencies": {
|
|
193
|
-
"@local/main": "file:packages/main",
|
|
194
|
-
"@fastify/static": "^9.1.3",
|
|
195
|
-
"@jskit-ai/kernel": "0.1.148",
|
|
196
|
-
"@tanstack/vue-query": "^5.101.0",
|
|
197
|
-
"@jskit-ai/http-runtime": "0.1.146",
|
|
198
|
-
"@jskit-ai/shell-web": "0.1.152",
|
|
199
|
-
"fastify": "^5.8.5",
|
|
200
|
-
"json-rest-schema": "^1.0.17",
|
|
201
|
-
"pinia": "^3.0.4",
|
|
202
|
-
"vue": "^3.5.38",
|
|
203
|
-
"vue-router": "^5.1.0",
|
|
204
|
-
"vuetify": "^4.1.2"
|
|
205
|
-
},
|
|
206
|
-
"devDependencies": {
|
|
207
|
-
"@jskit-ai/agent-docs": "0.1.119",
|
|
208
|
-
"@jskit-ai/config-eslint": "0.1.145",
|
|
209
|
-
"@jskit-ai/jskit-cli": "0.2.179",
|
|
210
|
-
"@playwright/test": "1.61.1",
|
|
211
|
-
"@vitejs/plugin-vue": "^6.0.7",
|
|
212
|
-
"eslint": "^10.8.0",
|
|
213
|
-
"vite": "^8.2.1",
|
|
214
|
-
"vitest": "^4.1.9"
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Published JSKIT libraries and tooling support Node.js 22 from 22.13.0 onward, Node.js 24, and Node.js 26. Newly generated applications deliberately require Node 26: their app-level `engines` contract, `.nvmrc`, and generated verification workflow all name that runtime. The app-level contract is the runtime boundary for the app and its installed JSKIT runtime packages, while independently consumed JSKIT CLI and tooling packages retain the wider supported range. The dependency on `@local/main` points at `file:packages/main`, which means your app already contains its own local JSKIT package. The maintenance scripts are also useful to notice early, because they show an important ownership boundary in JSKIT.
|
|
220
|
-
|
|
221
|
-
`verify`, `jskit:update`, and `release` are intentionally thin wrappers. They stay in `package.json` because they are convenient app-local shortcuts, but the real implementation lives in `jskit app ...`, not in copied scaffold scripts.
|
|
222
|
-
|
|
223
|
-
That matters because JSKIT maintenance policy changes over time. If the scaffold copied a large shell script into every app, existing apps would freeze the old behavior forever. By delegating to `jskit app verify`, `jskit app update-packages`, and `jskit app release`, the app keeps the nice `npm run` shortcuts while the maintained behavior stays in the installed CLI package.
|
|
224
|
-
|
|
225
|
-
The Playwright scaffold follows the same rule. `playwright.config.mjs` delegates to `@jskit-ai/jskit-cli/test/playwright`, and the starter browser specs delegate their shared responsive checks to published JSKIT helpers. The generated files stay small while later JSKIT package updates can change local server startup, managed `PLAYWRIGHT_BASE_URL` handling, and `VIBE64_PLAYWRIGHT_STORAGE_STATE` support without copying that logic into each new app.
|
|
226
|
-
|
|
227
|
-
The scaffold also configures `src/typed-router.d.ts` as generated output. It is intentionally absent from a fresh scaffold and ignored by Git; the Vue Router Vite plugin writes it during the first `npm run dev` or `npm run build` route scan.
|
|
228
|
-
|
|
229
|
-
`jskit app verify` is worth noticing specifically. Linting, tests, and builds check your source code and runtime behavior. The JSKIT part of that flow runs `doctor`, which checks the installed package graph, capabilities, migrations, generated CI, surfaces, placements, and other JSKIT-specific health rules.
|
|
230
|
-
|
|
231
|
-
After `npm install`, run `npx jskit ci generate` to write `.github/workflows/jskit-verify.yml` from installed package `ci` contracts. The baseline runs checkout, Node 26 setup, `npm ci`, and `npm run verify`. Packages can add job environment values, service containers, and explicit `before-verify` steps. For example, the database runtime adds migrations before verification. Its MySQL driver adds a MariaDB service with synthetic CI-only credentials and `DB_CLIENT=mysql2`; its Postgres driver adds the equivalent Postgres service and `DB_CLIENT=pg`.
|
|
232
|
-
|
|
233
|
-
`npx jskit ci generate` replaces that one workflow in full. Do not edit it. Put application-specific CI in separate workflow files, and use `npx jskit ci generate --check` in validation lanes.
|
|
234
|
-
|
|
235
|
-
The surface-specific script names are also worth noticing early, even in this tiny app. `dev:home`, `server:home`, and `build:home` are the first concrete places where surface selection shows up in the scaffold. They work by setting `VITE_SURFACE=home` on the client side and `SERVER_SURFACE=home` on the server side. In this first chapter, where `home` is the only surface, those variants behave almost the same as the default commands. Later, once more surfaces exist, those scripts become the simplest way to run or build just one surface at a time.
|
|
236
|
-
|
|
237
|
-
### App surfaces in JSKIT
|
|
238
|
-
|
|
239
|
-
A surface is JSKIT's name for a named slice of the application. They are a very important concept in JSKIT, since a surface can be built -- and deployed -- separately from each other. This is useful if for example you want the end-user interface _not_ to contain _any_ of the symbols/strings of the admin interface.
|
|
240
|
-
|
|
241
|
-
Surfaces are defined in a very important file in JSKIT: `config/public.js`. This is the app's shared public configuration, used both by client and server. It's called "public" because it _will_ be read by the browser, and therefore it _will_ be available to the world. It defines the current tenancy mode, the default surface, and the list of surface definitions. In this first scaffold there is only one surface, `home`, which is the starter surface
|
|
242
|
-
|
|
243
|
-
Even though we are using `--tenancy-mode none`, it will still be possible to add more surfaces. Every app starts with a single `home` surface, and later packages will expand that topology.
|
|
244
|
-
|
|
245
|
-
Here is the part of `config/public.js` that sets that up:
|
|
246
|
-
|
|
247
|
-
```js
|
|
248
|
-
import { surfaceAccessPolicies } from "./surfaceAccessPolicies.js";
|
|
249
|
-
|
|
250
|
-
export const config = {};
|
|
251
|
-
config.tenancyMode = "none";
|
|
252
|
-
|
|
253
|
-
config.surfaceModeAll = "all";
|
|
254
|
-
config.surfaceDefaultId = "home";
|
|
255
|
-
config.webRootAllowed = "no";
|
|
256
|
-
config.surfaceAccessPolicies = surfaceAccessPolicies;
|
|
257
|
-
config.surfaceDefinitions = {};
|
|
258
|
-
config.surfaceDefinitions.home = {
|
|
259
|
-
id: "home",
|
|
260
|
-
label: "Home",
|
|
261
|
-
pagesRoot: "home",
|
|
262
|
-
enabled: true,
|
|
263
|
-
requiresAuth: false,
|
|
264
|
-
requiresWorkspace: false,
|
|
265
|
-
accessPolicyId: "public",
|
|
266
|
-
origin: ""
|
|
267
|
-
};
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
Right next to that file is `config/surfaceAccessPolicies.js`. This is where the access rules for surfaces live. In the initial shell, `home` uses the `public` policy. You do not need to change these policies now, but you do need to know where they come from, because later packages will extend them.
|
|
271
|
-
|
|
272
|
-
The starter policies are small enough to read in one glance:
|
|
273
|
-
|
|
274
|
-
```js
|
|
275
|
-
export const surfaceAccessPolicies = {};
|
|
276
|
-
|
|
277
|
-
surfaceAccessPolicies.public = {};
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
That tells you one thing immediately: `home` is open. More specific policies only appear when later packages add them.
|
|
281
|
-
|
|
282
|
-
### The client side
|
|
283
|
-
|
|
284
|
-
#### Client bootstrap
|
|
285
|
-
|
|
286
|
-
The `src/` directory is the frontend application. `src/main.js` is the real boot file. It creates the Vue app, sets up the router, enables Vuetify, and builds a JSKIT surface runtime from `config/public.js`. That one file is worth reading carefully because it shows the main client-side contract of a JSKIT app: scaffold config is turned into a running client shell, with the surface runtime, router, installed client modules, and app boot all wired together.
|
|
287
|
-
|
|
288
|
-
The important part looks like this:
|
|
289
|
-
|
|
290
|
-
```js
|
|
291
|
-
import { createApp } from "vue";
|
|
292
|
-
import { createPinia } from "pinia";
|
|
293
|
-
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
|
|
294
|
-
import { createRouter, createWebHistory } from "vue-router/auto";
|
|
295
|
-
import { routes } from "vue-router/auto-routes";
|
|
296
|
-
import "vuetify/styles";
|
|
297
|
-
import { createVuetify } from "vuetify";
|
|
298
|
-
import { aliases as mdiAliases, mdi } from "vuetify/iconsets/mdi-svg";
|
|
299
|
-
import { createSurfaceRuntime } from "@jskit-ai/kernel/shared/surface/runtime";
|
|
300
|
-
import {
|
|
301
|
-
shouldRetryTransientQueryFailure,
|
|
302
|
-
transientQueryRetryDelay
|
|
303
|
-
} from "@jskit-ai/kernel/shared/support";
|
|
304
|
-
import {
|
|
305
|
-
bootstrapClientShellApp,
|
|
306
|
-
createShellRouter
|
|
307
|
-
} from "@jskit-ai/kernel/client";
|
|
308
|
-
import { bootInstalledClientModules } from "virtual:jskit-client-bootstrap";
|
|
309
|
-
import App from "./App.vue";
|
|
310
|
-
import NotFoundView from "./views/NotFound.vue";
|
|
311
|
-
import { config } from "../config/public.js";
|
|
312
|
-
|
|
313
|
-
const surfaceRuntime = createSurfaceRuntime({
|
|
314
|
-
allMode: config.surfaceModeAll,
|
|
315
|
-
surfaces: config.surfaceDefinitions,
|
|
316
|
-
defaultSurfaceId: config.surfaceDefaultId
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const surfaceMode = surfaceRuntime.normalizeSurfaceMode(import.meta.env.VITE_SURFACE);
|
|
320
|
-
const { router, fallbackRoute } = createShellRouter({
|
|
321
|
-
createRouter,
|
|
322
|
-
history: createWebHistory(),
|
|
323
|
-
routes,
|
|
324
|
-
surfaceRuntime,
|
|
325
|
-
surfaceMode,
|
|
326
|
-
notFoundComponent: NotFoundView,
|
|
327
|
-
guard: {
|
|
328
|
-
surfaceDefinitions: config.surfaceDefinitions,
|
|
329
|
-
defaultSurfaceId: config.surfaceDefaultId,
|
|
330
|
-
webRootAllowed: config.webRootAllowed
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
const vuetify = createVuetify({
|
|
335
|
-
theme: {
|
|
336
|
-
defaultTheme: "light"
|
|
337
|
-
},
|
|
338
|
-
icons: {
|
|
339
|
-
defaultSet: "mdi",
|
|
340
|
-
aliases: mdiAliases,
|
|
341
|
-
sets: { mdi }
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
const pinia = createPinia();
|
|
345
|
-
const queryClient = new QueryClient({
|
|
346
|
-
defaultOptions: {
|
|
347
|
-
queries: {
|
|
348
|
-
refetchOnWindowFocus: false,
|
|
349
|
-
refetchOnReconnect: true,
|
|
350
|
-
retry: shouldRetryTransientQueryFailure,
|
|
351
|
-
retryDelay: transientQueryRetryDelay
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
void bootstrapClientShellApp({
|
|
357
|
-
createApp,
|
|
358
|
-
rootComponent: App,
|
|
359
|
-
appConfig: config,
|
|
360
|
-
appPlugins: [
|
|
361
|
-
pinia,
|
|
362
|
-
[VueQueryPlugin, { queryClient }],
|
|
363
|
-
vuetify
|
|
364
|
-
],
|
|
365
|
-
pinia,
|
|
366
|
-
queryClient,
|
|
367
|
-
router,
|
|
368
|
-
bootClientModules: bootInstalledClientModules,
|
|
369
|
-
surfaceRuntime,
|
|
370
|
-
surfaceMode,
|
|
371
|
-
env: import.meta.env,
|
|
372
|
-
fallbackRoute
|
|
373
|
-
}).catch((error) => {
|
|
374
|
-
console.error("Failed to bootstrap client app.", error);
|
|
375
|
-
});
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
The flow is simple once you read it in order: config in, runtime in memory, router built from that runtime, app-owned Vue plugins created once, app bootstrapped. Pinia, Vue Query, the router, and Vuetify are owned by the base app bootstrap so runtime packages share the same instances instead of carrying independent copies. Passing the same `queryClient` into `bootstrapClientShellApp(...)` also gives `shell-web` the QueryClient it observes for automatic request recovery when transport failures exhaust their normal retries.
|
|
379
|
-
|
|
380
|
-
<DocsInDepth title="In depth" preview-height="15rem">
|
|
381
|
-
|
|
382
|
-
`createSurfaceRuntime(...)` turns the static surface config into a small runtime registry that both the router and later client modules can query. In the starter app, the input data is basically this:
|
|
383
|
-
|
|
384
|
-
```js
|
|
385
|
-
config.surfaceDefinitions = {
|
|
386
|
-
home: {
|
|
387
|
-
pagesRoot: "home", // this surface lives under src/pages/home
|
|
388
|
-
requiresAuth: false,
|
|
389
|
-
accessPolicyId: "public"
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
config.surfaceDefaultId = "home"; // default surface
|
|
394
|
-
config.surfaceModeAll = "all"; // unrestricted surface mode
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
That means the `home` surface itself lives under `/home`. Because `home` is also the default surface and the starter scaffold sets `webRootAllowed = "no"`, visiting `/` redirects to `/home`.
|
|
398
|
-
|
|
399
|
-
That gives the client a normalized surface runtime with answers to questions such as:
|
|
400
|
-
|
|
401
|
-
```js
|
|
402
|
-
surfaceRuntime.DEFAULT_SURFACE_ID; // "home"
|
|
403
|
-
surfaceRuntime.listEnabledSurfaceIds(); // ["home"]
|
|
404
|
-
surfaceRuntime.resolveSurfaceFromPathname("/home"); // "home"
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
`surfaceMode` is not another surface definition. It is the current viewing mode for the app. In a starter app, `VITE_SURFACE` is usually unset, so `surfaceMode` becomes `"all"`, meaning "do not restrict the router to one specific surface". Later, when you run surface-specific profiles, the same runtime can narrow the active routes to just one surface. In the scaffold scripts, `npm run dev:home` is simply setting `VITE_SURFACE=home`, while `npm run dev` and `npm run dev:all` leave the client in unrestricted `"all"` mode.
|
|
408
|
-
|
|
409
|
-
`createShellRouter(...)` uses that `surfaceRuntime` object to assemble the actual router. Concretely, it does this:
|
|
410
|
-
|
|
411
|
-
1. Takes the generated Vue routes.
|
|
412
|
-
2. Adds a fallback not-found route.
|
|
413
|
-
3. Filters those routes down to the ones that belong to the current surface mode.
|
|
414
|
-
4. Calls Vue Router's real `createRouter(...)` with that filtered route list.
|
|
415
|
-
5. Installs a `beforeEach` guard for surface-aware redirects and access behavior.
|
|
416
|
-
|
|
417
|
-
So this is not a separate routing system. It is a thin JSKIT wrapper around normal Vue Router setup, using the surface runtime to decide which routes should be active and which guard behavior should be applied.
|
|
418
|
-
|
|
419
|
-
In the starter app, with only `home`, this is almost boring. It mostly means:
|
|
420
|
-
|
|
421
|
-
- build the router
|
|
422
|
-
- add not-found
|
|
423
|
-
- enforce the `/` to `/home` redirect behavior
|
|
424
|
-
|
|
425
|
-
`createPinia()` is the standard shared-state layer for the client app. JSKIT installs it from day 0 so later packages can expose Vue-facing stores without each app having to bolt Pinia on afterward.
|
|
426
|
-
|
|
427
|
-
The default scaffold already has the `shell-web` client stores available, such as `useShellLayoutStore()` and the shell error presentation store behind `ShellErrorHost`. Pinia is also there from day 0 so later packages, such as `auth-web`, can add their own stores without changing the app bootstrap.
|
|
428
|
-
|
|
429
|
-
`createVuetify(...)` is the ordinary UI plugin setup. There is nothing especially JSKIT-specific there; it configures theme settings and icon aliases before the router is mounted. Vuetify components are auto-imported by `vite-plugin-vuetify`, so the scaffold does not register the full Vuetify component namespace in the client bundle.
|
|
430
|
-
|
|
431
|
-
`bootInstalledClientModules` is the extension seam, meaning "this is the point where later-installed JSKIT packages get to join client startup". The confusing part is that it is not a normal file in your app. In `src/main.js` you import it from:
|
|
432
|
-
|
|
433
|
-
```js
|
|
434
|
-
import { bootInstalledClientModules } from "virtual:jskit-client-bootstrap";
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
That `virtual:` prefix is a Vite convention. It means there is no real file on disk with that name. Instead, JSKIT's Vite plugin generates that module for you during development and build.
|
|
438
|
-
|
|
439
|
-
The startup path looks like this:
|
|
440
|
-
|
|
441
|
-
1. `src/main.js` imports `virtual:jskit-client-bootstrap`.
|
|
442
|
-
2. The JSKIT Vite plugin sees that import and generates a module on the fly.
|
|
443
|
-
3. That generated module imports the `.../client` entrypoint of each installed JSKIT package that has client-side behavior.
|
|
444
|
-
4. It wraps those imported modules into one function called `bootInstalledClientModules(...)`.
|
|
445
|
-
5. `bootstrapClientShellApp(...)` calls that function before the router is installed and before the app is mounted.
|
|
446
|
-
6. `bootClientModules(...)` then starts client providers and registers any client routes contributed by those installed packages.
|
|
447
|
-
|
|
448
|
-
The generated module is roughly like this:
|
|
449
|
-
|
|
450
|
-
```js
|
|
451
|
-
import * as authClient from "@jskit-ai/auth-web/client";
|
|
452
|
-
import * as usersClient from "@jskit-ai/users-web/client";
|
|
453
|
-
import { bootClientModules } from "@jskit-ai/kernel/client/moduleBootstrap";
|
|
454
|
-
|
|
455
|
-
const installedClientModules = [
|
|
456
|
-
{ packageId: "@jskit-ai/auth-web", module: authClient },
|
|
457
|
-
{ packageId: "@jskit-ai/users-web", module: usersClient }
|
|
458
|
-
];
|
|
459
|
-
|
|
460
|
-
async function bootInstalledClientModules(context = {}) {
|
|
461
|
-
return bootClientModules({
|
|
462
|
-
...context,
|
|
463
|
-
clientModules: installedClientModules
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
That is why Vite is involved. The browser cannot inspect the installed npm graph and turn it into bundler-visible imports at runtime. Vite needs a normal import graph up front. The plugin creates that graph for the app.
|
|
469
|
-
|
|
470
|
-
If this were plain Vue without that plugin, you would have to maintain the list yourself:
|
|
471
|
-
|
|
472
|
-
```js
|
|
473
|
-
import { bootClientModules } from "@jskit-ai/kernel/client/moduleBootstrap";
|
|
474
|
-
import * as authClient from "@jskit-ai/auth-web/client";
|
|
475
|
-
import * as usersClient from "@jskit-ai/users-web/client";
|
|
476
|
-
|
|
477
|
-
await bootClientModules({
|
|
478
|
-
app,
|
|
479
|
-
router,
|
|
480
|
-
surfaceRuntime,
|
|
481
|
-
surfaceMode,
|
|
482
|
-
clientModules: [
|
|
483
|
-
{ packageId: "@jskit-ai/auth-web", module: authClient },
|
|
484
|
-
{ packageId: "@jskit-ai/users-web", module: usersClient }
|
|
485
|
-
]
|
|
486
|
-
});
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
JSKIT automates that step so adding or removing packages does not require hand-editing `src/main.js`.
|
|
490
|
-
|
|
491
|
-
In a brand-new shell app, there are no extra installed client modules yet, so the generated function is effectively empty. Later, when you install packages with client providers or extra UI routes, this same hook is what starts those providers and registers those routes.
|
|
492
|
-
|
|
493
|
-
`bootstrapClientShellApp(...)` is the final assembly step. It creates the Vue app, installs plugins such as Pinia and Vuetify, passes the Pinia instance explicitly into client-module boot, stores the client app config, runs `bootInstalledClientModules(...)`, attaches the fallback route if needed, installs the router, waits for the router to be ready, and only then mounts the app. That order matters because installed packages need a chance to extend the app before the first render happens.
|
|
494
|
-
|
|
495
|
-
</DocsInDepth>
|
|
496
|
-
|
|
497
|
-
#### The main package (client side)
|
|
498
|
-
|
|
499
|
-
One more client-side piece is worth seeing before looking at page files: the starter app already has its own client provider. The app-local package declares it in `packages/main/package.json` like this:
|
|
500
|
-
|
|
501
|
-
```json
|
|
502
|
-
{
|
|
503
|
-
"jskit": {
|
|
504
|
-
"runtime": {
|
|
505
|
-
"client": {
|
|
506
|
-
"providers": [
|
|
507
|
-
{
|
|
508
|
-
"entrypoint": "src/client/providers/MainClientProvider.js",
|
|
509
|
-
"export": "MainClientProvider"
|
|
510
|
-
}
|
|
511
|
-
]
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
That declaration is one of the things `bootClientModules(...)` uses. On the client, the lifecycle is:
|
|
519
|
-
|
|
520
|
-
1. collect the installed client modules
|
|
521
|
-
2. resolve the provider classes they declare
|
|
522
|
-
3. create the client runtime application container
|
|
523
|
-
4. run each provider's `register()` method
|
|
524
|
-
5. run each provider's `boot()` method, if it has one
|
|
525
|
-
|
|
526
|
-
So yes: client providers use the same `register()`/`boot()` lifecycle pattern as server providers. In the scaffold, the app-local client provider starts like this:
|
|
527
|
-
|
|
528
|
-
```js
|
|
529
|
-
const mainClientComponents = [];
|
|
530
|
-
|
|
531
|
-
function registerMainClientComponent(token, resolveComponent) {
|
|
532
|
-
mainClientComponents.push({ token, resolveComponent });
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
class MainClientProvider {
|
|
536
|
-
static id = "local.main.client";
|
|
537
|
-
|
|
538
|
-
register(app) {
|
|
539
|
-
for (const { token, resolveComponent } of mainClientComponents) {
|
|
540
|
-
app.singleton(token, resolveComponent);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
export {
|
|
546
|
-
MainClientProvider,
|
|
547
|
-
registerMainClientComponent
|
|
548
|
-
};
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
The important idea is that this provider is not rendering UI directly. It is registering token-addressable client components into the application container. In the default scaffold, the list starts with shell link components that the placement runtime can use for menus and tabs. Later package installs and generators can extend this file by adding imports and `registerMainClientComponent(...)` calls for more app-owned client components. In other words, this file is the app's local registration seam.
|
|
552
|
-
|
|
553
|
-
```js
|
|
554
|
-
import MenuLinkItem from "/src/components/menus/MenuLinkItem.vue";
|
|
555
|
-
|
|
556
|
-
registerMainClientComponent("local.main.ui.menu-link-item", () => MenuLinkItem);
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
Then `MainClientProvider.register(app)` publishes those into the client container with `app.singleton(...)`. Later packages and placement runtime code can ask for those components by token instead of importing app files directly.
|
|
560
|
-
|
|
561
|
-
This code is intentionally small. `registerMainClientComponent(...)` is a private app-local registration hook, not a public validation API, so the scaffold keeps it minimal and lets obvious mistakes fail honestly when the provider is used.
|
|
562
|
-
|
|
563
|
-
`MainClientProvider` does not define a `boot()` method yet, so the boot phase is effectively empty for this provider right now. But the lifecycle still supports it. If you later add `boot()`, JSKIT will run it after all client providers have finished `register()`.
|
|
564
|
-
|
|
565
|
-
Inside `src/pages/` you will find both route owners and actual page components. The easy file to notice is `src/pages/home/index.vue`, because that is the page with visible content. The easy file to miss is `src/pages/home.vue`. That wrapper file contains route metadata that attaches the page tree to a JSKIT surface. When you later add more pages, that surface information is one of the things JSKIT uses to decide where a page belongs.
|
|
566
|
-
|
|
567
|
-
The wrapper file is tiny, but it is doing an important job:
|
|
568
|
-
|
|
569
|
-
```vue
|
|
570
|
-
<route lang="json">
|
|
571
|
-
{
|
|
572
|
-
"meta": {
|
|
573
|
-
"jskit": {
|
|
574
|
-
"surface": "home"
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
</route>
|
|
579
|
-
|
|
580
|
-
<template>
|
|
581
|
-
<RouterView />
|
|
582
|
-
</template>
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
This is why `src/pages/home/index.vue` becomes part of the `home` surface instead of just being "some route".
|
|
586
|
-
|
|
587
|
-
**Routing: How JSKIT Reads Route Metadata**
|
|
588
|
-
|
|
589
|
-
The `<route lang="json">` block is not something `shell-web` or `auth-web` parses directly.
|
|
590
|
-
|
|
591
|
-
During dev/build, the file-based Vue Router plugin scans `src/pages/` and turns that block into ordinary Vue Router route metadata. Later, when navigation happens, JSKIT reads that metadata from the matched route records.
|
|
592
|
-
|
|
593
|
-
That is why a block like this can affect behavior without any extra code in the page component itself:
|
|
594
|
-
|
|
595
|
-
- `meta.jskit.surface` tells JSKIT which surface the page tree belongs to
|
|
596
|
-
- `meta.guard.policy` can later tell JSKIT whether the route should be treated as `public` or `authenticated`
|
|
597
|
-
|
|
598
|
-
So the order is:
|
|
599
|
-
|
|
600
|
-
1. the router plugin reads the `<route>` block
|
|
601
|
-
2. the generated route record gets a normal `meta` object
|
|
602
|
-
3. JSKIT reads that `meta` during routing
|
|
603
|
-
|
|
604
|
-
`src/App.vue` is deliberately small. It is the outer Vuetify app shell, the top-level `RouterView`, and `ShellErrorHost`. That is another pattern you should get used to in JSKIT: the app root stays thin, and most behavior is pushed toward packages, page files, and runtime providers.
|
|
605
|
-
|
|
606
|
-
**Container: App Methods**
|
|
607
|
-
|
|
608
|
-
The `app` object in `register(app)` and `boot(app)` is JSKIT's application container.
|
|
609
|
-
|
|
610
|
-
Defining values:
|
|
611
|
-
|
|
612
|
-
- `app.singleton(token, factory)`: register one lazily created shared value for the whole app.
|
|
613
|
-
- `app.bind(token, factory)`: register a factory that creates a fresh value every time the token is resolved.
|
|
614
|
-
- `app.scoped(token, factory)`: register one value per child scope.
|
|
615
|
-
- `app.instance(token, value)`: register an already-created value directly.
|
|
616
|
-
- `app.tag(token, tagName)`: add a token to a named group.
|
|
617
|
-
|
|
618
|
-
Typical examples look like this:
|
|
619
|
-
|
|
620
|
-
```js
|
|
621
|
-
app.instance("appConfig", appConfig);
|
|
622
|
-
app.singleton("local.main.ui.menu-link-item", () => TabLinkItem);
|
|
623
|
-
app.bind("feature.clock", () => new Clock());
|
|
624
|
-
app.tag("auth.login.component", "auth.ui");
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
That means:
|
|
628
|
-
|
|
629
|
-
- `instance(...)` is good for something you already created, such as loaded config.
|
|
630
|
-
- `singleton(...)` is good for one shared runtime object or component resolver.
|
|
631
|
-
- `bind(...)` is for "make a fresh one each time someone asks".
|
|
632
|
-
- `tag(...)` lets you group related tokens so another part of the app can ask for the whole set later.
|
|
633
|
-
|
|
634
|
-
Using values:
|
|
635
|
-
|
|
636
|
-
- `app.make(token)`: resolve a token from the container.
|
|
637
|
-
- `app.has(token)`: check whether a token is registered.
|
|
638
|
-
- `app.createScope(scopeId)`: create a child scope.
|
|
639
|
-
- `app.resolveTag(tagName)`: resolve all tokens in that group.
|
|
640
|
-
|
|
641
|
-
Then later code can consume those registrations like this:
|
|
642
|
-
|
|
643
|
-
```js
|
|
644
|
-
const authGuardRuntime = app.make("runtime.auth-guard.client");
|
|
645
|
-
|
|
646
|
-
if (app.has("jskit.client.vue.app")) {
|
|
647
|
-
const vueApp = app.make("jskit.client.vue.app");
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
const authUi = app.resolveTag("auth.ui");
|
|
651
|
-
const requestScope = app.createScope("request:123");
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
A scope is a real child container, not just a label. You can put request-local values into it and then resolve them from the scope itself:
|
|
655
|
-
|
|
656
|
-
```js
|
|
657
|
-
app.singleton("logger", () => createLogger());
|
|
658
|
-
|
|
659
|
-
const requestScope = app.createScope("request:123");
|
|
660
|
-
requestScope.instance("request.id", "request:123");
|
|
661
|
-
|
|
662
|
-
const logger = requestScope.make("logger"); // inherited from the parent app container
|
|
663
|
-
const requestId = requestScope.make("request.id"); // local to this scope
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
Here:
|
|
667
|
-
|
|
668
|
-
- `make(...)` gets the thing behind a token.
|
|
669
|
-
- `has(...)` lets you check before assuming a token exists.
|
|
670
|
-
- `resolveTag(...)` gets every token in a named group.
|
|
671
|
-
- `createScope(...)` gives you a child container when you need scoped values rather than app-wide ones, and that child container can then use `make(...)`, `has(...)`, `instance(...)`, and the other container methods too.
|
|
672
|
-
|
|
673
|
-
### The server side
|
|
674
|
-
|
|
675
|
-
The backend entry point is `server.js`, with `bin/server.js` acting as the small executable wrapper used by the npm scripts. `server.js` starts Fastify, registers a built-in `/api/health` route, loads the provider runtime, and decides how to serve the frontend. In development, you normally visit the Vite dev server on port `5173`. In a built app, this same server can also serve the compiled frontend.
|
|
676
|
-
|
|
677
|
-
The core of that startup path looks like this:
|
|
678
|
-
|
|
679
|
-
```js
|
|
680
|
-
async function createServer() {
|
|
681
|
-
const app = Fastify({ logger: true });
|
|
682
|
-
|
|
683
|
-
app.get("/api/health", async () => {
|
|
684
|
-
return {
|
|
685
|
-
ok: true,
|
|
686
|
-
app: "exampleapp"
|
|
687
|
-
};
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
const runtimeEnv = resolveRuntimeEnv();
|
|
691
|
-
const appRoot = path.resolve(process.cwd());
|
|
692
|
-
const runtime = await tryCreateProviderRuntimeFromApp({
|
|
693
|
-
appRoot,
|
|
694
|
-
profile: resolveRuntimeProfileFromSurface({
|
|
695
|
-
surfaceRuntime,
|
|
696
|
-
serverSurface: runtimeEnv.SERVER_SURFACE
|
|
697
|
-
}),
|
|
698
|
-
env: runtimeEnv,
|
|
699
|
-
logger: app.log,
|
|
700
|
-
fastify: app
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
registerSurfaceRequestConstraint({
|
|
704
|
-
fastify: app,
|
|
705
|
-
surfaceRuntime,
|
|
706
|
-
serverSurface: runtimeEnv.SERVER_SURFACE,
|
|
707
|
-
globalUiPaths: resolveGlobalUiPaths(runtime?.globalUiPaths || [])
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
return app;
|
|
711
|
-
}
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
The health route is built in, but the more important idea is that the server is already prepared to validate HTTP input with Fastify's normal JSON Schema path, load the JSKIT provider runtime from the app itself, and constrain requests by surface.
|
|
715
|
-
|
|
716
|
-
You will also notice `config/server.js`. In the base app it is intentionally almost empty. It is there to reserve a clear place for server-side configuration as backend features are added, without pretending the starter app already has feature-specific server configuration.
|
|
717
|
-
|
|
718
|
-
The small `server/lib/` directory exists to keep that server boot code tidy. `runtimeEnv.js` reads environment variables such as port and host. `surfaceRuntime.js` builds the same surface runtime that the client uses, so the server and browser agree on what surfaces exist. In the scaffold scripts, `npm run server:home` is simply setting `SERVER_SURFACE=home`, while `npm run server` and `npm run server:all` leave the server unrestricted.
|
|
719
|
-
|
|
720
|
-
#### The main package (server side)
|
|
721
|
-
|
|
722
|
-
The most unusual part of the scaffold, if you are new to JSKIT, is `packages/main/`. This is the app-local runtime package. It is not there by accident, and it is not just a convenience folder. JSKIT treats your app itself as a local package with package metadata, client provider hooks, and server provider hooks. That is why the folder contains `package.json` and a small `src/` tree of its own.
|
|
723
|
-
|
|
724
|
-
You already saw the client-side provider in the client bootstrap path. The server side uses the same model: `package.json.jskit` tells JSKIT which provider class belongs to the local package, and the runtime calls `register()` and then `boot()`.
|
|
725
|
-
|
|
726
|
-
The server part of that metadata looks like this:
|
|
727
|
-
|
|
728
|
-
```json
|
|
729
|
-
{
|
|
730
|
-
"name": "@local/main",
|
|
731
|
-
"version": "0.1.0",
|
|
732
|
-
"jskit": {
|
|
733
|
-
"kind": "runtime",
|
|
734
|
-
"runtime": {
|
|
735
|
-
"server": {
|
|
736
|
-
"providerEntrypoint": "src/server/MainServiceProvider.js",
|
|
737
|
-
"providers": [
|
|
738
|
-
{
|
|
739
|
-
"entrypoint": "src/server/MainServiceProvider.js",
|
|
740
|
-
"export": "MainServiceProvider"
|
|
741
|
-
}
|
|
742
|
-
]
|
|
743
|
-
}
|
|
744
|
-
},
|
|
745
|
-
"metadata": {
|
|
746
|
-
"server": {
|
|
747
|
-
"routes": []
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
}
|
|
44
|
+
.git/
|
|
45
|
+
.agents/
|
|
46
|
+
.codex/
|
|
752
47
|
```
|
|
753
48
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
The server-side provider starts like this:
|
|
759
|
-
|
|
760
|
-
```js
|
|
761
|
-
import { loadAppConfig } from "./loadAppConfig.js";
|
|
762
|
-
|
|
763
|
-
class MainServiceProvider {
|
|
764
|
-
static id = "local.main";
|
|
765
|
-
|
|
766
|
-
async register(app) {
|
|
767
|
-
const appConfig = await loadAppConfig({
|
|
768
|
-
moduleUrl: import.meta.url
|
|
769
|
-
});
|
|
770
|
-
app.instance("appConfig", appConfig);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
boot() {}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
export { MainServiceProvider };
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
It is deliberately small because it is only for app-local glue: loading config, wiring tiny app-specific behavior, and bootstrapping shared runtime concerns. When a backend capability becomes substantial, do not grow `packages/main` into a mini service tree. Generate a dedicated package instead:
|
|
780
|
-
|
|
781
|
-
```bash
|
|
782
|
-
npx jskit generate feature-server-generator scaffold booking-engine
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
That keeps the ownership boundary clear: `packages/main` stays composition-only, while real server features get their own provider, service, and optional repository seams. The client side uses the same provider lifecycle; you already saw the matching pattern earlier in the client boot path.
|
|
786
|
-
|
|
787
|
-
The root `package.json`, its exact JSKIT dependency versions, and
|
|
788
|
-
`package-lock.json` define the installed graph. JSKIT reads each installed
|
|
789
|
-
package's `package.json.jskit` metadata directly. The local `@local/main`
|
|
790
|
-
package participates through its normal `file:packages/main` dependency.
|
|
791
|
-
|
|
792
|
-
### Other files and options
|
|
793
|
-
|
|
794
|
-
The remaining files are easier to understand once you know the core pieces above. `vite.config.mjs` configures the frontend build and the `/api` proxy used during development. `index.html` is the HTML shell Vite uses to mount Vue. `tests/` contains basic smoke tests so the app has a verification path from day one. The `scripts/` directory is intentionally small because JSKIT maintenance helpers such as `verify`, `jskit:update`, and `release` are package-owned CLI commands rather than copied app scripts.
|
|
49
|
+
Inspect every collision. A direct pattern copy must never need `--force` and
|
|
50
|
+
must never work through a temporary directory. Rename the packaged `gitignore`
|
|
51
|
+
asset to `.gitignore` when using it.
|
|
795
52
|
|
|
796
|
-
|
|
53
|
+
## Ownership after copying
|
|
797
54
|
|
|
798
|
-
|
|
55
|
+
All copied files are ordinary application source. The pattern package does not
|
|
56
|
+
own future edits. There is no generated-file header, pattern receipt, template
|
|
57
|
+
hash, completion record, or replay journal. Framework packages continue to own
|
|
58
|
+
their public runtime APIs; the application owns its product source.
|
|
799
59
|
|
|
800
|
-
|
|
60
|
+
## Loading and errors
|
|
801
61
|
|
|
802
|
-
|
|
62
|
+
The adaptive example demonstrates structure-matching skeletons for visible
|
|
63
|
+
loading. Do not replace them with generic spinners. Keep failures local when a
|
|
64
|
+
resource cannot render; use shared toast/snackbar feedback for transient user
|
|
65
|
+
commands so errors do not push the working page down.
|
|
803
66
|
|
|
804
|
-
##
|
|
67
|
+
## Verification
|
|
805
68
|
|
|
806
|
-
|
|
69
|
+
Pattern contract tests keep both examples concrete, parseable, npm-published,
|
|
70
|
+
free from interpolation tokens, and aligned with their public dependencies.
|
|
71
|
+
After adapting a foundation, verify the actual application rather than
|
|
72
|
+
comparing it byte-for-byte with the example.
|