@webstir-io/webstir-backend 0.1.15 → 0.1.16

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 (123) hide show
  1. package/README.md +106 -79
  2. package/dist/add.d.ts +59 -0
  3. package/dist/add.js +626 -0
  4. package/dist/build/artifacts.d.ts +115 -1
  5. package/dist/build/artifacts.js +4 -4
  6. package/dist/build/entries.js +1 -1
  7. package/dist/build/pipeline.d.ts +33 -1
  8. package/dist/build/pipeline.js +307 -65
  9. package/dist/cache/diff.js +9 -8
  10. package/dist/cache/reporters.js +1 -1
  11. package/dist/deploy-cli.d.ts +2 -0
  12. package/dist/deploy-cli.js +86 -0
  13. package/dist/diagnostics/summary.js +2 -2
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +4 -0
  16. package/dist/manifest/pipeline.js +103 -32
  17. package/dist/provider.js +35 -17
  18. package/dist/runtime/bun.d.ts +51 -0
  19. package/dist/runtime/bun.js +499 -0
  20. package/dist/runtime/core.d.ts +141 -0
  21. package/dist/runtime/core.js +316 -0
  22. package/dist/runtime/deploy-backend.d.ts +20 -0
  23. package/dist/runtime/deploy-backend.js +175 -0
  24. package/dist/runtime/deploy-shared.d.ts +43 -0
  25. package/dist/runtime/deploy-shared.js +75 -0
  26. package/dist/runtime/deploy-static.d.ts +2 -0
  27. package/dist/runtime/deploy-static.js +161 -0
  28. package/dist/runtime/deploy.d.ts +3 -0
  29. package/dist/runtime/deploy.js +91 -0
  30. package/dist/runtime/forms.d.ts +73 -0
  31. package/dist/runtime/forms.js +236 -0
  32. package/dist/runtime/request-hooks.d.ts +47 -0
  33. package/dist/runtime/request-hooks.js +102 -0
  34. package/dist/runtime/session-metadata.d.ts +13 -0
  35. package/dist/runtime/session-metadata.js +98 -0
  36. package/dist/runtime/session-runtime.d.ts +28 -0
  37. package/dist/runtime/session-runtime.js +180 -0
  38. package/dist/runtime/session.d.ts +83 -0
  39. package/dist/runtime/session.js +396 -0
  40. package/dist/runtime/views.d.ts +74 -0
  41. package/dist/runtime/views.js +221 -0
  42. package/dist/scaffold/assets.js +25 -21
  43. package/dist/testing/context.js +1 -1
  44. package/dist/testing/index.d.ts +1 -1
  45. package/dist/testing/index.js +100 -56
  46. package/dist/utils/bun.d.ts +2 -0
  47. package/dist/utils/bun.js +13 -0
  48. package/dist/watch.d.ts +13 -1
  49. package/dist/watch.js +345 -97
  50. package/dist/workspace.d.ts +8 -0
  51. package/dist/workspace.js +44 -3
  52. package/package.json +49 -14
  53. package/scripts/publish.sh +2 -92
  54. package/scripts/smoke.mjs +282 -107
  55. package/scripts/update-contract.sh +12 -10
  56. package/src/add.ts +964 -0
  57. package/src/build/artifacts.ts +49 -46
  58. package/src/build/entries.ts +12 -12
  59. package/src/build/pipeline.ts +779 -403
  60. package/src/cache/diff.ts +111 -105
  61. package/src/cache/reporters.ts +26 -26
  62. package/src/deploy-cli.ts +111 -0
  63. package/src/diagnostics/summary.ts +28 -22
  64. package/src/index.ts +11 -0
  65. package/src/manifest/pipeline.ts +328 -215
  66. package/src/provider.ts +115 -98
  67. package/src/runtime/bun.ts +793 -0
  68. package/src/runtime/core.ts +598 -0
  69. package/src/runtime/deploy-backend.ts +239 -0
  70. package/src/runtime/deploy-shared.ts +136 -0
  71. package/src/runtime/deploy-static.ts +191 -0
  72. package/src/runtime/deploy.ts +143 -0
  73. package/src/runtime/forms.ts +364 -0
  74. package/src/runtime/request-hooks.ts +165 -0
  75. package/src/runtime/session-metadata.ts +135 -0
  76. package/src/runtime/session-runtime.ts +267 -0
  77. package/src/runtime/session.ts +642 -0
  78. package/src/runtime/views.ts +385 -0
  79. package/src/scaffold/assets.ts +77 -73
  80. package/src/testing/context.js +8 -9
  81. package/src/testing/context.ts +9 -9
  82. package/src/testing/index.d.ts +14 -3
  83. package/src/testing/index.js +254 -175
  84. package/src/testing/index.ts +298 -195
  85. package/src/testing/types.d.ts +18 -19
  86. package/src/testing/types.ts +18 -18
  87. package/src/utils/bun.ts +26 -0
  88. package/src/watch.ts +503 -99
  89. package/src/workspace.ts +59 -3
  90. package/templates/backend/.env.example +15 -0
  91. package/templates/backend/auth/adapter.ts +335 -36
  92. package/templates/backend/db/connection.ts +190 -65
  93. package/templates/backend/db/migrate.ts +149 -43
  94. package/templates/backend/db/types.d.ts +1 -1
  95. package/templates/backend/env.ts +132 -20
  96. package/templates/backend/functions/hello/index.ts +1 -2
  97. package/templates/backend/index.ts +15 -508
  98. package/templates/backend/jobs/nightly/index.ts +1 -1
  99. package/templates/backend/jobs/runtime.ts +24 -11
  100. package/templates/backend/jobs/scheduler.ts +208 -46
  101. package/templates/backend/module.ts +227 -13
  102. package/templates/backend/observability/logger.ts +2 -12
  103. package/templates/backend/observability/metrics.ts +8 -5
  104. package/templates/backend/session/sqlite.ts +152 -0
  105. package/templates/backend/session/store.ts +45 -0
  106. package/templates/backend/tsconfig.json +1 -1
  107. package/tests/add.test.js +327 -0
  108. package/tests/authAdapter.test.js +315 -0
  109. package/tests/bundlerParity.test.js +217 -0
  110. package/tests/cacheReporter.test.js +10 -10
  111. package/tests/dbConnection.test.js +209 -0
  112. package/tests/deploy.test.js +357 -0
  113. package/tests/envLoader.test.js +271 -17
  114. package/tests/integration.test.js +2432 -3
  115. package/tests/jobsScheduler.test.js +253 -0
  116. package/tests/manifest.test.js +287 -12
  117. package/tests/migrationRunner.test.js +249 -0
  118. package/tests/sessionScaffoldStore.test.js +752 -0
  119. package/tests/sessionStore.test.js +490 -0
  120. package/tests/testing.test.js +252 -0
  121. package/tests/watch.test.js +192 -32
  122. package/tsconfig.json +3 -10
  123. package/templates/backend/server/fastify.ts +0 -288
