@jay-framework/jay-stack-cli 0.15.5 → 0.16.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/{INSTRUCTIONS.md → designer/INSTRUCTIONS.md} +11 -8
- package/agent-kit-template/{jay-html-syntax.md → designer/jay-html-components.md} +89 -158
- package/agent-kit-template/designer/jay-html-styling.md +97 -0
- package/agent-kit-template/designer/jay-html-syntax.md +44 -0
- package/agent-kit-template/designer/jay-html-template-syntax.md +203 -0
- package/agent-kit-template/developer/INSTRUCTIONS.md +34 -0
- package/agent-kit-template/developer/cli-commands.md +228 -0
- package/agent-kit-template/developer/component-data.md +109 -0
- package/agent-kit-template/developer/component-refs.md +117 -0
- package/agent-kit-template/developer/component-state.md +140 -0
- package/agent-kit-template/developer/configuration.md +76 -0
- package/agent-kit-template/developer/dev-server-service.md +126 -0
- package/agent-kit-template/developer/page-components.md +103 -0
- package/agent-kit-template/developer/page-contracts.md +114 -0
- package/agent-kit-template/developer/project-structure.md +242 -0
- package/agent-kit-template/developer/render-results.md +112 -0
- package/agent-kit-template/developer/routing.md +175 -0
- package/agent-kit-template/developer/seo-guide.md +93 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +43 -0
- package/agent-kit-template/plugin/actions-guide.md +184 -0
- package/agent-kit-template/plugin/component-context.md +103 -0
- package/agent-kit-template/plugin/component-data.md +109 -0
- package/agent-kit-template/plugin/component-refs.md +117 -0
- package/agent-kit-template/plugin/component-state.md +140 -0
- package/agent-kit-template/plugin/component-structure.md +174 -0
- package/agent-kit-template/plugin/contracts-guide.md +193 -0
- package/agent-kit-template/plugin/dev-server-service.md +137 -0
- package/agent-kit-template/plugin/plugin-routes.md +146 -0
- package/agent-kit-template/plugin/plugin-structure.md +210 -0
- package/agent-kit-template/plugin/render-results.md +112 -0
- package/agent-kit-template/plugin/seo-guide.md +93 -0
- package/agent-kit-template/plugin/services-guide.md +116 -0
- package/agent-kit-template/plugin/validation.md +101 -0
- package/dist/index.js +791 -60
- package/package.json +10 -10
- /package/agent-kit-template/{cli-commands.md → designer/cli-commands.md} +0 -0
- /package/agent-kit-template/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +0 -0
- /package/agent-kit-template/{project-structure.md → designer/project-structure.md} +0 -0
- /package/agent-kit-template/{routing.md → designer/routing.md} +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Contract Authoring Guide
|
|
2
|
+
|
|
3
|
+
Contracts (`.jay-contract` files) are the source of truth for a component's data shape. Define the contract before implementing the component.
|
|
4
|
+
|
|
5
|
+
## Basic Structure
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
name: ProductCard
|
|
9
|
+
description: Displays a single product with price and add-to-cart. Use for product grids and featured sections.
|
|
10
|
+
props:
|
|
11
|
+
- name: productId
|
|
12
|
+
type: string
|
|
13
|
+
required: true
|
|
14
|
+
description: The product to display
|
|
15
|
+
params:
|
|
16
|
+
slug: string
|
|
17
|
+
tags:
|
|
18
|
+
- tag: name
|
|
19
|
+
type: data
|
|
20
|
+
dataType: string
|
|
21
|
+
phase: slow
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Tag Types
|
|
25
|
+
|
|
26
|
+
### `data` — Read-only values
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
- tag: productName
|
|
30
|
+
type: data
|
|
31
|
+
dataType: string # string (default), number, boolean, date
|
|
32
|
+
required: true # optional, defaults to false
|
|
33
|
+
phase: slow # slow, fast, or fast+interactive
|
|
34
|
+
description: Display name
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `variant` — Enum/boolean for conditionals
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
- tag: status
|
|
41
|
+
type: variant
|
|
42
|
+
dataType: enum (AVAILABLE | OUT_OF_STOCK | PREORDER)
|
|
43
|
+
phase: fast+interactive
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `interactive` — Element refs for user interaction
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
- tag: addToCart
|
|
50
|
+
type: interactive
|
|
51
|
+
elementType: HTMLButtonElement # HTMLAnchorElement, HTMLInputElement, HTMLSelectElement, etc.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Interactive tags are always `fast+interactive` — do not specify a phase.
|
|
55
|
+
|
|
56
|
+
A tag can be both data and interactive:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
- tag: quantityInput
|
|
60
|
+
type: [data, interactive]
|
|
61
|
+
dataType: number
|
|
62
|
+
elementType: HTMLInputElement
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `sub-contract` — Nested objects
|
|
66
|
+
|
|
67
|
+
Inline:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
- tag: pricing
|
|
71
|
+
type: sub-contract
|
|
72
|
+
tags:
|
|
73
|
+
- tag: amount
|
|
74
|
+
type: data
|
|
75
|
+
dataType: number
|
|
76
|
+
- tag: currency
|
|
77
|
+
type: data
|
|
78
|
+
dataType: string
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Linked (reference another contract file):
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
- tag: author
|
|
85
|
+
type: sub-contract
|
|
86
|
+
link: ./author.jay-contract
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `sub-contract` with `repeated: true` — Arrays
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
- tag: items
|
|
93
|
+
type: sub-contract
|
|
94
|
+
repeated: true
|
|
95
|
+
trackBy: id # Required: identifies each item
|
|
96
|
+
phase: fast
|
|
97
|
+
tags:
|
|
98
|
+
- tag: id
|
|
99
|
+
type: data
|
|
100
|
+
dataType: string
|
|
101
|
+
- tag: name
|
|
102
|
+
type: data
|
|
103
|
+
dataType: string
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`trackBy` must reference a `data` tag with `string` or `number` type within the same sub-contract.
|
|
107
|
+
|
|
108
|
+
## Async Data
|
|
109
|
+
|
|
110
|
+
Wrap any tag in `Promise<T>` with `async: true`:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
- tag: reviews
|
|
114
|
+
type: data
|
|
115
|
+
async: true
|
|
116
|
+
dataType: string # Compiles to Promise<string>
|
|
117
|
+
|
|
118
|
+
- tag: relatedProducts
|
|
119
|
+
type: sub-contract
|
|
120
|
+
repeated: true
|
|
121
|
+
trackBy: id
|
|
122
|
+
async: true # Compiles to Promise<Array<...>>
|
|
123
|
+
tags:
|
|
124
|
+
- tag: id
|
|
125
|
+
type: data
|
|
126
|
+
dataType: string
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Rendering Phases
|
|
130
|
+
|
|
131
|
+
Each tag has a phase that determines when its data is available:
|
|
132
|
+
|
|
133
|
+
| Phase | When | Use For |
|
|
134
|
+
| ------------------ | ------------------ | --------------------------------------- |
|
|
135
|
+
| `slow` | Build time (SSG) | Static content, SEO data, product names |
|
|
136
|
+
| `fast` | Request time (SSR) | Per-request data, live pricing, stock |
|
|
137
|
+
| `fast+interactive` | Request + client | Data that also updates on the client |
|
|
138
|
+
|
|
139
|
+
**How to choose:**
|
|
140
|
+
|
|
141
|
+
- Can the data be known at build time? Use `slow`
|
|
142
|
+
- Does it change per request (user, time, session)? Use `fast`
|
|
143
|
+
- Does it also update on the client after interaction? Use `fast+interactive`
|
|
144
|
+
- Interactive tags (refs) are always `fast+interactive`
|
|
145
|
+
|
|
146
|
+
**Phase rules for arrays:** Child phases must be >= parent phase. If the array is `fast`, all children must be `fast` or later.
|
|
147
|
+
|
|
148
|
+
## Props vs Params
|
|
149
|
+
|
|
150
|
+
### Props — Component configuration
|
|
151
|
+
|
|
152
|
+
Props are passed by the parent component or jay-html template. Use for component inputs like IDs, configuration flags, display options.
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
props:
|
|
156
|
+
- name: productId
|
|
157
|
+
type: string
|
|
158
|
+
required: true
|
|
159
|
+
description: The product to display
|
|
160
|
+
- name: showPricing
|
|
161
|
+
type: boolean
|
|
162
|
+
default: 'true'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Params — URL route segments
|
|
166
|
+
|
|
167
|
+
Params come from dynamic route segments (`[slug]`, `[[lang]]`, `[...path]`). Use for page-level routing data.
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
params:
|
|
171
|
+
slug: string # required — from [slug]
|
|
172
|
+
lang: string? # optional — from [[lang]]
|
|
173
|
+
path: string[] # catch-all — from [...path]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Description Field
|
|
177
|
+
|
|
178
|
+
Always include a `description` at the contract level explaining when to use this contract:
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
name: product-search
|
|
182
|
+
description: Product listing with filters, sorting, and pagination. Use for search results and category pages.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Validation Rules
|
|
186
|
+
|
|
187
|
+
- Tag names must be unique at each level
|
|
188
|
+
- `repeated: true` requires `trackBy`
|
|
189
|
+
- `trackBy` must reference a `data` tag with `string` or `number` type
|
|
190
|
+
- Interactive tags cannot have an explicit `phase`
|
|
191
|
+
- Sub-contracts must have either `tags` (inline) or `link` (external), not both
|
|
192
|
+
- Array children must have phase >= parent phase
|
|
193
|
+
- Prop names must be unique
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Dev Server Service API
|
|
2
|
+
|
|
3
|
+
The dev server exposes a `DevServerService` for plugins, design board applications, and CLI tools. It provides route listing, param discovery, and freeze management.
|
|
4
|
+
|
|
5
|
+
## Service Marker
|
|
6
|
+
|
|
7
|
+
Registered as `DEV_SERVER_SERVICE` — inject in actions and components:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { DEV_SERVER_SERVICE } from '@jay-framework/dev-server';
|
|
11
|
+
|
|
12
|
+
export const listAllRoutes = makeJayAction('admin.listRoutes')
|
|
13
|
+
.withServices(DEV_SERVER_SERVICE)
|
|
14
|
+
.withHandler(async (_input, devServer) => {
|
|
15
|
+
return devServer.listRoutes();
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or in a component:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
makeJayStackComponent()
|
|
23
|
+
.withServices(DEV_SERVER_SERVICE)
|
|
24
|
+
.withFastRender(async (_props, devServer) => {
|
|
25
|
+
const routes = devServer.listRoutes();
|
|
26
|
+
return phaseOutput({ routes, routeCount: routes.length }, {});
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Direct Access
|
|
31
|
+
|
|
32
|
+
Also returned from `mkDevServer()` for CLI usage:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const { service } = await mkDevServer(options);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Routes
|
|
39
|
+
|
|
40
|
+
### listRoutes()
|
|
41
|
+
|
|
42
|
+
Returns all page routes in the project (including plugin-provided routes):
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const routes = service.listRoutes();
|
|
46
|
+
// [{ path: '/products/kitan/[[category]]', jayHtmlPath: '...', compPath: '...' }]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### loadRouteParams(route)
|
|
50
|
+
|
|
51
|
+
Async generator that yields param batches from the route's `loadParams`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
for await (const batch of service.loadRouteParams('/products/kitan/[[category]]')) {
|
|
55
|
+
console.log(batch); // [{ category: 'shirts' }, { category: 'pants' }]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Returns empty if the route has no `page.ts` or no `loadParams`. Throws if the route doesn't exist.
|
|
60
|
+
|
|
61
|
+
## Freeze Management
|
|
62
|
+
|
|
63
|
+
### FreezeStore
|
|
64
|
+
|
|
65
|
+
Accessible via `service.freezeStore`:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const store = service.freezeStore;
|
|
69
|
+
|
|
70
|
+
// Save a ViewState snapshot
|
|
71
|
+
const entry = await store.save('/products/kitan', viewState);
|
|
72
|
+
|
|
73
|
+
// List freezes for a route
|
|
74
|
+
const freezes = await store.list('/products/kitan');
|
|
75
|
+
|
|
76
|
+
// Get a specific freeze
|
|
77
|
+
const freeze = await store.get('abc123');
|
|
78
|
+
|
|
79
|
+
// Rename
|
|
80
|
+
await store.rename('abc123', 'in-stock state');
|
|
81
|
+
|
|
82
|
+
// Delete
|
|
83
|
+
await store.delete('abc123');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Editor Protocol
|
|
87
|
+
|
|
88
|
+
These APIs are also exposed via the editor protocol (Socket.IO) for design board applications:
|
|
89
|
+
|
|
90
|
+
| Protocol Message | Service Method |
|
|
91
|
+
| ----------------- | ----------------------------------------- |
|
|
92
|
+
| `listRoutes` | `service.listRoutes()` |
|
|
93
|
+
| `listFreezes` | `service.freezeStore.list(route)` |
|
|
94
|
+
| `renameFreeze` | `service.freezeStore.rename(id, name)` |
|
|
95
|
+
| `deleteFreeze` | `service.freezeStore.delete(id)` |
|
|
96
|
+
| `loadRouteParams` | `service.loadRouteParams(route, onBatch)` |
|
|
97
|
+
|
|
98
|
+
### Streaming Events
|
|
99
|
+
|
|
100
|
+
`loadRouteParams` streams batches via `routeParamsBatch` socket events:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Client sends: { type: 'loadRouteParams', route: '/products/[slug]' }
|
|
104
|
+
// Server responds: { type: 'loadRouteParams', success: true }
|
|
105
|
+
// Server emits: { type: 'routeParamsBatch', route: '...', params: [...], hasMore: true }
|
|
106
|
+
// Server emits: { type: 'routeParamsBatch', route: '...', params: [...], hasMore: true }
|
|
107
|
+
// Server emits: { type: 'routeParamsBatch', route: '...', params: [], hasMore: false }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Freeze Changed Event
|
|
111
|
+
|
|
112
|
+
The `freezeChanged` socket event is emitted when jay-html or CSS files change. Design board applications should listen for this to refresh their frozen views:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
socket.on('freezeChanged', () => {
|
|
116
|
+
// Re-fetch frozen page fragments
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Iframe / Embed Mode
|
|
121
|
+
|
|
122
|
+
When a page is loaded inside an iframe with `?_jay_embed=true` (e.g., by the AIditor), the Alt+S shortcut is disabled (parent owns it). Freeze is triggered via `postMessage`:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Parent → iframe: request freeze
|
|
126
|
+
iframe.contentWindow.postMessage({ type: 'jay:requestFreeze' }, '*');
|
|
127
|
+
|
|
128
|
+
// Iframe → parent: freeze done
|
|
129
|
+
// { type: 'jay:freeze', id: string, route: string }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The parent constructs the frozen page URL:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
route + '?_jay_freeze=' + id // full page
|
|
136
|
+
route + '?_jay_freeze=' + id + '&format=fragment' // shadow DOM fragment
|
|
137
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Plugin Routes
|
|
2
|
+
|
|
3
|
+
Plugins can provide complete pages served by the dev server. This is designed for backoffice tools, admin dashboards, and editors — pages with a boxed design that doesn't need per-site visual customization.
|
|
4
|
+
|
|
5
|
+
## When to Use Plugin Routes
|
|
6
|
+
|
|
7
|
+
- **Admin dashboards** — product management, analytics, settings
|
|
8
|
+
- **Editor tools** — visual page editors, contract browsers
|
|
9
|
+
- **Developer tools** — debugging panels, state inspectors
|
|
10
|
+
|
|
11
|
+
Plugin routes are NOT for end-user pages that need visual customization per site. For those, provide headless components and let the project create its own pages.
|
|
12
|
+
|
|
13
|
+
## Creating a Plugin Route
|
|
14
|
+
|
|
15
|
+
A plugin route is a **headless component + jay-html template + route path**. It uses the same rendering pipeline as project pages.
|
|
16
|
+
|
|
17
|
+
### 1. Create the jay-html template
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<!-- pages/admin/page.jay-html -->
|
|
21
|
+
<html>
|
|
22
|
+
<head>
|
|
23
|
+
<script type="application/jay-data">
|
|
24
|
+
data:
|
|
25
|
+
title: string
|
|
26
|
+
items:
|
|
27
|
+
- name: string
|
|
28
|
+
count: number
|
|
29
|
+
</script>
|
|
30
|
+
<style>
|
|
31
|
+
.admin {
|
|
32
|
+
max-width: 800px;
|
|
33
|
+
margin: 0 auto;
|
|
34
|
+
padding: 20px;
|
|
35
|
+
font-family: system-ui;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
38
|
+
</head>
|
|
39
|
+
<body>
|
|
40
|
+
<div class="admin">
|
|
41
|
+
<h1>{title}</h1>
|
|
42
|
+
<div forEach="items" trackBy="name"><span>{name}</span>: <strong>{count}</strong></div>
|
|
43
|
+
</div>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Create the page component
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// pages/admin/page.ts
|
|
52
|
+
import { makeJayStackComponent, phaseOutput } from '@jay-framework/fullstack-component';
|
|
53
|
+
import { MY_SERVICE, MyService } from '../../services';
|
|
54
|
+
|
|
55
|
+
export const page = makeJayStackComponent()
|
|
56
|
+
.withProps<{}>()
|
|
57
|
+
.withServices(MY_SERVICE)
|
|
58
|
+
.withFastRender(async (_props, myService: MyService) => {
|
|
59
|
+
const data = await myService.getDashboardData();
|
|
60
|
+
return phaseOutput({ title: 'Admin Dashboard', items: data.items }, {});
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The page component follows the same pattern as any `makeJayStackComponent` — supports `withSlowlyRender`, `withFastRender`, `withInteractive`, `withLoadParams`, and `withServices`.
|
|
65
|
+
|
|
66
|
+
### 3. Declare the route in plugin.yaml
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
name: my-plugin
|
|
70
|
+
routes:
|
|
71
|
+
- path: /admin/dashboard
|
|
72
|
+
jayHtml: ./pages/admin/page.jay-html
|
|
73
|
+
component: ./pages/admin/page.ts
|
|
74
|
+
description: Admin dashboard showing key metrics
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For local plugins, `jayHtml` and `component` are relative paths. For NPM packages, use `package.json` export subpaths.
|
|
78
|
+
|
|
79
|
+
### 4. For NPM packages — export the page files
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"exports": {
|
|
84
|
+
"./admin-dashboard.jay-html": "./dist/pages/admin/page.jay-html",
|
|
85
|
+
"./admin-dashboard.css": "./dist/pages/admin/page.css"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then reference the export subpath in plugin.yaml:
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
routes:
|
|
94
|
+
- path: /admin/dashboard
|
|
95
|
+
jayHtml: admin-dashboard.jay-html
|
|
96
|
+
css: admin-dashboard.css
|
|
97
|
+
component: adminDashboard
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Route Parameters
|
|
101
|
+
|
|
102
|
+
Plugin routes support the same parameter patterns as project routes:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
routes:
|
|
106
|
+
- path: /admin/products/[id]
|
|
107
|
+
jayHtml: ./pages/product-detail/page.jay-html
|
|
108
|
+
component: ./pages/product-detail/page.ts
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The page component can use `withLoadParams` for SSG parameter discovery:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
export const page = makeJayStackComponent()
|
|
115
|
+
.withProps<{}>()
|
|
116
|
+
.withServices(PRODUCTS_SERVICE)
|
|
117
|
+
.withLoadParams<{ id: string }>(async function* (productsService) {
|
|
118
|
+
const products = await productsService.listAll();
|
|
119
|
+
yield products.map((p) => ({ id: p.id }));
|
|
120
|
+
})
|
|
121
|
+
.withSlowlyRender(async (props: { id: string }, productsService) => {
|
|
122
|
+
const product = await productsService.getById(props.id);
|
|
123
|
+
return phaseOutput({ name: product.name, price: product.price }, {});
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Route Priority
|
|
128
|
+
|
|
129
|
+
Project routes always take precedence. If the project creates a page at the same path, the plugin's route is skipped:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
src/pages/admin/dashboard/page.jay-html ← project wins
|
|
133
|
+
plugin provides /admin/dashboard ← skipped
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This lets projects override any plugin page without modifying the plugin.
|
|
137
|
+
|
|
138
|
+
## Prefix Convention
|
|
139
|
+
|
|
140
|
+
Each plugin should choose a recognizable route prefix to avoid collisions:
|
|
141
|
+
|
|
142
|
+
- `/admin/...` — admin tools
|
|
143
|
+
- `/aiditor/...` — AIditor editor
|
|
144
|
+
- `/cms/...` — content management
|
|
145
|
+
|
|
146
|
+
There is no enforced convention — just pick a prefix that's unique and descriptive.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Plugin Structure
|
|
2
|
+
|
|
3
|
+
A plugin provides headless components, contracts, and actions. It can be a standalone npm package or inline within a project.
|
|
4
|
+
|
|
5
|
+
## plugin.yaml
|
|
6
|
+
|
|
7
|
+
The plugin manifest declares all contracts, actions, services, contexts, and configuration:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
name: my-plugin
|
|
11
|
+
contracts:
|
|
12
|
+
- name: product-page
|
|
13
|
+
contract: product-page.jay-contract
|
|
14
|
+
component: productPage
|
|
15
|
+
description: Complete product detail page with SSR
|
|
16
|
+
|
|
17
|
+
- name: product-search
|
|
18
|
+
contract: product-search.jay-contract
|
|
19
|
+
component: productSearch
|
|
20
|
+
description: Product listing with filters and pagination
|
|
21
|
+
|
|
22
|
+
actions:
|
|
23
|
+
- name: searchProducts
|
|
24
|
+
action: search-products.jay-action
|
|
25
|
+
- name: addToCart
|
|
26
|
+
action: add-to-cart.jay-action
|
|
27
|
+
|
|
28
|
+
services:
|
|
29
|
+
- name: my-store
|
|
30
|
+
marker: MY_STORE_SERVICE_MARKER
|
|
31
|
+
description: Provides product catalog API (query, filter, sort)
|
|
32
|
+
|
|
33
|
+
contexts:
|
|
34
|
+
- name: my-cart
|
|
35
|
+
marker: MY_CART_CONTEXT
|
|
36
|
+
description: Client-side cart state (add/remove items, totals)
|
|
37
|
+
|
|
38
|
+
routes:
|
|
39
|
+
- path: /admin/dashboard
|
|
40
|
+
jayHtml: ./pages/admin/page.jay-html
|
|
41
|
+
component: ./pages/admin/page.ts
|
|
42
|
+
description: Admin dashboard with product stats
|
|
43
|
+
|
|
44
|
+
setup:
|
|
45
|
+
handler: setup-handler
|
|
46
|
+
references: references-handler
|
|
47
|
+
configTemplate:
|
|
48
|
+
- source: templates/config.yaml
|
|
49
|
+
target: my-plugin.yaml
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Contract Entry Fields
|
|
53
|
+
|
|
54
|
+
- `name` — Contract name (used in `contract="..."` in jay-html)
|
|
55
|
+
- `contract` — Path to `.jay-contract` file (relative to plugin root)
|
|
56
|
+
- `component` — Export name of the component (e.g., `productPage`)
|
|
57
|
+
- `description` — What this component does and when to use it
|
|
58
|
+
|
|
59
|
+
### Action Entry Fields
|
|
60
|
+
|
|
61
|
+
- `name` — Action name (used with `jay-stack action <plugin>/<action>`)
|
|
62
|
+
- `action` — Path to `.jay-action` metadata file
|
|
63
|
+
|
|
64
|
+
### Service Entry Fields
|
|
65
|
+
|
|
66
|
+
- `name` — Service name (for identification in plugins-index)
|
|
67
|
+
- `marker` — Exported service marker constant (e.g., `MY_STORE_SERVICE_MARKER`)
|
|
68
|
+
- `description` — What APIs this service provides
|
|
69
|
+
- `doc` — (optional) Path to a markdown file documenting the service API
|
|
70
|
+
|
|
71
|
+
Services are server-side APIs created with `createJayService`. Other plugins and page components consume them via `.withServices(MARKER)`.
|
|
72
|
+
|
|
73
|
+
### Context Entry Fields
|
|
74
|
+
|
|
75
|
+
- `name` — Context name (for identification in plugins-index)
|
|
76
|
+
- `marker` — Exported context marker constant (e.g., `MY_CART_CONTEXT`)
|
|
77
|
+
- `description` — What reactive state this context provides
|
|
78
|
+
- `doc` — (optional) Path to a markdown file documenting the context API
|
|
79
|
+
|
|
80
|
+
Contexts are client-side reactive state. Other plugins and page components consume them via `.withContexts(MARKER)`.
|
|
81
|
+
|
|
82
|
+
### Documentation Files
|
|
83
|
+
|
|
84
|
+
When `doc` is specified, the markdown file must exist and (for NPM packages) be exported in `package.json`:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
services:
|
|
88
|
+
- name: my-store
|
|
89
|
+
marker: MY_STORE_SERVICE_MARKER
|
|
90
|
+
description: Product catalog API
|
|
91
|
+
doc: ./docs/my-store-service.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"exports": {
|
|
97
|
+
"./docs/my-store-service.md": "./docs/my-store-service.md"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Route Entry Fields
|
|
103
|
+
|
|
104
|
+
- `path` — Route path (e.g., `/admin/products`, `/dashboard/[section]`)
|
|
105
|
+
- `jayHtml` — Path to the page's jay-html template (relative to plugin root, or export subpath for NPM)
|
|
106
|
+
- `css` — (optional) Path to the page's CSS file
|
|
107
|
+
- `component` — Path to the page component (relative to plugin root, or exported member name for NPM)
|
|
108
|
+
- `description` — What this page does
|
|
109
|
+
|
|
110
|
+
Plugin routes are served by the dev server alongside project routes. If a project defines the same route path, the project's page takes precedence.
|
|
111
|
+
|
|
112
|
+
### Setup Fields
|
|
113
|
+
|
|
114
|
+
- `handler` — Setup handler for `jay-stack setup` (handles config, credentials)
|
|
115
|
+
- `references` — Reference generator for `jay-stack agent-kit` (generates discovery data)
|
|
116
|
+
- `configTemplate` — Config file templates to copy during setup
|
|
117
|
+
|
|
118
|
+
## Package Layout
|
|
119
|
+
|
|
120
|
+
### Standalone NPM Package
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
my-plugin/
|
|
124
|
+
├── plugin.yaml
|
|
125
|
+
├── package.json
|
|
126
|
+
├── lib/
|
|
127
|
+
│ ├── contracts/
|
|
128
|
+
│ │ ├── product-page.jay-contract
|
|
129
|
+
│ │ └── product-search.jay-contract
|
|
130
|
+
│ ├── actions/
|
|
131
|
+
│ │ ├── search-products.jay-action
|
|
132
|
+
│ │ └── add-to-cart.jay-action
|
|
133
|
+
│ ├── components/
|
|
134
|
+
│ │ ├── product-page.ts
|
|
135
|
+
│ │ └── product-search.ts
|
|
136
|
+
│ ├── services/
|
|
137
|
+
│ │ └── products-db.ts
|
|
138
|
+
│ └── init.ts
|
|
139
|
+
├── agent-kit/ # Optional: plugin-contributed guides
|
|
140
|
+
│ ├── designer/
|
|
141
|
+
│ │ └── my-plugin-usage.md
|
|
142
|
+
│ └── developer/
|
|
143
|
+
│ └── my-plugin-config.md
|
|
144
|
+
└── dist/
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Inline Plugin (within a project)
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
my-project/
|
|
151
|
+
├── src/
|
|
152
|
+
│ ├── pages/
|
|
153
|
+
│ │ └── ...
|
|
154
|
+
│ └── plugins/
|
|
155
|
+
│ └── my-plugin/
|
|
156
|
+
│ ├── plugin.yaml
|
|
157
|
+
│ ├── product-page.jay-contract
|
|
158
|
+
│ ├── product-page.ts
|
|
159
|
+
│ └── init.ts
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
See `examples/jay-stack/fake-shop` for a working example.
|
|
163
|
+
|
|
164
|
+
## package.json Exports
|
|
165
|
+
|
|
166
|
+
For NPM packages, declare exports so the framework can resolve the plugin:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"name": "@my-org/my-plugin",
|
|
171
|
+
"type": "module",
|
|
172
|
+
"exports": {
|
|
173
|
+
".": "./dist/index.js",
|
|
174
|
+
"./plugin.yaml": "./plugin.yaml"
|
|
175
|
+
},
|
|
176
|
+
"files": ["dist", "plugin.yaml", "lib/contracts", "lib/actions"]
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Plugin-Contributed Agent-Kit Guides
|
|
181
|
+
|
|
182
|
+
A plugin can include guides that are merged into the project's agent-kit during `jay-stack agent-kit`. Create an `agent-kit/` folder with subfolders for each role:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
my-plugin/
|
|
186
|
+
└── agent-kit/
|
|
187
|
+
├── designer/
|
|
188
|
+
│ └── my-plugin-usage.md # How to use contracts in jay-html
|
|
189
|
+
├── developer/
|
|
190
|
+
│ └── my-plugin-config.md # How to configure the plugin
|
|
191
|
+
└── plugin/
|
|
192
|
+
└── my-plugin-extending.md # How to extend the plugin
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Reference Declarations
|
|
196
|
+
|
|
197
|
+
Plugins can declare reference data generated by `jay-stack agent-kit`:
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
# In plugin.yaml
|
|
201
|
+
references:
|
|
202
|
+
- name: product-catalog
|
|
203
|
+
description: All products with IDs, slugs, names, and prices
|
|
204
|
+
file: product-catalog.json
|
|
205
|
+
- name: collection-schemas
|
|
206
|
+
description: Collection field schemas for filtering
|
|
207
|
+
file: collection-schemas.json
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The `agent-kit` command generates `references/<plugin>/INDEX.md` from these declarations.
|