@jay-framework/jay-stack-cli 0.17.3 → 0.18.0

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.
@@ -89,6 +89,8 @@ Inside `<jay:...>`, bindings resolve to **that instance's** contract tags (not t
89
89
 
90
90
  Headfull components own their UI and can be made full-stack by adding a `contract` attribute.
91
91
 
92
+ Headfull FS components must be placed in `src/components/` — each component in its own subdirectory with `.ts`, `.jay-html`, and `.jay-contract` files. The production build only discovers server-side component modules from `src/components/` and `src/plugins/`. Placing them inside page directories will work in dev mode but fail in production.
93
+
92
94
  ### Import Declaration
93
95
 
94
96
  ```html
@@ -14,7 +14,7 @@ Entry point at `src/pages/`. Can import all component types.
14
14
 
15
15
  ### Headfull FS
16
16
 
17
- Reusable component with its own template + contract + three-phase rendering (slow/fast/interactive). Lives alongside the page in a components directory. Can nest other headfull FS and instance headless in its own `<head>`. Cannot use keyed headless.
17
+ Reusable component with its own template + contract + three-phase rendering (slow/fast/interactive). Must live in `src/components/` (not inside page directories) so the production build can discover and compile them. Can nest other headfull FS and instance headless in its own `<head>`. Cannot use keyed headless.
18
18
 
19
19
  ### Headless
20
20
 
@@ -14,6 +14,11 @@ my-project/
14
14
  │ ├── project.conf.yaml # Project metadata (name, etc.)
15
15
  │ └── <plugin-name>.yaml # Plugin-specific config files
16
16
  ├── src/
17
+ │ ├── components/ # Headfull full-stack components (shared across pages)
18
+ │ │ └── site-header/
19
+ │ │ ├── site-header.ts
20
+ │ │ ├── site-header.jay-html
21
+ │ │ └── site-header.jay-contract
17
22
  │ ├── pages/ # Pages (directory-based routing)
18
23
  │ │ ├── page.jay-html # Homepage → /
19
24
  │ │ ├── page.jay-contract # Homepage contract (optional)
@@ -64,7 +64,7 @@ Validate all `.jay-html` and `.jay-contract` files.
64
64
  jay-stack validate
65
65
 
66
66
  # Validate a specific path
67
- jay-stack validate src/pages/products/
67
+ jay-stack validate -p src/pages/products/
68
68
 
69
69
  # Verbose (per-file status)
70
70
  jay-stack validate -v
@@ -167,6 +167,10 @@ If not found, lists available actions:
167
167
  Available actions: searchProducts, getProductBySlug, getCategories
168
168
  ```
169
169
 
170
+ ## Production Commands
171
+
172
+ For `jay-stack build`, `jay-stack serve`, and `jay-stack rebuild`, see the [DevOps guides](../devops/INSTRUCTIONS.md).
173
+
170
174
  ## jay-stack dev
171
175
 
172
176
  Start the development server.
@@ -31,9 +31,13 @@ refs.submitButton.onClick(() => {
31
31
  /* ... */
32
32
  });
33
33
 