package/README.md CHANGED
@@ -1,31 +1,14 @@
1
1
  # @webstir-io/webstir-backend
2
2
 
3
- Backend build orchestration for Webstir workspaces. The package exposes a `ModuleProvider` that typechecks with TypeScript, builds with esbuild, collects build artifacts, and returns diagnostics for the Webstir CLI and installers.
4
-
5
- ## Status
6
-
7
- - Experimental provider for the Webstir ecosystem — APIs, defaults, and behavior may change between releases while things stabilize.
8
- - Not yet recommended for production workloads; treat it as a learning and exploration tool.
3
+ Backend delivery for Webstir's HTML-first application model. The package type-checks backend workspaces, builds runnable Bun-targeted output, and ships the default request runtime for server-handled forms, fragment responses, sessions, request-time views, and request-time document caching.
9
4
 
10
5
  ## Quick Start
11
6
 
12
- 1. **Authenticate to GitHub Packages**
13
- Configure user-level auth (recommended) or set an env var:
14
- - User config (`~/.npmrc`):
15
- ```ini
16
- @webstir-io:registry=https://npm.pkg.github.com
17
- //npm.pkg.github.com/:_authToken=${GH_PACKAGES_TOKEN}
18
- ```
19
- - Or export a token (CI uses `NODE_AUTH_TOKEN`):
20
- ```bash
21
- export NODE_AUTH_TOKEN="$GH_PACKAGES_TOKEN"
22
- ```
23
- Consumers need `read:packages`; publishers also require `write:packages`.
24
- 2. **Install**
7
+ 1. **Install**
25
8
  ```bash
26
- npm install @webstir-io/webstir-backend
9
+ bun add @webstir-io/webstir-backend
27
10
  ```
28
- 3. **Run a build**
11
+ 2. **Run a build**
29
12
  ```ts
30
13
  import { backendProvider } from '@webstir-io/webstir-backend';
31
14
 
@@ -38,7 +21,33 @@ Backend build orchestration for Webstir workspaces. The package exposes a `Modul
38
21
  console.log(manifest.entryPoints);
