webspresso 0.0.74 → 0.0.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +41 -3
  2. package/bin/commands/orm-map.js +139 -0
  3. package/bin/commands/skill.js +22 -8
  4. package/bin/utils/orm-map-html.js +689 -0
  5. package/bin/utils/orm-map-load.js +85 -0
  6. package/bin/utils/orm-map-snapshot.js +179 -0
  7. package/bin/utils/resolve-webspresso-orm.js +23 -0
  8. package/bin/webspresso.js +2 -0
  9. package/core/auth/manager.js +14 -1
  10. package/core/kernel/app.js +96 -0
  11. package/core/kernel/base-repository.js +143 -0
  12. package/core/kernel/events.js +101 -0
  13. package/core/kernel/flow.js +22 -0
  14. package/core/kernel/index.js +17 -0
  15. package/core/kernel/plugin.js +23 -0
  16. package/core/kernel/plugins/sample-seo.js +26 -0
  17. package/core/kernel/run-demo.js +58 -0
  18. package/core/kernel/view.js +167 -0
  19. package/core/openapi/build-from-api-routes.js +8 -2
  20. package/core/orm/model.js +3 -1
  21. package/core/url-path-normalize.js +30 -0
  22. package/index.d.ts +168 -1
  23. package/index.js +20 -2
  24. package/package.json +11 -1
  25. package/plugins/admin-panel/api.js +43 -15
  26. package/plugins/admin-panel/client/README.md +39 -0
  27. package/plugins/admin-panel/client/load-parts.js +74 -0
  28. package/plugins/admin-panel/client/manifest.parts.json +12 -0
  29. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +150 -0
  30. package/plugins/admin-panel/client/parts/02-filter-components.js +554 -0
  31. package/plugins/admin-panel/client/parts/03-pagination-intro.js +70 -0
  32. package/plugins/admin-panel/client/parts/04-field-renderers.js +287 -0
  33. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +335 -0
  34. package/plugins/admin-panel/client/parts/06-login-setup-forms.js +125 -0
  35. package/plugins/admin-panel/client/parts/07-model-list.js +596 -0
  36. package/plugins/admin-panel/client/parts/08-record-list.js +536 -0
  37. package/plugins/admin-panel/client/parts/09-record-form.js +170 -0
  38. package/plugins/admin-panel/client/parts/10-export-registry.js +11 -0
  39. package/plugins/admin-panel/client/verify-spa-parts.js +32 -0
  40. package/plugins/admin-panel/client/vite.config.example.mjs +22 -0
  41. package/plugins/admin-panel/components.js +4 -2640
  42. package/plugins/admin-panel/core/api-extensions.js +100 -10
  43. package/plugins/admin-panel/index.js +3 -0
  44. package/plugins/admin-panel/lib/is-rich-text-empty.js +23 -0
  45. package/plugins/admin-panel/lib/sanitize-rich-html.js +106 -0
  46. package/plugins/admin-panel/modules/dashboard.js +3 -2
  47. package/plugins/admin-panel/modules/user-management.js +90 -20
  48. package/plugins/index.js +4 -0
  49. package/plugins/rate-limit/index.js +178 -0
  50. package/plugins/redirect/index.js +204 -0
  51. package/plugins/rest-resources/index.js +2 -1
  52. package/plugins/swagger.js +2 -1
  53. package/plugins/upload/local-file-provider.js +6 -2
  54. package/src/file-router.js +191 -50
  55. package/src/njk-frontmatter.js +156 -0
  56. package/src/plugin-manager.js +4 -2
  57. package/src/server.js +26 -9
  58. package/templates/skills/webspresso-usage/REFERENCE-framework.md +276 -0
  59. package/templates/skills/webspresso-usage/REFERENCE-kernel.md +93 -0
  60. package/templates/skills/webspresso-usage/SKILL.md +29 -278