34
- // exec$ gives direct access to the element and current ViewState
35
- refs.submitButton.exec$((element, viewState) => {
36
- element.disabled = viewState.isSubmitting;
34
+ // exec$ gives direct access to the element and current ViewState.
35
+ // Only use exec$ inside event handlers — never at top-level component
36
+ // creation or in effects, because elements don't exist yet at that point.
37
+ refs.submitButton.onclick(() => {
38
+ refs.submitButton.exec$((element, viewState) => {
39
+ element.disabled = viewState.isSubmitting;
40
+ });
37
41
  });
38
42
  ```
39
43
 
@@ -14,6 +14,11 @@ my-project/
14
14
  │ ├── project.conf.yaml # Project metadata (name, etc.)
15
15
  │ └── <plugin-name>.yaml # Plugin-specific config files
16
16
  ├── src/
17
+ │ ├── components/ # Headfull full-stack components (shared across pages)
18
+ │ │ └── site-header/
19
+ │ │ ├── site-header.ts
20
+ │ │ ├── site-header.jay-html
21
+ │ │ └── site-header.jay-contract
17
22
  │ ├── pages/ # Pages (directory-based routing)
18
23
  │ │ ├── page.jay-html # Homepage → /
19
24
  │ │ ├── page.jay-contract # Homepage contract (optional)
@@ -17,6 +17,43 @@ return phaseOutput(
17
17
 
18
18
  CarryForward is available in the next phase via `props.carryForward` but is not part of the ViewState.
19
19
 
20
+ ### Response Headers (fast phase only)
21
+
22
+ The third parameter accepts `responseHeaders` to set HTTP headers on the page response:
23
+
24
+ ```typescript
25
+ return phaseOutput(
26
+ { memberName: member.name },
27
+ {},
28
+ { responseHeaders: { 'Cache-Control': 'no-store' } },
29
+ );
30
+ ```
31
+
32
+ Use this when the page renders per-user data that must not be cached by CDN or browser. Can be combined with `headTags` in the same options object.
33
+
34
+ ### Cookies (fast phase only)
35
+
36
+ The fast phase receives `props.cookies` — a `Record<string, string>` parsed from the HTTP `Cookie` header:
37
+
38
+ ```typescript
39
+ .withFastRender(async (props, memberService) => {
40
+ const token = props.cookies['session-token'];
41
+ if (!token) return redirect3xx(302, '/login');
42
+
43
+ const member = await memberService.validate(token);
44
+ if (!member) return redirect3xx(302, '/login');
45
+
46
+ return phaseOutput(
47
+ { memberName: member.name },
48
+ {},
49
+ { responseHeaders: { 'Cache-Control': 'no-store' } },
50
+ );
51
+ })
52
+ ```
53
+
54
+ - `props.cookies` is `Record<string, string>` — empty `{}` when no cookies
55
+ - Not available in the slow phase (compile error) — same as `props.query`
56
+
20
57
  ## Error Results
21
58
 
22
59
  Return errors to stop rendering and show an error page:
@@ -160,6 +160,31 @@ URL query parameters (`?page=2&sort=price`) are available in the **fast render p
160
160
  - Not available in the slow phase (compile error) — slow results are cached by path params only
161
161
  - In the interactive phase, use `new URLSearchParams(window.location.search)` directly
162
162
 
163
+ ## Cookies
164
+
165
+ HTTP cookies are available in the **fast render phase only** via `props.cookies`:
166
+
167
+ ```typescript
168
+ .withFastRender(async (props, carryForward, memberService) => {
169
+ const token = props.cookies['session-token'];
170
+ if (!token) return redirect3xx(302, '/login');
171
+
172
+ const member = await memberService.validate(token);
173
+ if (!member) return redirect3xx(302, '/login');
174
+
175
+ return phaseOutput(
176
+ { memberName: member.name },
177
+ {},
178
+ { responseHeaders: { 'Cache-Control': 'no-store' } },
179
+ );
180
+ })
181
+ ```
182
+
183
+ - `props.cookies` is `Record<string, string>` — empty `{}` when no cookies
184
+ - Not available in the slow phase (compile error) — same as query params
185
+ - In the interactive phase, use `document.cookie` directly
186
+ - To set response headers (e.g. `Cache-Control: no-store`), use `responseHeaders` in `phaseOutput()` options
187
+
163
188
  ## Plugin Routes
164
189
 
165
190
  Plugins can provide their own pages via `routes` in `plugin.yaml`. These are backoffice tools, admin dashboards, or editors with boxed designs that don't need per-site customization.
@@ -0,0 +1,24 @@
1
+ # Jay Stack DevOps — Agent Kit
2
+
3
+ This folder contains guides for building, deploying, and operating jay-stack projects in production.
4
+
5
+ ## What Does the DevOps Role Do?
6
+
7
+ The devops role handles the production lifecycle: building artifacts, configuring deployment environments, serving in different modes (self-hosted, CDN, BaaS), and managing content invalidation. This is distinct from the developer role (creates page logic), designer role (creates UI), and plugin role (creates headless components).
8
+
9
+ ## Workflow
10
+
11
+ 1. **Build** — `jay-stack build` to compile all pages into production artifacts
12
+ 2. **Deploy** — upload `frontend/` to CDN, deploy `backend/` to server container. Plugins can provide deploy commands via `jay-stack run <plugin>/deploy`
13
+ 3. **Serve** — start the production server with environment-appropriate flags
14
+ 4. **Invalidate** — rebuild specific pages when data changes
15
+ 5. **Admin** — run plugin CLI commands via `jay-stack run <plugin>/<command>` (media upload, data sync, cache purge)
16
+
17
+ ## Guides
18
+
19
+ | File | Topic |
20
+ | ------------------------------------------ | ---------------------------------------------------------- |
21
+ | [production-build.md](production-build.md) | Build pipeline, output structure, frontend/backend split |
22
+ | [serving-modes.md](serving-modes.md) | Self-hosted, CDN, BaaS (fetch handler), CLI flags |
23
+ | [fetch-handler.md](fetch-handler.md) | Fetch handler, ArtifactStore interface, BaaS custom stores |
24
+ | [invalidation.md](invalidation.md) | Rebuild, renderer server, cleanup |
@@ -0,0 +1,185 @@
1
+ # Fetch Handler Package
2
+
3
+ ## Overview
4
+
5
+ `@jay-framework/jay-fetch-handler` exports a standard `(Request) → Response` function for BaaS platforms (Wix, Cloudflare Workers) where an HTTP server is not needed — the platform provides the HTTP layer and calls the fetch function directly.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @jay-framework/jay-fetch-handler
11
+ ```
12
+
13
+ ## API
14
+
15
+ ```typescript
16
+ import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
17
+
18
+ const handler = createJayFetchHandler(options);
19
+ // handler: (request: Request) => Promise<Response>
20
+ ```
21
+
22
+ ### Options
23
+
24
+ ```typescript
25
+ interface JayFetchHandlerOptions {
26
+ // Artifact source (one required)
27
+ backendDir?: string; // Path to build/v{n}/backend/ (creates FilesystemArtifactStore)
28
+ artifactStore?: ArtifactStore; // Custom store for non-filesystem backends (DL#143)
29
+
30
+ staticBaseUrl?: string; // Base URL for browser assets (default: '/')
31
+ frontendDir?: string; // When set, serves static files from this directory
32
+
33
+ // Pre-imported modules — for bundled entry.mjs (DL#143)
34
+ plugins?: PreImportedPlugin[];
35
+ actionModules?: Array<{ module: Record<string, unknown>; name: string }>;
36
+ }
37
+ ```
38
+
39
+ | Option | Required | Description |
40
+ | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
41
+ | `backendDir` | \* | Path to the backend build directory. Creates a `FilesystemArtifactStore` internally |
42
+ | `artifactStore` | \* | Custom `ArtifactStore` implementation (e.g., cloud storage). Use instead of `backendDir` |
43
+ | `staticBaseUrl` | No | URL prefix for import maps, CSS links, and client bundles. Set to your CDN URL for external hosting. Default: `/` |
44
+ | `frontendDir` | No | When provided, the handler serves static files from this directory. Omit for CDN deployments where static files are hosted elsewhere |
45
+ | `plugins` | No | Pre-imported plugin init modules. Bypasses filesystem discovery — use for bundled deployments |
46
+ | `actionModules` | No | Pre-imported action modules. Bypasses filesystem discovery — use for bundled deployments |
47
+
48
+ \* One of `backendDir` or `artifactStore` is required.
49
+
50
+ ## Usage — Self-Hosted
51
+
52
+ ```typescript
53
+ import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
54
+
55
+ const handler = createJayFetchHandler({
56
+ backendDir: './build/v1/backend',
57
+ staticBaseUrl: '/',
58
+ frontendDir: './build/v1/frontend',
59
+ });
60
+ ```
61
+
62
+ ## Usage — CDN Mode
63
+
64
+ ```typescript
65
+ const handler = createJayFetchHandler({
66
+ backendDir: './build/v1/backend',
67
+ staticBaseUrl: 'https://static.parastorage.com/services/my-app/1.0.0/',
68
+ });
69
+
70
+ export default { fetch: handler };
71
+ ```
72
+
73
+ The BaaS runtime calls `handler(request)` for each incoming HTTP request.
74
+
75
+ ## Usage — BaaS with Custom Artifact Store
76
+
77
+ For deployments where backend files are not on the local filesystem (e.g., stored in a cloud database), provide a custom `ArtifactStore` and pre-imported modules:
78
+
79
+ ```typescript
80
+ import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
81
+ import { WixDataArtifactStore } from '@jay-framework/wix-baas-adapter';
82
+ import { init as wixStoresInit } from '@jay-framework/wix-stores';
83
+ import * as wixStoresModule from '@jay-framework/wix-stores';
84
+
85
+ const handler = createJayFetchHandler({
86
+ artifactStore: new WixDataArtifactStore({
87
+ collectionId: 'jay-backend-files',
88
+ cacheDir: '/tmp/jay-backend',
89
+ }),
90
+ staticBaseUrl: 'https://static.parastorage.com/services/my-app/1.0.0/',
91
+ plugins: [{ name: 'wix-stores', init: wixStoresInit }],
92
+ actionModules: [{ module: wixStoresModule, name: 'wix-stores' }],
93
+ });
94
+
95
+ export default { fetch: handler };
96
+ ```
97
+
98
+ The `ArtifactStore` interface:
99
+
100
+ ```typescript
101
+ interface ArtifactStore {
102
+ readManifest(): Promise<RouteManifest>;
103
+ readCacheData(relativePath: string): Promise<CacheEntry>;
104
+ readPagePartsConfig(relativePath: string): Promise<any>;
105
+ loadServerElement(relativePath: string): Promise<ServerElementModule>;
106
+ loadModule(modulePath: string, local?: boolean): Promise<any>;
107
+ getAssetPath(relativePath: string): string;
108
+ getBuildDir(): string;
109
+ }
110
+ ```
111
+
112
+ `loadModule` handles all module loading — server elements, page components, headless components. The `local` flag indicates whether the path is relative to the build directory (`true`) or an npm package (`false`). For filesystem deployments, local modules resolve from `basePath` and npm modules use bare `import()`. BaaS implementations resolve all modules from their pre-bundled registry, ignoring the `local` flag.
113
+
114
+ For serve-only imports (no build-time dependencies), use `@jay-framework/production-server/serve`.
115
+
116
+ ## Usage — Cloudflare Workers
117
+
118
+ ```typescript
119
+ import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
120
+
121
+ const handler = createJayFetchHandler({
122
+ backendDir: './backend',
123
+ staticBaseUrl: 'https://cdn.example.com/assets/',
124
+ });
125
+
126
+ export default { fetch: handler };
127
+ ```
128
+
129
+ ## Usage — Standalone with HTTP Server
130
+
131
+ ```typescript
132
+ import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
133
+ import http from 'node:http';
134
+ import { Readable } from 'node:stream';
135
+
136
+ const handler = createJayFetchHandler({
137
+ backendDir: './build/v1/backend',
138
+ staticBaseUrl: '/',
139
+ frontendDir: './build/v1/frontend',
140
+ });
141
+
142
+ http
143
+ .createServer(async (req, res) => {
144
+ const url = new URL(req.url, `http://${req.headers.host}`);
145
+ const headers = new Headers();
146
+ for (const [k, v] of Object.entries(req.headers)) {
147
+ if (v) headers.set(k, Array.isArray(v) ? v.join(', ') : v);
148
+ }
149
+ const init: RequestInit = { method: req.method, headers };
150
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
151
+ init.body = Readable.toWeb(req) as ReadableStream;
152
+ (init as any).duplex = 'half';
153
+ }
154
+ const request = new Request(url, init);
155
+ const response = await handler(request);
156
+
157
+ const resHeaders: Record<string, string> = {};
158
+ response.headers.forEach((v, k) => {
159
+ resHeaders[k] = v;
160
+ });
161
+ res.writeHead(response.status, resHeaders);
162
+ if (response.body) {
163
+ const reader = response.body.getReader();
164
+ while (true) {
165
+ const { done, value } = await reader.read();
166
+ if (done) break;
167
+ res.write(value);
168
+ }
169
+ }
170
+ res.end();
171
+ })
172
+ .listen(4000);
173
+ ```
174
+
175
+ This is what `jay-stack serve` does internally. Use the CLI for standard deployments; use the handler directly when you need custom server logic.
176
+
177
+ ## Behavior
178
+
179
+ The handler processes requests in this order:
180
+
181
+ 1. **Actions** — `/_jay/actions/*` routes to the action registry
182
+ 2. **Static files** — if `frontendDir` is set, checks `frontend/`, then `frontend/public/`
183
+ 3. **Page requests** — matches against the route manifest, runs fast-phase SSR, streams HTML
184
+
185
+ Initialization (loading manifest, running `init.ts`, registering actions) happens lazily on the first request.
@@ -0,0 +1,77 @@
1
+ # Invalidation & Rebuild
2
+
3
+ ## When to Rebuild
4
+
5
+ Page instances are pre-rendered at build time with slow-phase data. When that data changes (product updated, content edited), the affected instances need to be rebuilt without a full build.
6
+
7
+ ## jay-stack rebuild
8
+
9
+ Three targeting modes:
10
+
11
+ ```bash
12
+ # By contract — rebuild all routes using this contract
13
+ jay-stack rebuild --contract product-page
14
+
15
+ # By route — rebuild all instances of a route pattern
16
+ jay-stack rebuild --route /products/[slug]
17
+
18
+ # By URL — resolve to route+params, rebuild that one instance
19
+ jay-stack rebuild --url /products/blue-widget
20
+ ```
21
+
22
+ Narrow it down with `--params`:
23
+
24
+ ```bash
25
+ # Rebuild one specific instance
26
+ jay-stack rebuild --contract product-page --params '{"slug":"blue-widget"}'
27
+ jay-stack rebuild --route /products/[slug] --params '{"slug":"blue-widget"}'
28
+ ```
29
+
30
+ ### How it works
31
+
32
+ 1. Reads the route manifest to find affected routes/instances
33
+ 2. Re-runs slow render with fresh data
34
+ 3. Re-compiles server element and client bundle
35
+ 4. Updates the instance entry in the manifest
36
+ 5. Touches `build-metadata.json` to trigger main server manifest reload
37
+ 6. Old files are tracked in `cleanup-manifest.json` for later cleanup
38
+
39
+ ### Cleanup
40
+
41
+ After rebuilds, orphaned files (old client bundles, old server elements) accumulate. Clean them up:
42
+
43
+ ```bash
44
+ jay-stack cleanup
45
+ ```
46
+
47
+ ## Renderer Server
48
+
49
+ For automated invalidation, run the renderer server alongside the main server:
50
+
51
+ ```bash
52
+ # Main server — pages and actions
53
+ jay-stack serve --role main --port 4000
54
+
55
+ # Renderer server — listens for webhooks, triggers rebuilds
56
+ jay-stack serve --role renderer --port 4001
57
+ ```
58
+
59
+ The renderer server:
60
+
61
+ - Listens for data change webhooks from external systems (CMS, e-commerce)
62
+ - Determines which routes are affected (via contract name → route resolution)
63
+ - Runs `rebuild` for each affected instance
64
+ - The main server detects the updated `build-metadata.json` and reloads the manifest
65
+
66
+ ### Contract-based resolution
67
+
68
+ The manifest tracks which contracts each route uses. When a webhook says "product-page data changed", the renderer finds all routes that use the `product-page` contract and rebuilds their instances.
69
+
70
+ ## Build Output After Rebuild
71
+
72
+ Rebuilt instances write new files to both `frontend/` and `backend/`:
73
+
74
+ - `backend/pre-rendered/` — updated jay-html, cache, server element
75
+ - `frontend/pages/` — updated client bundle, CSS
76
+
77
+ Old files are not deleted immediately — they're tracked in `cleanup-manifest.json`. This avoids breaking in-flight requests that still reference old bundles.
@@ -0,0 +1,80 @@
1
+ # Production Build
2
+
3
+ ## Building
4
+
5
+ ```bash
6
+ jay-stack build
7
+ jay-stack build --version 2
8
+ jay-stack build --no-minify # debugging
9
+ jay-stack build -v # verbose output
10
+ ```
11
+
12
+ Version defaults to the `version` field in `package.json` (e.g., `"1.2.3"` → build version `10203`). Override with `--version`.
13
+
14
+ ## Build Output Structure
15
+
16
+ The build produces two directories under `build/v{n}/`:
17
+
18
+ ```
19
+ build/v{n}/
20
+ ├── frontend/ # Browser-facing assets (→ CDN or static serving)
21
+ │ ├── shared/ # Framework + plugin client chunks
22
+ │ │ ├── runtime-{hash}.js
23
+ │ │ ├── component-{hash}.js
24
+ │ │ └── shared-manifest.json
25
+ │ ├── pages/ # Per-page client bundles + CSS
26
+ │ │ ├── index/
27
+ │ │ │ ├── page-{hash}.js
28
+ │ │ │ └── page.css
29
+ │ │ └── products/[slug]/
30
+ │ │ ├── page_{hash}-{hash}.js
31
+ │ │ └── page_{hash}.css
32
+ │ └── public/ # Copied from project ./public
33
+ │ └── images/
34
+ │ └── logo.png
35
+
36
+ ├── backend/ # Server-only artifacts (→ container)
37
+ │ ├── route-manifest.json # All routes, instances, action registry
38
+ │ ├── build-metadata.json # Version, timestamp, instance count
39
+ │ ├── server/ # Compiled server code
40
+ │ │ ├── init.js
41
+ │ │ ├── pages/{route}/page.js
42
+ │ │ ├── components/{name}/{name}.js
43
+ │ │ ├── plugins/{name}/{name}.js
44
+ │ │ └── actions/{name}.actions.js
45
+ │ └── pre-rendered/ # SSR artifacts per instance
46
+ │ └── {route}/
47
+ │ ├── page.jay-html # Pre-rendered HTML template
48
+ │ ├── page.cache.json # Slow ViewState + carryForward
49
+ │ ├── page.server-element.js # Streaming SSR module
50
+ │ └── page-parts.json # Component wiring config
51
+ ```
52
+
53
+ ## Frontend vs Backend
54
+
55
+ | Directory | Contains | Deploy to |
56
+ | ----------- | -------------------------------------------------------------------------- | ------------------------- |
57
+ | `frontend/` | JS bundles, CSS, images — everything the browser loads | CDN or static file server |
58
+ | `backend/` | Server modules, pre-rendered HTML, manifests — everything the server reads | Container / server |
59
+
60
+ The build is **environment-agnostic**. The same output serves any deployment mode. `staticBaseUrl` (where browser assets are hosted) is a serve-time parameter, not baked into the build.
61
+
62
+ ## Manifest
63
+
64
+ `backend/route-manifest.json` contains:
65
+
66
+ - **routes** — pattern, segments, server module path, instances with params
67
+ - **instances** — `preRenderedPath` and `serverElementPath` (relative to `backend/`), `clientBundlePath` and `clientCssPath` (relative to `frontend/`)
68
+ - **actions** — server module paths, action names
69
+ - **sharedManifest** — maps package names to hashed filenames in `frontend/shared/`
70
+
71
+ ## Project Structure Requirements
72
+
73
+ For production builds to work correctly:
74
+
75
+ - **Headfull FS components** must be in `src/components/` (not inside page directories)
76
+ - **Headless plugins** must be in `src/plugins/`
77
+ - **Actions** must be in `src/actions/` with `*.actions.ts` naming
78
+ - **Init** must be at `src/init.ts`
79
+
80
+ The build discovers server-side modules from `src/pages/`, `src/components/`, `src/plugins/`, and `src/actions/`. Files outside these directories are not compiled for server use.
@@ -0,0 +1,118 @@
1
+ # Serving Modes
2
+
3
+ ## Overview
4
+
5
+ The production server supports three deployment modes, all using the same build output:
6
+
7
+ | Mode | Static files | Server | Use case |
8
+ | ---------------- | ------------------------------ | ----------------------------------------------------------- | ------------------------------------ |
9
+ | **Self-hosted** | Server serves from `frontend/` | `jay-stack serve` | Local testing, standalone deployment |
10
+ | **CDN** | Uploaded to external CDN | `jay-stack serve --static-base-url <url> --no-serve-static` | Production with CDN |
11
+ | **BaaS (fetch)** | Uploaded to CDN | `createJayFetchHandler()` | Wix, Cloudflare Workers |
12
+
13
+ ## Self-Hosted (Default)
14
+
15
+ The server serves both pages and static files. No external CDN needed.
16
+
17
+ ```bash
18
+ jay-stack build
19
+ jay-stack serve --port 4000
20
+ ```
21
+
22
+ Static files are served from `build/v{n}/frontend/` at these URL prefixes:
23
+
24
+ - `/shared/` — framework client chunks
25
+ - `/pages/` — per-page client bundles and CSS
26
+ - `/` — public folder assets (images, fonts, JSON)
27
+
28
+ ## CDN Mode
29
+
30
+ Static files are hosted on an external CDN. The server only handles page requests and actions.
31
+
32
+ ```bash
33
+ jay-stack build
34
+
35
+ # Upload frontend/ to CDN
36
+ # e.g., aws s3 sync build/v1/frontend/ s3://my-bucket/app/1.0.0/
37
+
38
+ # Start server with CDN URL
39
+ jay-stack serve --port 4000 \
40
+ --static-base-url https://cdn.example.com/app/1.0.0/ \
41
+ --no-serve-static
42
+ ```
43
+
44
+ The server generates import maps, CSS links, and client bundle URLs prefixed with `--static-base-url`. It does not serve static files itself.
45
+
46
+ ## BaaS Mode (Custom Artifact Store)
47
+
48
+ For platforms where backend files are not on the local filesystem (e.g., stored in a cloud database), use `createJayFetchHandler` with a custom `ArtifactStore` and pre-imported modules:
49
+
50
+ ```typescript
51
+ import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
52
+
53
+ const handler = createJayFetchHandler({
54
+ artifactStore: customStore, // Custom ArtifactStore implementation
55
+ staticBaseUrl: 'https://cdn.example.com/app/1.0.0/',
56
+ plugins: [
57
+ // Pre-imported plugin init modules
58
+ { name: 'my-plugin', init: myPluginInit },
59
+ ],
60
+ actionModules: [
61
+ // Pre-imported action modules
62
+ { module: myPluginModule, name: 'my-plugin' },
63
+ ],
64
+ });
65
+
66
+ export default { fetch: handler };
67
+ ```
68
+
69
+ Pre-imported modules bypass filesystem discovery — the entry file bundles everything with esbuild. See [fetch-handler.md](fetch-handler.md) for the `ArtifactStore` interface and full BaaS example.
70
+
71
+ For serve-only imports without build-time dependencies, use `@jay-framework/production-server/serve`.
72
+
73
+ ## CLI Flags
74
+
75
+ ### jay-stack serve
76
+
77
+ | Flag | Default | Description |
78
+ | ------------------------- | ------------------- | ----------------------------------------------------------- |
79
+ | `--port <n>` | `3000` | Server port |
80
+ | `--version <n>` | from package.json | Build version to serve |
81
+ | `--role <role>` | `main` | `main` (pages + actions) or `renderer` (webhooks + rebuild) |
82
+ | `--static-base-url <url>` | `/` | Base URL for all browser-facing assets |
83
+ | `--no-serve-static` | (serves by default) | Disable serving static files from `frontend/` |
84
+ | `--test-mode` | off | Enable `/_jay/health` and `/_jay/shutdown` endpoints |
85
+ | `-v, --verbose` | off | Verbose logging |
86
+
87
+ ### jay-stack build
88
+
89
+ | Flag | Default | Description |
90
+ | --------------- | ----------------- | -------------------------------- |
91
+ | `--version <n>` | from package.json | Build version number |
92
+ | `--no-minify` | minified | Disable minification (debugging) |
93
+ | `-v, --verbose` | off | Verbose logging |
94
+
95
+ ## Test Mode
96
+
97
+ When `--test-mode` is enabled, the server exposes:
98
+
99
+ | Endpoint | Method | Response |
100
+ | ---------------- | ------ | ---------------------------------------------------------- |
101
+ | `/_jay/health` | GET | `{"status":"ready","port":4000,"uptime":5.2}` |
102
+ | `/_jay/shutdown` | POST | `{"status":"shutting_down"}` — gracefully stops the server |
103
+
104
+ Use for smoke tests and CI pipelines. The dev server (`jay-stack dev --test-mode`) has the same endpoints.
105
+
106
+ ## Two-Server Architecture
107
+
108
+ For data-driven sites, run two servers:
109
+
110
+ ```bash
111
+ # Main server — handles page requests
112
+ jay-stack serve --role main --port 4000
113
+
114
+ # Renderer server — handles webhooks and rebuilds
115
+ jay-stack serve --role renderer --port 4001
116
+ ```
117
+
118
+ The renderer server listens for data change webhooks and rebuilds affected page instances. The main server picks up the updated artifacts automatically (it re-reads the manifest when `build-metadata.json` changes).
@@ -29,9 +29,11 @@ A plugin provides headless components (data + interactions, no UI) that project
29
29
  | [component-context.md](component-context.md) | Context hooks: provide, reactive, global |
30
30
  | [render-results.md](render-results.md) | phaseOutput, RenderPipeline, errors, redirects |
31
31
  | [actions-guide.md](actions-guide.md) | makeJayAction, makeJayQuery, .jay-action files |
32
+ | [webhooks-guide.md](webhooks-guide.md) | makeWebhook, data change invalidation, renderer server |
32
33
  | [services-guide.md](services-guide.md) | createJayService, makeJayInit |
33
34
  | [plugin-routes.md](plugin-routes.md) | Plugin-provided pages: routes, jay-html templates, page components |
34
35
  | [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
36
+ | [commands-guide.md](commands-guide.md) | makeCliCommand, .jay-command files, CONSOLE_CONTEXT, jay-stack run |
35
37
  | [validation.md](validation.md) | jay-stack validate-plugin usage |
36
38
  | [dev-server-service.md](dev-server-service.md) | Dev server service API: routes, params, freeze management |
37
39
  | `../references/<plugin>/` | Plugin reference data |