39
22
  ```
40
23
 
41
- Requires Node.js **20.18.x** or newer.
24
+ Requires Bun **1.3.11** or newer.
25
+
26
+ ## What This Runtime Is Good At
27
+
28
+ - Server-rendered HTML routes and request-time views
29
+ - HTML form workflows that still work without client JavaScript
30
+ - Redirect-after-post and fragment responses from the same backend handlers
31
+ - Session, flash, CSRF, auth, and request hooks in the default scaffold
32
+ - Request-time document caching for view shells plus explicit fragment no-store behavior
33
+
34
+ Canonical proof apps in this repo:
35
+
36
+ - [`examples/demos/auth-crud`](../../../examples/demos/auth-crud) for server-handled sign-in and CRUD forms
37
+ - [`examples/demos/dashboard`](../../../examples/demos/dashboard) for dashboard-style shell and panel refreshes without SPA architecture
38
+
39
+ ## Shipped HTML-First Runtime
40
+
41
+ The default `src/backend/index.ts` entry is the supported runtime surface. Older workspaces can keep their explicit Bun shim/runtime wrapper files if they already exist:
42
+
43
+ - Route auto-mounting from compiled `module.ts`
44
+ - Health probes at `/api/health`, `/healthz`, and `/readyz`
45
+ - Structured request logging with `x-request-id`
46
+ - Form parsing for `application/x-www-form-urlencoded`
47
+ - Redirect and fragment responses via `Location` and `x-webstir-fragment-*`
48
+ - Session, flash, and request-hook execution in the scaffold runtime
49
+ - Request-time views with `x-webstir-document-cache: miss|hit|stale`
50
+ - Explicit fragment cache bypass with `Cache-Control: no-store` and `x-webstir-fragment-cache: bypass`
42
51
 
43
52
  ## Community & Support
44
53
 
@@ -72,41 +81,39 @@ The provider expects a standard workspace layout and performs two steps:
72
81
 
73
82
  `backendProvider` implements `ModuleProvider` from `@webstir-io/module-contract`:
74
83
 
75
- - `metadata` — package id, version, kind (`backend`), CLI compatibility, Node range.
84
+ - `metadata` — package id, version, kind (`backend`), CLI compatibility, and runtime notes.
76
85
  - `resolveWorkspace({ workspaceRoot })` — returns canonical source/build/test roots.
77
86
  - `build(options)` — type‑checks with `tsc --noEmit`, then runs esbuild. In `build`/`test` mode it transpiles without bundling; in `publish` it bundles workspace code, externalizes `node_modules`, minifies, strips comments, and defines `NODE_ENV=production`. Artifacts are gathered and a manifest describing entry points, diagnostics, and the module contract manifest is returned.
78
87
  - `getScaffoldAssets()` — returns starter files to bootstrap a backend workspace:
79
88
  - `src/backend/tsconfig.json` (NodeNext, outDir `build/backend`)
80
- - `src/backend/index.ts` (built-in HTTP server with `/api/health`, `/healthz`, `/readyz`, manifest summaries, `x-request-id` propagation, and automatic module route mounting)
89
+ - `src/backend/index.ts` (thin composition entry that boots the package-managed Bun runtime)
81
90
  - `src/backend/module.ts` (optional manifest + handler example the server loads automatically)
82
- - `src/backend/server/fastify.ts` (optional Fastify server scaffold)
83
91
 
84
- ### Fastify Scaffold (optional)
92
+ ### Bun Scaffold (default)
93
+
94
+ Fresh scaffolds now boot through the package-managed Bun runtime by default:
85
95
 
86
- The default HTTP server handles `/api/health`, readiness logging, and auto-mounts the compiled `module.ts` handlers. If you prefer Fastify’s plugin ecosystem or need advanced routing features, you can swap the entry for the Fastify scaffold:
96
+ - `src/backend/index.ts` composes through `createDefaultBunBackendBootstrap(...)` from `@webstir-io/webstir-backend`, so bootstrap-level fixes can ship through package upgrades instead of template-only copies.
87
97
 
88
- - Install Fastify in your workspace:
98
+ - Start the built backend with Bun:
89
99
  ```bash
90
- npm i fastify
100
+ bun build/backend/index.js
91
101
  ```
92
- - Import and start it from your `src/backend/index.ts`:
93
- ```ts
94
- // src/backend/index.ts
95
- import { start } from './server/fastify';
96
- start().catch((err) => { console.error(err); process.exit(1); });
97
- ```
98
- - Or run it directly after a build:
102
+
103
+ Published `api` and `full` workspaces also ship the supported Bun deploy runner:
104
+
105
+ - Start the published workspace through the single-port deploy host:
99
106
  ```bash
