@jay-framework/jay-stack-cli 0.17.2 → 0.17.4
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/devops/INSTRUCTIONS.md +23 -0
- package/agent-kit-template/devops/fetch-handler.md +122 -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 +91 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +1 -0
- package/agent-kit-template/plugin/component-refs.md +7 -3
- package/agent-kit-template/plugin/plugin-structure.md +12 -0
- package/agent-kit-template/plugin/webhooks-guide.md +124 -0
- package/dist/index.js +559 -445
- 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)
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
|
13
|
+
3. **Serve** — start the production server with environment-appropriate flags
|
|
14
|
+
4. **Invalidate** — rebuild specific pages when data changes
|
|
15
|
+
|
|
16
|
+
## Guides
|
|
17
|
+
|
|
18
|
+
| File | Topic |
|
|
19
|
+
| ------------------------------------------ | -------------------------------------------------------- |
|
|
20
|
+
| [production-build.md](production-build.md) | Build pipeline, output structure, frontend/backend split |
|
|
21
|
+
| [serving-modes.md](serving-modes.md) | Self-hosted, CDN, BaaS (fetch handler), CLI flags |
|
|
22
|
+
| [fetch-handler.md](fetch-handler.md) | @jay-framework/jay-fetch-handler for BaaS integration |
|
|
23
|
+
| [invalidation.md](invalidation.md) | Rebuild, renderer server, cleanup |
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
backendDir: string; // Path to build/v{n}/backend/
|
|
27
|
+
staticBaseUrl?: string; // Base URL for browser assets (default: '/')
|
|
28
|
+
frontendDir?: string; // When set, serves static files from this directory
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Option | Required | Description |
|
|
33
|
+
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
34
|
+
| `backendDir` | Yes | Path to the backend build directory containing manifest, server modules, and pre-rendered files |
|
|
35
|
+
| `staticBaseUrl` | No | URL prefix for import maps, CSS links, and client bundles. Set to your CDN URL for external hosting. Default: `/` |
|
|
36
|
+
| `frontendDir` | No | When provided, the handler serves static files from this directory. Omit for CDN deployments where static files are hosted elsewhere |
|
|
37
|
+
|
|
38
|
+
## Usage — Wix BaaS
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
|
|
42
|
+
|
|
43
|
+
const handler = createJayFetchHandler({
|
|
44
|
+
backendDir: './build/v1/backend',
|
|
45
|
+
staticBaseUrl: 'https://static.parastorage.com/services/my-app/1.0.0/',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export default { fetch: handler };
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The BaaS runtime calls `handler(request)` for each incoming HTTP request.
|
|
52
|
+
|
|
53
|
+
## Usage — Cloudflare Workers
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
|
|
57
|
+
|
|
58
|
+
const handler = createJayFetchHandler({
|
|
59
|
+
backendDir: './backend',
|
|
60
|
+
staticBaseUrl: 'https://cdn.example.com/assets/',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export default { fetch: handler };
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage — Standalone with HTTP Server
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { createJayFetchHandler } from '@jay-framework/jay-fetch-handler';
|
|
70
|
+
import http from 'node:http';
|
|
71
|
+
import { Readable } from 'node:stream';
|
|
72
|
+
|
|
73
|
+
const handler = createJayFetchHandler({
|
|
74
|
+
backendDir: './build/v1/backend',
|
|
75
|
+
staticBaseUrl: '/',
|
|
76
|
+
frontendDir: './build/v1/frontend',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
http
|
|
80
|
+
.createServer(async (req, res) => {
|
|
81
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
82
|
+
const headers = new Headers();
|
|
83
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
84
|
+
if (v) headers.set(k, Array.isArray(v) ? v.join(', ') : v);
|
|
85
|
+
}
|
|
86
|
+
const init: RequestInit = { method: req.method, headers };
|
|
87
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
88
|
+
init.body = Readable.toWeb(req) as ReadableStream;
|
|
89
|
+
(init as any).duplex = 'half';
|
|
90
|
+
}
|
|
91
|
+
const request = new Request(url, init);
|
|
92
|
+
const response = await handler(request);
|
|
93
|
+
|
|
94
|
+
const resHeaders: Record<string, string> = {};
|
|
95
|
+
response.headers.forEach((v, k) => {
|
|
96
|
+
resHeaders[k] = v;
|
|
97
|
+
});
|
|
98
|
+
res.writeHead(response.status, resHeaders);
|
|
99
|
+
if (response.body) {
|
|
100
|
+
const reader = response.body.getReader();
|
|
101
|
+
while (true) {
|
|
102
|
+
const { done, value } = await reader.read();
|
|
103
|
+
if (done) break;
|
|
104
|
+
res.write(value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
res.end();
|
|
108
|
+
})
|
|
109
|
+
.listen(4000);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This is what `jay-stack serve` does internally. Use the CLI for standard deployments; use the handler directly when you need custom server logic.
|
|
113
|
+
|
|
114
|
+
## Behavior
|
|
115
|
+
|
|
116
|
+
The handler processes requests in this order:
|
|
117
|
+
|
|
118
|
+
1. **Actions** — `/_jay/actions/*` routes to the action registry
|
|
119
|
+
2. **Static files** — if `frontendDir` is set, checks `frontend/`, then `frontend/public/`
|
|
120
|
+
3. **Page requests** — matches against the route manifest, runs fast-phase SSR, streams HTML
|
|
121
|
+
|
|
122
|
+
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,91 @@
|
|
|
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
|
+
## CLI Flags
|
|
47
|
+
|
|
48
|
+
### jay-stack serve
|
|
49
|
+
|
|
50
|
+
| Flag | Default | Description |
|
|
51
|
+
| ------------------------- | ------------------- | ----------------------------------------------------------- |
|
|
52
|
+
| `--port <n>` | `3000` | Server port |
|
|
53
|
+
| `--version <n>` | from package.json | Build version to serve |
|
|
54
|
+
| `--role <role>` | `main` | `main` (pages + actions) or `renderer` (webhooks + rebuild) |
|
|
55
|
+
| `--static-base-url <url>` | `/` | Base URL for all browser-facing assets |
|
|
56
|
+
| `--no-serve-static` | (serves by default) | Disable serving static files from `frontend/` |
|
|
57
|
+
| `--test-mode` | off | Enable `/_jay/health` and `/_jay/shutdown` endpoints |
|
|
58
|
+
| `-v, --verbose` | off | Verbose logging |
|
|
59
|
+
|
|
60
|
+
### jay-stack build
|
|
61
|
+
|
|
62
|
+
| Flag | Default | Description |
|
|
63
|
+
| --------------- | ----------------- | -------------------------------- |
|
|
64
|
+
| `--version <n>` | from package.json | Build version number |
|
|
65
|
+
| `--no-minify` | minified | Disable minification (debugging) |
|
|
66
|
+
| `-v, --verbose` | off | Verbose logging |
|
|
67
|
+
|
|
68
|
+
## Test Mode
|
|
69
|
+
|
|
70
|
+
When `--test-mode` is enabled, the server exposes:
|
|
71
|
+
|
|
72
|
+
| Endpoint | Method | Response |
|
|
73
|
+
| ---------------- | ------ | ---------------------------------------------------------- |
|
|
74
|
+
| `/_jay/health` | GET | `{"status":"ready","port":4000,"uptime":5.2}` |
|
|
75
|
+
| `/_jay/shutdown` | POST | `{"status":"shutting_down"}` — gracefully stops the server |
|
|
76
|
+
|
|
77
|
+
Use for smoke tests and CI pipelines. The dev server (`jay-stack dev --test-mode`) has the same endpoints.
|
|
78
|
+
|
|
79
|
+
## Two-Server Architecture
|
|
80
|
+
|
|
81
|
+
For data-driven sites, run two servers:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Main server — handles page requests
|
|
85
|
+
jay-stack serve --role main --port 4000
|
|
86
|
+
|
|
87
|
+
# Renderer server — handles webhooks and rebuilds
|
|
88
|
+
jay-stack serve --role renderer --port 4001
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
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,6 +29,7 @@ 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 |
|
|
@@ -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
|
|
|
@@ -35,6 +35,10 @@ actions:
|
|
|
35
35
|
- name: addToCart
|
|
36
36
|
action: add-to-cart.jay-action
|
|
37
37
|
|
|
38
|
+
webhooks:
|
|
39
|
+
- name: onProductChange
|
|
40
|
+
- name: onInventoryUpdate
|
|
41
|
+
|
|
38
42
|
services:
|
|
39
43
|
- name: my-store
|
|
40
44
|
marker: MY_STORE_SERVICE_MARKER
|
|
@@ -114,6 +118,12 @@ tags:
|
|
|
114
118
|
- `name` — Action name (used with `jay-stack action <plugin>/<action>`)
|
|
115
119
|
- `action` — Path to `.jay-action` metadata file
|
|
116
120
|
|
|
121
|
+
### Webhook Entry Fields
|
|
122
|
+
|
|
123
|
+
- `name` — Export name of the `makeWebhook()` constant (e.g., `onProductChange`)
|
|
124
|
+
|
|
125
|
+
Webhooks are exposed at `POST /_jay/webhooks/{webhookName}` on the renderer server. The `webhookName` comes from the `makeWebhook('plugin.event-name')` call, not from the export name. The export name in plugin.yaml tells the framework which export to load from the plugin module.
|
|
126
|
+
|
|
117
127
|
### Service Entry Fields
|
|
118
128
|
|
|
119
129
|
- `name` — Service name (for identification in plugins-index)
|
|
@@ -183,6 +193,8 @@ my-plugin/
|
|
|
183
193
|
│ ├── actions/
|
|
184
194
|
│ │ ├── search-products.jay-action
|
|
185
195
|
│ │ └── add-to-cart.jay-action
|
|
196
|
+
│ ├── webhooks/
|
|
197
|
+
│ │ └── on-product-change.ts
|
|
186
198
|
│ ├── components/
|
|
187
199
|
│ │ ├── product-page.ts
|
|
188
200
|
│ │ └── product-search.ts
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Webhooks & Data Change Invalidation
|
|
2
|
+
|
|
3
|
+
Webhooks let external systems (CMS, database, etc.) notify the renderer server when data changes, triggering targeted rebuilds of affected pages.
|
|
4
|
+
|
|
5
|
+
## makeWebhook
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { makeWebhook } from '@jay-framework/fullstack-component';
|
|
9
|
+
|
|
10
|
+
export const onProductChange = makeWebhook('wix-stores.product-change')
|
|
11
|
+
.withServices(PRODUCTS_SERVICE)
|
|
12
|
+
.withHandler(async (event, invalidate, productsService) => {
|
|
13
|
+
const slug = await productsService.resolveSlug(event.payload.itemId);
|
|
14
|
+
await invalidate('product-page', { slug });
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Builder API
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
makeWebhook('plugin-name.event-name')
|
|
22
|
+
.withServices(SERVICE1, SERVICE2) // Inject services
|
|
23
|
+
.withHandler(async (event, invalidate, svc1, svc2) => {
|
|
24
|
+
// event.type — webhook name
|
|
25
|
+
// event.payload — parsed JSON body from the HTTP request
|
|
26
|
+
// event.headers — HTTP headers
|
|
27
|
+
// invalidate(contractName, params?) — trigger rebuild
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## The invalidate Function
|
|
32
|
+
|
|
33
|
+
The `invalidate` callback resolves contract names to routes and rebuilds affected instances:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Rebuild a specific instance — finds routes using 'product-page' contract,
|
|
37
|
+
// rebuilds the instance matching { slug: 'blue-widget' }
|
|
38
|
+
await invalidate('product-page', { slug: 'blue-widget' });
|
|
39
|
+
|
|
40
|
+
// Rebuild ALL instances of routes using 'search-results' contract
|
|
41
|
+
await invalidate('search-results');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Contract-based, not route-based.** A plugin knows its own contract names but not the project's route structure. The framework resolves which routes use each contract via the `contracts` field in the route manifest.
|
|
45
|
+
|
|
46
|
+
## Declaring in plugin.yaml
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
webhooks:
|
|
50
|
+
- name: onProductChange
|
|
51
|
+
- name: onInventoryUpdate
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The `name` field is the **export name** from the plugin module. The webhook URL is derived from the `makeWebhook()` name argument, not the export name.
|
|
55
|
+
|
|
56
|
+
## URL Mapping
|
|
57
|
+
|
|
58
|
+
Webhooks are exposed on the renderer server at:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
POST /_jay/webhooks/{webhookName}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Where `{webhookName}` is the first argument to `makeWebhook()`. Example:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
makeWebhook('wix-stores.product-change')
|
|
68
|
+
→ POST /_jay/webhooks/wix-stores.product-change
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Optimistic Skip
|
|
72
|
+
|
|
73
|
+
After re-running the slow render, the framework compares the new pre-rendered jay-html with the existing one. If the template structure is unchanged (e.g., a price change updates ViewState but doesn't toggle a slow conditional), the server element compilation and Vite client build are skipped.
|
|
74
|
+
|
|
75
|
+
## Project Webhooks
|
|
76
|
+
|
|
77
|
+
Projects can define webhooks in `src/webhooks/`:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// src/webhooks/on-content-update.ts
|
|
81
|
+
import { makeWebhook } from '@jay-framework/fullstack-component';
|
|
82
|
+
|
|
83
|
+
export const onContentUpdate = makeWebhook('cms.content-update').withHandler(
|
|
84
|
+
async (event, invalidate) => {
|
|
85
|
+
const { pageSlug } = event.payload as { pageSlug: string };
|
|
86
|
+
await invalidate('content-page', { slug: pageSlug });
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Project webhooks don't need plugin.yaml — they are discovered by scanning compiled files in `server/webhooks/`.
|
|
92
|
+
|
|
93
|
+
## CLI Rebuild
|
|
94
|
+
|
|
95
|
+
For manual or CI-triggered rebuilds without a webhook:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# By contract — rebuild all routes using this contract
|
|
99
|
+
jay-stack rebuild --contract product-page
|
|
100
|
+
|
|
101
|
+
# By contract + params — rebuild specific instance
|
|
102
|
+
jay-stack rebuild --contract product-page --params '{"slug":"blue-widget"}'
|
|
103
|
+
|
|
104
|
+
# By route — rebuild all instances of a route (useful for pages with page.ts, no contract)
|
|
105
|
+
jay-stack rebuild --route /products/[slug]
|
|
106
|
+
|
|
107
|
+
# By route + params — rebuild specific instance
|
|
108
|
+
jay-stack rebuild --route /products/[slug] --params '{"slug":"blue-widget"}'
|
|
109
|
+
|
|
110
|
+
# By URL — resolve URL to route+params, rebuild that instance
|
|
111
|
+
jay-stack rebuild --url /products/blue-widget
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Renderer Server
|
|
115
|
+
|
|
116
|
+
The renderer server (`jay-stack serve --role=renderer`) hosts webhook endpoints and a rebuild API:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
POST /_jay/webhooks/:name — Webhook handler (plugin-defined)
|
|
120
|
+
POST /_jay/rebuild — Programmatic rebuild { contract, route, url, params }
|
|
121
|
+
GET /_jay/status — Health check + build info
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The `POST /_jay/rebuild` endpoint accepts one of `contract`, `route`, or `url` in the JSON body.
|