@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.
- package/agent-kit-template/designer/jay-html-components.md +2 -0
- package/agent-kit-template/designer/jay-html-syntax.md +1 -1
- package/agent-kit-template/designer/project-structure.md +5 -0
- package/agent-kit-template/developer/cli-commands.md +5 -1
- package/agent-kit-template/developer/component-refs.md +7 -3
- package/agent-kit-template/developer/project-structure.md +5 -0
- package/agent-kit-template/developer/render-results.md +37 -0
- package/agent-kit-template/developer/routing.md +25 -0
- package/agent-kit-template/devops/INSTRUCTIONS.md +24 -0
- package/agent-kit-template/devops/fetch-handler.md +185 -0
- package/agent-kit-template/devops/invalidation.md +77 -0
- package/agent-kit-template/devops/production-build.md +80 -0
- package/agent-kit-template/devops/serving-modes.md +118 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +2 -0
- package/agent-kit-template/plugin/actions-guide.md +2 -10
- package/agent-kit-template/plugin/commands-guide.md +121 -0
- package/agent-kit-template/plugin/component-refs.md +7 -3
- package/agent-kit-template/plugin/component-structure.md +26 -2
- package/agent-kit-template/plugin/plugin-structure.md +27 -0
- package/agent-kit-template/plugin/render-results.md +37 -0
- package/agent-kit-template/plugin/webhooks-guide.md +124 -0
- package/dist/index.js +732 -456
- package/package.json +11 -11
|
@@ -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).
|
|
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
|
-
|
|
36
|
-
|
|
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 |
|