100
- node build/backend/server/fastify.js
107
+ bun ./node_modules/.bin/webstir-backend-deploy --workspace "$PWD"
101
108
  ```
109
+ - `api` workspaces proxy all requests to the published backend runtime.
110
+ - `full` workspaces serve `dist/frontend/**` and proxy `/api/*` to the published backend runtime.
102
111
 
103
- Note: The package’s smoke test temporarily installs Fastify only to type‑check the optional scaffold. Normal users do not need Fastify unless they choose to use this server. In CI or offline environments, set `WEBSTIR_BACKEND_SMOKE_FASTIFY=skip` to bypass the Fastify install and type‑check.
104
-
105
- When present, the Fastify scaffold will also attempt to auto‑mount any compiled module routes it finds under `build/backend/module(.js)`. Export your module definition as `module`, `moduleDefinition`, or the `default` export from `src/backend/module.ts` and build; the server will attach handlers using the route metadata.
112
+ Fresh scaffolds do not copy `src/backend/server/bun.ts` or `src/backend/runtime/*` re-export files. The operational runtime lives in upgradeable package exports instead.
106
113
 
107
114
  ### Server runtime baseline
108
115
 
109
- The default `src/backend/index.ts` entry (and the optional Fastify scaffold) share the same runtime guarantees:
116
+ The default `src/backend/index.ts` entry provides these runtime guarantees:
110
117
 
111
118
  - Route auto-mounting: any `module.ts` routes are compiled, logged, and attached on startup with manifest summaries (name, version, route count, capabilities).
112
119
  - Health probes: `/api/health` (for the orchestrator), `/healthz` (generic health), and `/readyz` (status + manifest summary). The CLI still waits for `API server running` before proxying requests.
@@ -114,20 +121,33 @@ The default `src/backend/index.ts` entry (and the optional Fastify scaffold) sha
114
121
  - Request context: handlers receive `params`, `query`, `body`, `env`, `logger`, `request`, `reply`, `requestId`, and `now()` helpers that align with the `RequestContext` shape from `@webstir-io/module-contract`.
115
122
  - Request IDs: each response sets `x-request-id` and the context/logger include the same identifier so you can correlate logs.
116
123
  - Failure safety: handler exceptions are caught and surfaced as `{ error: 'internal_error' }` without tearing down the process.
124
+ - Progressive enhancement responses: handlers can return redirects (`303` by default) or targeted fragment payloads; the scaffold emits `Location` and `x-webstir-fragment-*` headers accordingly.
125
+ - Form handling: JSON bodies still work as before, and the scaffold now parses `application/x-www-form-urlencoded` requests into plain objects for HTML form workflows.
126
+
127
+ Stick with the default Bun entry while exploring the manifest helpers, or import package runtime exports directly when you need an explicit local entry. The readiness + manifest wiring stays the same.
128
+
129
+ ### Runtime cache ergonomics
117
130
 
118
- Stick with the built-in server while exploring the manifest helpers, then drop in the Fastify scaffold when you need its plugin ecosystem—the readiness + manifest wiring stays the same.
131
+ - Request-time views cache the built frontend HTML document shell in process memory, keyed by the resolved built file under `build/frontend/pages/**` (or `dist/frontend/**` when serving published output).
132
+ - The first request for a document is a cache `miss`; unchanged follow-up requests are `hit`; if the built HTML file changes on disk, the next request invalidates the stale entry, reloads it, and reports `stale`.
133
+ - Request-time document responses always send `Cache-Control: no-store` and expose the cache outcome via `x-webstir-document-cache`, so you can verify whether the runtime reused or refreshed the shell.
134
+ - Fragment responses are never reused by the scaffold runtime. They always send `Cache-Control: no-store` plus `x-webstir-fragment-cache: bypass`, because fragment bodies come from live route execution and should reflect current session/auth/request state.
135
+ - Process restarts clear the in-memory document cache. There is no separate persisted request-time HTML cache today; the existing `.webstir` cache files remain build/publish metadata, not response payload storage.
119
136
 
120
137
  ### Secrets & auth adapters
121
138
 
122
139
  The backend template now ships a lightweight auth adapter so you can secure routes without wiring a full identity provider on day one:
123
140
 
124
- - **Environment-driven secrets** — populate `.env.local`/`.env` with `AUTH_JWT_SECRET` (required for bearer tokens), optional `AUTH_JWT_ISSUER` / `AUTH_JWT_AUDIENCE`, and comma/space-delimited `AUTH_SERVICE_TOKENS`. An example lives in `templates/backend/.env.example`.
125
- - **Bearer verification (HS256)** — when `AUTH_JWT_SECRET` is set, incoming `Authorization: Bearer <token>` headers are validated using HMAC-SHA256. Matching issuer/audience claims are enforced if you provide them. On success, `ctx.auth` includes `userId`, `email`, `scopes`, `roles`, and the raw claims payload.
126
- - **Service tokens** — internal callers can present `X-Service-Token` or `X-API-Key` values that match `AUTH_SERVICE_TOKENS`. Successful matches yield a `ctx.auth` context with the `service` scope so you can distinguish automated jobs from end users.
141
+ - **Environment-driven secrets** — populate `.env.local`/`.env` with one JWT verification input: `AUTH_JWT_SECRET` for shared-secret HS256, `AUTH_JWT_PUBLIC_KEY` or `AUTH_JWT_PUBLIC_KEY_FILE` for RSA public-key verification, or `AUTH_JWKS_URL` for remote JWKS discovery. Optional `AUTH_JWT_ISSUER` / `AUTH_JWT_AUDIENCE` claims and comma/space-delimited `AUTH_SERVICE_TOKENS` still apply. An example lives in `templates/backend/.env.example`.
142
+ - **Bearer verification (HS256 + RS256)** — incoming `Authorization: Bearer <token>` headers validate against HS256 shared secrets, inline/file-backed RSA public keys, or RSA keys discovered from JWKS. Unsupported algorithms, malformed compact segments, bad signatures, wrong issuer/audience, invalid numeric-date claims, and invalid `nbf`/`exp` windows fail closed. On success, `ctx.auth` includes `userId`, `email`, `scopes`, `roles`, and the raw claims payload.
143
+ - **Service tokens** — internal callers can present `X-Service-Token` or `X-API-Key` values that match `AUTH_SERVICE_TOKENS`. Successful matches yield a `ctx.auth` context with the `service` scope so you can distinguish automated jobs from end users. If an invalid bearer token and a valid service token are both present, the service token is still accepted and bearer diagnostics stay redacted.
127
144
  - **Route ergonomics** — the module template now demonstrates gating access on `ctx.auth` and sets the `auth` capability in the manifest so downstream tooling knows the module expects identity context.
128
- - Install `pino` in your workspace (`npm install pino`) before running the scaffold; the template server imports it directly.
145
+ - **Session & request-body defaults** set `SESSION_SECRET` for stable session cookies. In development, the scaffold still falls back to a per-process random secret when unset; in production, `SESSION_SECRET` is now required and startup fails fast when it is missing. Request bodies are capped by `REQUEST_BODY_MAX_BYTES` (default `1048576`) in the supported Bun server template.
146
+ - **Durable session storage (optional)** — the scaffold now defaults to SQLite-backed sessions in production when `SESSION_STORE_DRIVER` is unset, while keeping in-memory storage as the development default. You can still opt into SQLite explicitly with `SESSION_STORE_DRIVER=sqlite` or just configure `SESSION_STORE_URL`; set `SESSION_STORE_DRIVER=memory` only when you intentionally want the non-durable path. `SESSION_STORE_URL` defaults to `file:./data/sessions.sqlite` when the SQLite store is active and resolves from the workspace root, so launch directory changes do not redirect session state into the wrong folder.
147
+ - **Session/form safety rules** — stale or tampered session cookies clear on commit, CSRF tokens are single-use after successful verification, and malformed SQLite session rows fail with a session-row diagnostic. Ordinary session updates keep the same session id; clearing a session and starting a new one creates a new id.
148
+ - Install `pino` in your workspace (`bun add pino`) before running the scaffold; the template server imports it directly.
129
149
 
130
- This adapter is intentionally simple (HS256 only) but gives you a hook to plug in third-party IdPs: generate/sign tokens there, supply the shared secret via env, and the scaffold will populate `ctx.auth` for every route.
150
+ This adapter is still intentionally scoped, but it now supports the two most common integration paths: shared-secret HS256 for local/simple deployments and RSA/JWKS verification for third-party IdPs. The scaffold populates `ctx.auth` for every route once one of those verification inputs is configured.
131
151
 
132
152
  ### Observability & metrics
133
153
 
@@ -139,33 +159,40 @@ Install `pino` (and optionally `pino-pretty` for local formatting) in any worksp
139
159
 
140
160
  ### Jobs & scheduling
141
161
 
142
- - Define jobs via `webstir add-job <name> [--schedule "<cron>"] [--description "..."] [--priority <number|label>]`. The CLI creates `src/backend/jobs/<name>/index.ts` and records metadata in `webstir.moduleManifest.jobs` in `package.json`.
162
+ - Define jobs via `webstir add-job <name> [--schedule "<cron|@macro|rate(...)>"] [--description "..."] [--priority <number|label>]`. The CLI creates `src/backend/jobs/<name>/index.ts` and records metadata in `webstir.moduleManifest.jobs` in `package.json`.
143
163
  - The template provides a zero-config job loader (`src/backend/jobs/runtime.ts`) and a lightweight scheduler/runner (`build/backend/jobs/scheduler.js`). Use it to explore your jobs without wiring a full queue:
144
164
 
145
165
  ```bash
146
- npm install pino # already needed for the server
147
- npx tsx src/backend/jobs/scheduler.ts --list
148
- node build/backend/jobs/scheduler.js --job nightly
149
- node build/backend/jobs/scheduler.js --watch # runs @hourly/@daily/@weekly/@reboot or rate(...) jobs
166
+ bun add pino # already needed for the server
167
+ bun src/backend/jobs/scheduler.ts --list
168
+ bun src/backend/jobs/scheduler.ts --json
169
+ bun build/backend/jobs/scheduler.js --job nightly
170
+ bun build/backend/jobs/scheduler.js --watch # runs cron expressions, cron nicknames, @reboot, or rate(...) jobs
150
171
  ```
151
172
 
152
- - `/readyz` surfaces manifest job counts, and `node build/backend/jobs/<name>/index.js` remains the quickest way to execute a single job in isolation.
153
- - Cron expressions recorded in the manifest are left untouched so you can plug them into your real scheduler (Temporal, Quartz, Cloud Scheduler, etc.). The built-in watcher supports the `@hourly`, `@daily`, `@weekly`, `@reboot`, and `rate(n units)` patterns for basic local loops; fall back to external tooling for full cron semantics.
173
+ - `/readyz` surfaces manifest job counts, and `bun build/backend/jobs/<name>/index.js` remains the quickest way to execute a single job in isolation.
174
+ - Cron expressions recorded in the manifest are left untouched so you can plug them into your real scheduler (Temporal, Quartz, Cloud Scheduler, etc.). On Bun `1.3.11+`, the built-in watcher now uses `Bun.cron.parse(...)` for real cron expressions and nicknames such as `0 0 * * *`, `*/15 * * * *`, `@daily`, or `@monthly`, while still preserving `rate(n units)` and `@reboot` for local development loops. Cron-based schedules wait for the next matching wall-clock time; use `--job <name>` or `--all` when you want an immediate run.
175
+ - Local watch mode skips overlapping runs for the same job and disposes scheduled timers on `SIGINT`/`SIGTERM`. Use an external scheduler or queue when you need distributed locking, retries, or durable job state.
154
176
 
155
177
  ### Database & migrations
156
178
 
157
179
  - `DATABASE_URL` defaults to `file:./data/dev.sqlite`. Point it at Postgres (`postgres://...`) or another SQLite file as needed. Override the tracking table via `DATABASE_MIGRATIONS_TABLE` (defaults to `_webstir_migrations`).