@@ -0,0 +1,276 @@
1
+ # Webspresso — framework reference (SSR, API, ORM)
2
+
3
+ Sections follow the source repo layout (`index.js`, `src/`, `core/orm`, `doc/index.html`).
4
+
5
+ ## 1. What this framework is
6
+
7
+ - **SSR**: Express + **Nunjucks**; URLs map to files under `pages/`.
8
+ - **API**: File-based handlers under `pages/api/` with method suffixes and optional **Zod** validation (`req.input`).
9
+ - **i18n**: JSON locales; `t('key')` in templates.
10
+ - **ORM**: Knex-backed layer in `core/orm` — `defineModel`, `zdb` schema helpers, repositories, query builder, migrations.
11
+ - **Plugins**: Register in `createApp({ plugins })`; optional `db` passed as `ctx.db`.
12
+
13
+ Public API surface: `require('webspresso')` / [`index.js`](../../../index.js) — `createApp`, **`resolveClientRuntime`**, **`CLIENT_RUNTIME_BASE`**, file-router utilities, `createHelpers`, plugin manager, ORM exports, built-in plugins. **Session auth** lives in [`core/auth`](../../../core/auth) — import **`require('webspresso/core/auth')`** (`createAuth`, `quickAuth`, `hash`, `verify`, `setupAuthMiddleware`, `createRememberTokensTable`, policy helpers); wire with **`createApp({ auth })`**.
14
+
15
+ ---
16
+
17
+ ## 2. Typical project layout
18
+
19
+ ```
20
+ project/
21
+ ├── server.js # listen(); uses createApp()
22
+ ├── pages/
23
+ │ ├── _hooks.js # global lifecycle hooks (optional)
24
+ │ ├── locales/en.json # global i18n
25
+ │ ├── index.njk # GET /
26
+ │ ├── about/index.njk # GET /about
27
+ │ ├── blog/[slug].njk # dynamic
28
+ │ └── api/
29
+ │ ├── health.get.js
30
+ │ └── posts.post.js
31
+ ├── views/ # layouts (e.g. layout.njk)
32
+ ├── public/ # static assets
33
+ ├── models/ # ORM models (defineModel)
34
+ ├── migrations/
35
+ ├── webspresso.db.js # or knexfile.js
36
+ └── plugins/ # app-specific plugins (optional)
37
+ ```
38
+
39
+ ---
40
+
41
+ ## 3. `createApp(options)` — essentials
42
+
43
+ **Required**
44
+
45
+ - `pagesDir` — path to `pages/`.
46
+
47
+ **Common optional**
48
+
49
+ | Option | Role |
50
+ |--------|------|
51
+ | `viewsDir` | Layouts / partials (Nunjucks paths) |
52
+ | `publicDir` | Static files (`express.static`) |
53
+ | `db` | DB instance from `createDatabase()` → **`ctx.db`** in `load`, `meta`, plugins |
54
+ | `middlewares` | Named map; reference by string in route/API config (`middleware: ['auth']`) |
55
+ | `plugins` | Array of plugin factories/objects |
56
+ | `errorPages` | `{ notFound, serverError, timeout }` — function or template path. File-based SSR/API errors are passed to this Express error middleware via `next(err)`. **`serverError` / `timeout` as a template path** is not used for paths under **`/api`** (those get default JSON). |
57
+ | `timeout` | e.g. `'30s'` or `false` |
58
+ | `helmet` | `true` / `false` / object |
59
+ | `assets` | `{ version, manifestPath, prefix }` for `fsy.asset` / `fsy.css` / `fsy.js` |
60
+ | `pageAssets` | Opt-in **`true`** or **`{ enabled?, stylesheets?, scripts? }`**. When on, route **`load()`** may return reserved keys **`stylesheets`** (string or list; also `{ href, media? }` objects) and **`scripts`** (string, `{ src, defer?, async?, type? }`, or list). They are removed from the root Nunjucks context and passed as **`pageHead`** with **`pageAssets: true`**. The app layout must print them (see [`views/layout.njk`](../../../views/layout.njk) in the package). Default **off** — `stylesheets` / `scripts` in **`load()`** behave as normal data keys. |
61
+ | `clientRuntime` | Opt-in **`{ alpine?: boolean \| object, swup?: boolean \| object }`**. Serves **`/__webspresso/client-runtime/*`** (Alpine 3, swup 4 + Head + Scripts plugins + bootstrap). Template context **`clientRuntime`**; include [`views/partials/webspresso-client-runtime.njk`](../../../views/partials/webspresso-client-runtime.njk) and set **`<main id="swup">`** when swup is on. Env overrides: **`WEBSPRESSO_ALPINE`**, **`WEBSPRESSO_SWUP`** (`1` or `true`). Admin / dev dashboard HTML is unchanged (Mithril). Use **`data-no-swup`** on links for full page loads. HTMX is not used. |
62
+ | `auth` | `AuthManager` from **`createAuth()`** / **`quickAuth()`** (`webspresso/core/auth`). Mounts cookie parser + **`express-session`** + per-request **`authenticate`**; sets **`req.user`**, **`req.auth`**. Injects named route middleware **`auth`** and **`guest`** (overwrites same keys in `middlewares` if you passed both — avoid reusing those names for custom handlers). |
63
+ | `setupRoutes(app, ctx)` | **Register custom Express routes here** — runs **after** file routes and plugins’ `onRoutesReady`, **before** 404. **`ctx.clientRuntime`** is the resolved flags. **`ctx.authMiddleware`** is set when `auth` was passed (guards: `requireAuth`, `requireGuest`, `requireCan`, `requireVerified`, …). Do not rely on `app.get` *after* `createApp` returns unless routes are appended before the 404 middleware (see [`src/server.js`](../../../src/server.js)). |
64
+
65
+ **Returns:** `{ app, nunjucksEnv, pluginManager, authMiddleware }` — `authMiddleware` is **`null`** when `auth` was not configured.
66
+
67
+ ### Client runtime — implementation notes
68
+
69
+ - **Package helpers:** `resolveClientRuntime(options)` merges **`createApp({ clientRuntime })`** with env; **`CLIENT_RUNTIME_BASE`** is **`/__webspresso/client-runtime`** (script URLs under that path).
70
+ - **After swup navigation:** bootstrap runs **`Alpine.initTree`** on **`#swup`** on swup’s **`content:replace`** so new SSR markup gets Alpine bindings.
71
+ - **Default `ignoreVisit` (bootstrap):** links under **`/_admin`**, **`/_webspresso`**, elements with **`data-no-swup`**, plus swup’s usual rules (e.g. `target="_blank"`, other origin).
72
+ - **CSP / Helmet:** production **`script-src 'self'`** works for **`/__webspresso/client-runtime/`**; some Alpine builds may need **`unsafe-eval`** — validate for your version or use a stricter build.
73
+ - **Longer doc:** **[`doc/index.html#client-runtime`](../../../doc/index.html#client-runtime)** · README **Client runtime**.
74
+
75
+ ### Session authentication — essentials
76
+
77
+ - **Import:** `const { createAuth, quickAuth, hash, verify, createRememberTokensTable } = require('webspresso/core/auth')` (published `core/` tree on npm; **not** re-exported from package root).
78
+ - **`createAuth({ findUserById, findUserByCredentials, session: { secret }, rememberTokens?, ... })`** — adapter pattern; optional **remember-me** via `rememberTokens: { create, find, delete, deleteAllForUser }` + **`createRememberTokensTable(knex)`** for the default table shape.
79
+ - **`quickAuth({ db, userModel, identifierField, passwordField, rememberMe })`** — wires **`getRepository`** + bcrypt **`verify`**; optional Knex **`remember_tokens`** when `rememberMe: true`.
80
+ - **Request API** (after global authenticate): **`req.auth.attempt(id, password, { remember })`**, **`login`**, **`logout`**, **`check`**, **`guest`**, **`user`**, **`id`**, **`can` / `cannot` / `authorize`** (policies: **`auth.definePolicy`**, **`defineGate`**, **`beforePolicy`**).
81
+ - **Route config:** `middleware: ['auth']` (must be logged in) or `['guest']` (logged-out only). For JSON APIs mounted in **`setupRoutes`**, use **`ctx.authMiddleware.requireAuth({ api: true })`** for 401 JSON instead of redirect.
82
+ - **Login page pitfall:** a **`pages/login.njk`** can register **before** `setupRoutes` and bypass **`requireGuest`**. Prefer login GET/POST in **`setupRoutes`** with templates under **`views/`** only, or omit **`pages/login.njk`** — see [`tests/e2e/auth.spec.js`](../../../tests/e2e/auth.spec.js).
83
+ - **Admin panel** uses a **separate** session (`req.session.adminUser`, `/_admin/api/auth/*`); it does **not** replace **`createApp({ auth })`** for site users.
84
+ - **Site users in the admin UI (`userManagement`):** Opt-in on **`adminPanelPlugin`**. Set **`userManagement: { enabled: true, model: 'User', fields?: { ... } }`** so the SPA shows **Users**: sidebar **All Users** / **Add User** link to **`/_admin/models/{model}`** and **`/_admin/models/{model}/new`** (same RecordList/RecordForm as other models). **`/_admin/users`**, **`/_admin/users/new`**, **`/_admin/users/:id/edit`** remain SPA aliases that redirect there; **`/_admin/users/sessions`** is **Active Sessions**. The **`model`** must match site auth (e.g. **`quickAuth({ userModel: 'User', ... })`** / **`createAuth`**) and must have **`admin: { enabled: true, ... }`** on **`defineModel`** so admin CRUD metadata loads; otherwise Users screens look empty. Pass **`auth: authManager`** with the **same** **`AuthManager`** as **`createApp({ auth: authManager })`** for **Active Sessions** / revoke (**`rememberTokens`** / **`remember_me`**); without **`auth`**, user CRUD still works via **`db.getRepository(model)`**, but session endpoints return empty or “not enabled”.
85
+ - **Wiring:** `plugins: [ adminPanelPlugin({ db, auth: authManager, userManagement: { enabled: true, model: 'User' } }) ]` alongside `createApp({ ..., auth: authManager })`. Admin staff log in at **`/_admin`**; end users use your normal site login — two different cookies/sessions.
86
+
87
+ Longer narrative: **[`doc/index.html#authentication`](../../../doc/index.html#authentication)** · **[`#admin-user-management`](../../../doc/index.html#admin-user-management)** · README **Authentication (session)** and **Admin Panel Plugin**.
88
+
89
+ ---
90
+
91
+ ## 4. File-based routing (SSR)
92
+
93
+ | Path pattern | URL |
94
+ |--------------|-----|
95
+ | `pages/index.njk` | `/` |
96
+ | `pages/about/index.njk` | `/about` |
97
+ | `pages/tools/[slug].njk` | `/tools/:slug` |
98
+ | `pages/docs/[...rest].njk` | `/docs/*` (catch-all) |
99
+
100
+ **Route config** — sibling `.js` file (e.g. `pages/tools/index.js`):
101
+
102
+ - `middleware` — array of Express functions, **string names** from `createApp({ middlewares })`, or **`['name', options]` tuples** when the registry entry is a **factory** `(options) => (req, res, next) => …` (bare string calls the factory with `{}`). Built-in **`auth`** / **`guest`** (with `createApp({ auth })`) are factories — e.g. **`['auth', { api: true }]`** for JSON 401 on APIs.
103
+ - `load(req, ctx)` — async; return object merged into template context; use **`ctx.db`** when `createApp({ db })` is set.
104
+ - `meta(req, ctx)` — title, description, etc.
105
+ - `hooks` — `beforeLoad`, `afterRender`, etc. (see hook order below).
106
+
107
+ ---
108
+
109
+ ## 5. File-based API (`pages/api/`)
110
+
111
+ | File | Route |
112
+ |------|-------|
113
+ | `api/health.get.js` | `GET /api/health` |
114
+ | `api/items.post.js` | `POST /api/items` |
115
+ | `api/users/[id].get.js` | `GET /api/users/:id` |
116
+
117
+ **Shapes**
118
+
119
+ 1. **Function** — `module.exports = async (req, res) => { ... }`
120
+ 2. **Object** — **`handler`**, optional **`middleware`** (names, functions, or **`['name', options]`** tuples with factory registry entries), optional **`schema`**
121
+
122
+ **Order:** `req.db` (if any) → **Zod** `schema` → **`middleware`** → **`handler`**.
123
+
124
+ **Zod** — `schema: ({ z }) => ({ body, query, params, response })` → **`req.input`**; invalid → **400** `{ error: 'Validation Error', issues }`.
125
+
126
+ ---
127
+
128
+ ## 6. Global and route hooks
129
+
130
+ **Global:** `pages/_hooks.js` exports `onRequest`, `beforeLoad`, `afterLoad`, `beforeRender`, `afterRender`, `onError`, etc.
131
+
132
+ **`onError(ctx, err)`** — runs when an unhandled error occurs in a **file-based** SSR route or **`pages/api/*`** handler (after `console.error`, before the central `errorPages` handler). Optional second argument **`err`** matches **`ctx.error`**. Use for APM (Sentry, New Relic `noticeError`, …).
133
+
134
+ **Rough order:** global `onRequest` → route middleware chain → `load` → render → `afterRender`. (See README “Hook Execution Order” for full list.)
135
+
136
+ ---
137
+
138
+ ## 7. i18n
139
+
140
+ - **Global:** `pages/locales/<locale>.json` (nested keys).
141
+ - **Route-specific:** `pages/<route>/locales/<locale>.json` overrides global.
142
+ - Templates: `{{ t('nav.home') }}`.
143
+ - Env: `DEFAULT_LOCALE`, `SUPPORTED_LOCALES` (comma-separated).
144
+
145
+ ---
146
+
147
+ ## 8. Template helpers (`fsy`)
148
+
149
+ Available in all Nunjucks templates:
150
+
151
+ - **URL:** `fsy.url`, `fsy.fullUrl`, `fsy.route`, `fsy.canonical`
152
+ - **Request:** `fsy.q`, `fsy.param`, `fsy.hdr`
153
+ - **Utils:** `fsy.slugify`, `fsy.truncate`, `fsy.prettyBytes`, `fsy.prettyMs`
154
+ - **Dates:** `fsy.date`, `fsy.dateFormat`, `fsy.dateFromNow`, `fsy.dateDiff`, …
155
+ - **Assets:** `fsy.asset`, `fsy.css`, `fsy.js`, `fsy.img` (with `assets` config)
156
+ - **Dev:** `fsy.isDev()`
157
+ - **SEO:** `fsy.jsonld`
158
+
159
+ Analytics plugin adds `fsy.analyticsHead`, `fsy.verificationTags`, etc., when configured.
160
+
161
+ ---
162
+
163
+ ## 9. ORM overview
164
+
165
+ **Define schema** with **`zdb`** (`zdb.id()`, `zdb.uuid()`, `zdb.nanoid()`, `zdb.string({...})`, **`zdb.file({ maxLength, nullable })`** — URL/path string for uploaded assets, `zdb.foreignKey`, `zdb.foreignUuid`, `zdb.foreignNanoid`, `zdb.timestamp`, `zdb.json`, …).
166
+
167
+ **Define model** with **`defineModel({ name, table, schema, relations, scopes, hidden, admin })`**.
168
+
169
+ - **Relations:** `belongsTo`, `hasMany`, `hasOne` with `model: () => OtherModel`.
170
+ - **Scopes:** `softDelete`, `timestamps`, optional `tenant` column.
171
+ - **`hidden`:** columns never exposed in admin/API (e.g. `password_hash`).
172
+ - **Nanoid PK:** `zdb.nanoid()` / `zdb.nanoid({ maxLength: 12 })` — string primary key; migrations use `string(length)`. On **`create()`**, omitting the PK auto-fills a URL-safe id (built-in generator, same alphabet as `nanoid`). Use **`zdb.foreignNanoid('table', { maxLength })`** when the parent uses nanoid PKs; **`generateNanoid`** is exported from `webspresso` for manual ids. In API **`schema`**, use **`z.nanoid()`** / **`z.nanoid(12)`** / **`z.nanoid({ maxLength })`** (the `z` from `schema: ({ z })` is extended by Webspresso). **`zodNanoid`** / **`extendZ`** are also exported for non-route use.
173
+
174
+ **Database:** `createDatabase({ client, connection, models: './models' })` — auto-loads `models/*.js` (ignore `_prefix`).
175
+
176
+ **Repository:** `db.getRepository('User')` → `findById`, `findOne`, `findAll`, `create`, `update`, `delete`, `query()`, …
177
+
178
+ **Query builder:** `UserRepo.query().where(...).with('relation').orderBy(...).list()` / `.first()` / `.paginate()` / `.count()`. **`with()`** eager-loads relations; **`count()`** ignores builder `.limit`/`.offset` for total; see ORM docs for edge cases.
179
+
180
+ **Migrations:** `webspresso db:migrate`, `db:rollback`, `db:status`, `db:make`.
181
+
182
+ **Transactions:** `db.transaction(async (trx) => { trx.getRepository('User') })`.
183
+
184
+ **Query cache (optional):** `createDatabase({ ..., cache: true })` or `cache: { defaultStrategy: 'auto'|'smart', memory: { maxEntries, defaultTtlMs }, provider?: custom }`. Opt-in per model: `defineModel({ ..., cache: 'auto'|'smart'|true })`. API: `db.cache` → `getMetrics()`, `purge()`, `invalidateModel(name)`, `invalidateTags(tags[])`, `resetMetrics()`. Reads bypass cache when using a transaction knex. Admin UI: `ormCacheAdminPlugin({ db })` (needs `admin-panel` and `cache` enabled).
185
+
186
+ Pass **`db`** into **`createApp({ db })`** so **`ctx.db`** works in pages and plugins. **`pages/api/`** handlers receive **`req.db`** (and route **`middleware`** runs after it). Outside requests, use **`getDb()`** / **`hasDb()`**; for **`setupRoutes`**-only routes, use **`attachDbMiddleware`**.
187
+
188
+ ---
189
+
190
+ ## 10. Plugins (built-in — concise)
191
+
192
+ | Plugin | Purpose |
193
+ |--------|---------|
194
+ | `dashboardPlugin` | Dev route `/_webspresso` — route list |
195
+ | `sitemapPlugin` | `/sitemap.xml`, robots; optional DB-driven URLs |
196
+ | `analyticsPlugin` | GA / GTM / Yandex / Bing / Facebook — `fsy` helpers |
197
+ | `adminPanelPlugin` | SPA admin CRUD — needs **`db`**; optional **`uploadUrl`** (or infer from **`uploadPlugin`**); optional **`userManagement: { enabled, model, fields }`** + **`auth`** (same **`AuthManager`** as **`createApp({ auth })`**) for site-user CRUD + remember-me session UI — see **Session authentication** above |
198
+ | `dataExchangePlugin` | Admin-only **Excel export** + **CSV/XLSX import** under `${adminPath}/api/data-exchange/*`; register **after** `adminPanelPlugin` with same `db` / `adminPath`; optional `maxRows`, `maxFileBytes`; adds UI buttons + bulk `export-xlsx` |
199
+ | `redirectPlugin` | Configurable **301–308** redirects in `register()` — runs **before** file-based SSR routes; `rules` (`from` path or `RegExp`, `to`, `status`, `methods`), `preserveQuery`, `allowExternal`, `trailingSlash`, `defaultMethods`; docs **[`doc/index.html#plugins-redirect`](../../../doc/index.html#plugins-redirect)**, README **Redirect plugin** |
200
+ | `uploadPlugin` | `POST` multipart (`multer`), `createLocalFileProvider` or custom `provider`; set **`mimeAllowlist`** / **`maxBytes`** in production |
201
+ | `siteAnalyticsPlugin` | Self-hosted page views + admin charts |
202
+ | `auditLogPlugin` | Admin mutation audit trail |
203
+ | `recaptchaPlugin` | v2/v3 + middleware |
204
+ | `seoCheckerPlugin` | Dev SEO panel |
205
+ | `restResourcePlugin` | Opt-in REST CRUD from models; `?include=` uses ORM eager load (single-level relations only) |
206
+ | `ormCacheAdminPlugin` | Admin page for ORM cache metrics / purge / invalidate (`db.cache` required) |
207
+
208
+ **Custom plugin:** `name`, `version`, `register(ctx)`, `onRoutesReady(ctx)` — use `ctx.app`, `ctx.db`, `ctx.addHelper`, `ctx.addRoute`, `ctx.usePlugin('other')`. Plugin failures **warn**; app keeps running.
209
+
210
+ ---
211
+
212
+ ## 11. CLI (project directory)
213
+
214
+ | Command | Role |
215
+ |---------|------|
216
+ | `webspresso new` | Scaffold project |
217
+ | `webspresso new .` / `new ./` | Scaffold **into the current directory** |
218
+ | `webspresso new … --yes` | **Non-interactive** (agents / CI); use `-i` to install deps |
219
+ | `webspresso dev` / `start` | Servers |
220
+ | `webspresso page` / `api` | Interactive scaffolding |
221
+ | `webspresso db:*` | migrate, rollback, status, make |
222
+ | `webspresso upgrade` | Bump **`webspresso`** in **`package.json`** |
223
+ | `webspresso seed` | Seed data |
224
+ | `webspresso doctor` | Sanity checks |
225
+ | `webspresso skill` | Cursor **`SKILL.md`** scaffold |
226
+ | `webspresso skill --preset webspresso` | Copy bundled Webspresso agent skill (`SKILL.md` + `REFERENCE-*.md`) |
227
+ | `webspresso add tailwind` | Tailwind setup |
228
+ | `webspresso favicon:generate` | Favicons + manifest |
229
+ | `webspresso admin:setup` / `admin:password` | Admin users |
230
+ | `webspresso audit:prune` | Audit log retention |
231
+
232
+ **`webspresso new` — automation:** **`new .`** scaffolds in place. **Typical one-liners:** `webspresso new . --yes --no-tailwind` · `webspresso new . --yes -i`.
233
+
234
+ ---
235
+
236
+ ## 12. Environment variables (common)
237
+
238
+ | Var | Notes |
239
+ |-----|-------|
240
+ | `NODE_ENV` | `development` / `production` |
241
+ | `DEFAULT_LOCALE` | Default locale |
242
+ | `SUPPORTED_LOCALES` | Comma-separated |
243
+ | `BASE_URL` | Canonical / links |
244
+ | `DATABASE_URL` | DB connection |
245
+ | `SESSION_SECRET` | Session cookie signing |
246
+ | `WEBSPRESSO_ALPINE` | Forces **`clientRuntime.alpine`** when `1` / `true` |
247
+ | `WEBSPRESSO_SWUP` | Forces **`clientRuntime.swup`** when `1` / `true` |
248
+
249
+ ---
250
+
251
+ ## 13. Testing
252
+
253
+ - **Unit / integration:** `npm test` (Vitest).
254
+ - **E2E:** `npm run test:e2e` (Playwright).
255
+
256
+ ---
257
+
258
+ ## 14. Pitfalls (for agents)
259
+
260
+ 1. **Custom Express routes** — use **`setupRoutes`**, not only `app.use` after `createApp` returns.
261
+ 2. **File-router order** — understand precedence vs manual routes.
262
+ 3. **`ctx.db`** — only when `createApp({ db })` is set.
263
+ 4. **ORM hidden fields** — never expose to clients unintentionally.
264
+ 5. **Zod on API** — assume **`req.input`** when schema is set.
265
+ 6. **Built-in `auth`** — framework owns **`middlewares.auth`** / **`guest`** names.
266
+ 7. **`pages/login.njk`** — can shadow custom login; prefer **`setupRoutes`** + **`views/`**.
267
+ 8. **Client runtime + swup** — need **`<main id="swup">`** and the client-runtime partial when enabled.
268
+
269
+ ---
270
+
271
+ ## 15. When to read this file (framework)
272
+
273
+ - Pages, API routes, ORM, migrations, **SSR `createApp`**, first-party **plugins**, i18n, **session auth**, **client runtime** (Alpine / swup).
274
+ - Not for: in-process **application kernel** (event bus + `kernel.createApp`) — see **`REFERENCE-kernel.md`**.
275
+
276
+ Authoritative long-form: **[README.md](../../../README.md)**, **[`doc/index.html`](../../../doc/index.html)**.
@@ -0,0 +1,93 @@
1
+ # Webspresso — application kernel (event bus, flows, views)
2
+
3
+ This layer must **not** be confused with Express SSR. The package root **`createApp`** sets up file-based SSR; **`kernel.createApp`** is a lightweight in-process API with an event bus, optional view resolver, and flow registry.
4
+
5
+ ## Name clash
6
+
7
+ | API | Purpose |
8
+ |-----|---------|
9
+ | `require('webspresso').createApp(...)` | SSR — `pages/`, Nunjucks, Express |
10
+ | `require('webspresso').kernel.createApp(...)` | Kernel — `events`, `registerPlugin`, `registerFlow`, `view` |
11
+
12
+ ## Usage
13
+
14
+ ```javascript
15
+ const { kernel } = require('webspresso');
16
+ const app = kernel.createApp({
17
+ paths: {
18
+ appViews: './views', // optional app override root
19
+ themeViews: './themes/default',
20
+ },
21
+ });
22
+ ```
23
+
24
+ Module layout (source repo): [`core/kernel/`](../../../core/kernel/).
25
+
26
+ ## Event bus
27
+
28
+ - **`events.dispatch(name, ctx)`** — Handlers for the same name run **sequentially** with `await`; `ctx` is mutable; errors stop the caller.
29
+ - **`events.publish(name, ctx)`** — Handlers run concurrently via **`Promise.all`** (side effects / “after” events).
30
+ - **`events.on(name, handler)`** / **`off`** — Subscribe / unsubscribe.
31
+ - **`events.buildContext(payload, { source, requestId?, userId? })`** → `{ payload, meta: { source, createdAt, … } }`; `source`: `'orm' | 'auth' | 'route' | 'plugin' | 'system'`.
32
+
33
+ Standalone bus: `kernel.createEventBus()`.
34
+
35
+ ## Simulated repository (ORM lifecycle)
36
+
37
+ `kernel.BaseRepository` — in-memory store; events named by `resource`: `orm.<resource>.beforeCreate`, `afterCreate`, `beforeUpdate`, …
38
+
39
+ Implementation: [`core/kernel/base-repository.js`](../../../core/kernel/base-repository.js).
40
+
41
+ ## Plugin shell
42
+
43
+ ```javascript
44
+ const { kernel } = require('webspresso');
45
+ const myPlugin = kernel.definePlugin({
46
+ name: 'my-plugin',
47
+ events(app) {
48
+ app.events.on('orm.post.afterCreate', async (ctx) => { /* ... */ });
49
+ },
50
+ views() {
51
+ return {
52
+ namespace: 'blog',
53
+ layouts: {},
54
+ pages: { home: '<h1>{{ title }}</h1>' },
55
+ partials: {},
56
+ };
57
+ },
58
+ });
59
+ app.registerPlugin(myPlugin);
60
+ ```
61
+
62
+ ## View resolver
63
+
64
+ - Name shape: **`namespace::page`** (e.g. `blog::home`).
65
+ - Resolution order: app file overrides → theme file overrides → inline plugin templates.
66
+ - **`app.view.renderView('ns::id', data, { layout: 'ns::layoutName' })`**
67
+ - **`app.view.renderPartial(...)`**
68
+ - Minimal templates: `{{ field }}` interpolation.
69
+
70
+ ## Flow
71
+
72
+ ```javascript
73
+ app.registerFlow(
74
+ kernel.defineFlow({
75
+ trigger: 'orm.post.afterCreate',
76
+ when: (ctx) => ctx.payload.record?.status === 'published',
77
+ actions: [async (ctx, kernApp) => { /* run in order */ }],
78
+ }),
79
+ );
80
+ ```
81
+
82
+ ## Demo
83
+
84
+ From the repo: `node core/kernel/run-demo.js` — plugin, flow, and view example.
85
+
86
+ ## When to read this file
87
+
88
+ - Event-driven domain logic, “on afterCreate do X” automation.
89
+ - When you want this minimal kernel instead of SSR routes or Knex ORM `ModelEvents` alone.
90
+
91
+ Types: npm package **`index.d.ts`** (`kernel`, `KernelAppShell`, …).
92
+
93
+ HTML section: **[`doc/index.html#application-kernel`](../../../doc/index.html#application-kernel)**.