158
- - `src/backend/db/connection.ts` exposes a tiny helper that connects to SQLite (via `better-sqlite3`) or Postgres (`pg`). Install whichever driver you need in your workspace: `npm install better-sqlite3` for the default flow or `npm install pg` for Postgres.
180
+ - `src/backend/db/connection.ts` exposes a tiny helper backed by `Bun.SQL`, so the same Bun-native client now handles SQLite (`file:./data/dev.sqlite`, `sqlite:./data/dev.sqlite`, `:memory:`) and Postgres (`postgres://...`) without a separate `pg` install.
181
+ - `src/backend/session/store.ts` now owns the runtime session-store choice. Development still defaults to the in-memory store for stateless/local flows, while production defaults to SQLite unless you pin `SESSION_STORE_DRIVER=memory`. You can also persist sessions explicitly in SQLite via `src/backend/session/sqlite.ts` by setting `SESSION_STORE_DRIVER=sqlite` or just configuring `SESSION_STORE_URL`. The SQLite adapter creates its table lazily and runs on Bun without any extra SQLite package install.
159
182
  - Drop SQL/TypeScript migrations under `src/backend/db/migrations/*.ts`, exporting `id`, `up`, and optional `down`.
160
183
  - Run migrations with:
161
184
 
162
185
  ```bash
163
- npx tsx src/backend/db/migrate.ts --list
164
- npx tsx src/backend/db/migrate.ts # apply pending migrations
165
- npx tsx src/backend/db/migrate.ts --down --steps 1
186
+ bun src/backend/db/migrate.ts --list
187
+ bun src/backend/db/migrate.ts --status
188
+ bun src/backend/db/migrate.ts # apply pending migrations
189
+ bun src/backend/db/migrate.ts --down --steps 1
166
190
  ```
167
191
 
168
- - The runner logs each migration, records history in `DATABASE_MIGRATIONS_TABLE`, and works the same way once compiled (`node build/backend/db/migrate.js ...`).
192
+ - The runner logs each migration, records history in the validated `DATABASE_MIGRATIONS_TABLE`, and works the same way once compiled (`bun build/backend/db/migrate.js ...`).
193
+ - Each migration runs in a transaction with its history update. A failed `up()` rolls back and is not recorded; a failed `down()` keeps the record so it can be retried. Avoid opening nested transactions inside migration files.
194
+ - For repeatable tests, point `DATABASE_URL` at a throwaway SQLite file and use `--down` without `--steps` to run every available `down()` migration before recreating test state. App seed data should live in explicit app-owned scripts or migrations rather than an implicit runner hook.
195
+ - When you pass migration parameters, use `?` placeholders in your scaffold code. The helper keeps that style working across SQLite and Postgres.
169
196
 
170
197
  ### Module Manifest Integration
171
198
 
@@ -258,7 +285,7 @@ export const module = createModule({
258
285
  });
259
286
  ```
260
287
 
261
- When `npm run build` completes, the provider detects `build/backend/module.js`, hydrates the manifest with the `routes` metadata above, and returns it to the orchestrator alongside the compiled entry points.
288
+ When `bun run build` completes, the provider detects `build/backend/module.js`, hydrates the manifest with the `routes` metadata above, and returns it to the orchestrator alongside the compiled entry points.
262
289
 
263
290
  #### Module Definition Only Example
264
291
 
@@ -308,7 +335,7 @@ This keeps your manifest co-located with runtime code while the provider handles
308
335
 
309
336
  - `src/backend/env.ts` loads `.env.local` (if present) followed by `.env`, merges values into `process.env`, and exposes a typed `loadEnv()` helper.
310
337
  - A `.env.example` file is scaffolded at the workspace root—copy it to `.env`/`.env.local`, fill in secrets (e.g., `API_BASE_URL`, `DATABASE_URL`, `JWT_SECRET`), and adjust `loadEnv()` to require the variables your backend needs.
311
- - The default HTTP server (and Fastify scaffold) calls `loadEnv()` before binding, so the same config is available inside route handlers. Use `ctx.env.require('JWT_SECRET')` to fetch validated values.
338
+ - The default Bun scaffold calls `loadEnv()` before binding, so the same config is available inside route handlers. Use `ctx.env.require('JWT_SECRET')` to fetch validated values.
312
339
 
313
340
  ### Multiple Entry Points
314
341
  The provider discovers these entries automatically (all optional):
@@ -335,29 +362,29 @@ Artifacts are returned as absolute paths so installers can copy or upload them.
335
362
 
336
363
  | Script | Description |
337
364
  |--------|-------------|
338
- | `npm run build` | Compiles provider TypeScript from `src/` into `dist/`. |
339
- | `npm test` | Builds and runs Node's test runner over `tests/**/*.test.js`. |
340
- | `npm run smoke` | Quick end-to-end check: scaffolds a temp workspace and runs build/publish via the provider. |
341
- | `npm run clean` | Removes `dist/`. |
365
+ | `bun run build` | Compiles provider TypeScript from `src/` into `dist/`. |
366
+ | `bun run test` | Builds and runs Node's test runner over `tests/**/*.test.js`. |
367
+ | `bun run smoke` | Quick end-to-end check: scaffolds a temp workspace and runs build/publish via the provider. |
368
+ | `bun run clean` | Removes `dist/`. |
342
369
 
343
370
  The published package ships prebuilt JavaScript and type definitions in `dist/`.
344
371
 
345
372
  ## Maintainer Workflow
346
373
 
347
374
  ```bash
348
- npm install
349
- npm run clean # remove dist artifacts
350
- npm run build # emits dist/
351
- npm run test # runs unit/integration tests
352
- npm run smoke
353
- # Release helper (bumps version, pushes tags to trigger release workflow)
354
- npm run release -- patch
375
+ bun install
376
+ bun run clean # remove dist artifacts
377
+ bun run build # emits dist/
378
+ bun run test # runs unit/integration tests
379
+ bun run smoke
380
+ # Release helper (bumps version and pushes a package-scoped release tag)
381
+ bun run release -- patch
355
382
  ```
356
383
 
357
- - Add tests under `tests/**/*.test.ts` and wire them into `npm test` once the backend runtime is ready.
358
- - Ensure CI runs `npm ci`, `npm run clean`, `npm run build`, `npm run test`, and `npm run smoke` before publish.
359
- - Publishing targets GitHub Packages via `publishConfig.registry`.
360
- - Use `npm run release -- <patch|minor|major|x.y.z>` to bump the version, build, test, run the smoke check, and push tags to trigger the release workflow.
384
+ - Add tests under `tests/**/*.test.ts` and wire them into `bun run test` once the backend runtime is ready.
385
+ - Ensure CI runs `bun install --frozen-lockfile`, `bun run clean`, `bun run build`, `bun run test`, and `bun run smoke` before publish.
386
+ - Publishing targets npm via `publishConfig.registry`.
387
+ - Use `bun run release -- <patch|minor|major|x.y.z>` to bump the version, build, test, run the smoke check, and push a package-scoped tag that triggers the monorepo release workflow.
361
388
 
362
389
  ## Troubleshooting
363
390
 
@@ -383,8 +410,8 @@ MIT © Webstir
383
410
  Start incremental builds with type-checking in the background:
384
411
 
385
412
  ```bash
386
- npm run dev # type-check + transpile on change
387
- npm run dev:fast # faster DX: skip tsc in watch
413
+ bun run dev # type-check + transpile on change
414
+ bun run dev:fast # faster DX: skip tsc in watch
388
415
  ```
389
416
 
390
417
  Notes
@@ -424,4 +451,4 @@ If you use `getScaffoldAssets()` programmatically, these templates are included
424
451
  - The backend template listens on `process.env.PORT` (default `4000`) and logs `API server running` when ready.
425
452
  - The orchestrator's dev server waits for that readiness line and proxies `/api/*` to your Node server.
426
453
  - Health probes: `/api/health` (orchestrator compatibility) mirrors `/healthz`, while `/readyz` exposes the readiness state plus the current manifest summary for external monitors.
427
- - If you switch to a framework (e.g., Fastify), keep the same behavior: listen on `process.env.PORT`, expose the same endpoints, and print `API server running` once the server is listening.
454
+ - If you replace the default runtime locally, keep the same behavior: listen on `process.env.PORT`, expose the same endpoints, and print `API server running` once the server is listening.
package/dist/add.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { FragmentUpdateMode, SessionAccessMode } from '@webstir-io/module-contract';
2
+ export interface AddRouteOptions {
3
+ readonly workspaceRoot: string;
4
+ readonly name: string;
5
+ readonly method?: string;
6
+ readonly path?: string;
7
+ readonly summary?: string;
8
+ readonly description?: string;
9
+ readonly tags?: readonly string[];
10
+ readonly interaction?: string;
11
+ readonly sessionMode?: string;
12
+ readonly sessionWrite?: boolean;
13
+ readonly formUrlEncoded?: boolean;
14
+ readonly formCsrf?: boolean;
15
+ readonly fragmentTarget?: string;
16
+ readonly fragmentSelector?: string;
17
+ readonly fragmentMode?: string;
18
+ readonly paramsSchema?: string;
19
+ readonly querySchema?: string;
20
+ readonly bodySchema?: string;
21
+ readonly headersSchema?: string;
22
+ readonly responseSchema?: string;
23
+ readonly responseStatus?: string | number;
24
+ readonly responseHeadersSchema?: string;
25
+ }
26
+ export interface AddJobOptions {
27
+ readonly workspaceRoot: string;
28
+ readonly name: string;
29
+ readonly schedule?: string;
30
+ readonly description?: string;
31
+ readonly priority?: string | number;
32
+ }
33
+ export interface UpdateRouteContractOptions {
34
+ readonly workspaceRoot: string;
35
+ readonly method: string;
36
+ readonly path: string;
37
+ readonly sessionMode?: SessionAccessMode | string | null;
38
+ readonly sessionWrite?: boolean | null;
39
+ readonly formUrlEncoded?: boolean | null;
40
+ readonly formCsrf?: boolean | null;
41
+ readonly fragmentTarget?: string | null;
42
+ readonly fragmentSelector?: string | null;
43
+ readonly fragmentMode?: FragmentUpdateMode | string | null;
44
+ readonly paramsSchema?: string | null;
45
+ readonly querySchema?: string | null;
46
+ readonly bodySchema?: string | null;
47
+ readonly headersSchema?: string | null;
48
+ readonly responseSchema?: string | null;
49
+ readonly responseStatus?: string | number | null;
50
+ readonly responseHeadersSchema?: string | null;
51
+ }
52
+ export interface BackendAddResult {
53
+ readonly subject: 'route' | 'job';
54
+ readonly target: string;
55
+ readonly changes: readonly string[];
56
+ }
57
+ export declare function runAddRoute(options: AddRouteOptions): Promise<BackendAddResult>;
58
+ export declare function runAddJob(options: AddJobOptions): Promise<BackendAddResult>;
59
+ export declare function runUpdateRouteContract(options: UpdateRouteContractOptions): Promise<BackendAddResult>;