@jay-framework/jay-stack-cli 0.15.4 → 0.15.6
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/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +1 -0
- 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/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 +161 -0
- package/agent-kit-template/developer/seo-guide.md +93 -0
- package/agent-kit-template/plugin/INSTRUCTIONS.md +40 -0
- package/agent-kit-template/plugin/actions-guide.md +125 -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/plugin-structure.md +194 -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 +805 -61
- package/package.json +10 -10
- /package/agent-kit-template/{cli-commands.md → designer/cli-commands.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,203 @@
|
|
|
1
|
+
# Jay-HTML Template Syntax
|
|
2
|
+
|
|
3
|
+
## File Structure
|
|
4
|
+
|
|
5
|
+
A `.jay-html` file is standard HTML with jay-specific extensions.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<html>
|
|
9
|
+
<head>
|
|
10
|
+
<!-- Page contract (optional — defines page-level data) -->
|
|
11
|
+
<script type="application/jay-data" contract="./page.jay-contract"></script>
|
|
12
|
+
|
|
13
|
+
<!-- Explicit route params (for static override routes) -->
|
|
14
|
+
<script type="application/jay-params">
|
|
15
|
+
slug: ceramic-flower-vase
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<!-- Headless component imports -->
|
|
19
|
+
<script type="application/jay-headless" plugin="..." contract="..." key="..."></script>
|
|
20
|
+
|
|
21
|
+
<!-- Headfull component imports -->
|
|
22
|
+
<script type="application/jay-headfull" src="..." names="..." contract="..."></script>
|
|
23
|
+
|
|
24
|
+
<!-- Styles -->
|
|
25
|
+
<style>
|
|
26
|
+
/* inline CSS */
|
|
27
|
+
</style>
|
|
28
|
+
<link rel="stylesheet" href="../../styles/theme.css" />
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<!-- Template with data bindings -->
|
|
32
|
+
<h1>{title}</h1>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Data Binding
|
|
38
|
+
|
|
39
|
+
Use `{expression}` to bind contract data:
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<h1>{productName}</h1>
|
|
43
|
+
<!-- simple -->
|
|
44
|
+
<span>{product.price}</span>
|
|
45
|
+
<!-- nested via key -->
|
|
46
|
+
<div style="color: {textColor}">{msg}</div>
|
|
47
|
+
<!-- in attributes -->
|
|
48
|
+
<a href="/products/{slug}">{name}</a>
|
|
49
|
+
<!-- interpolated in attr values -->
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Boolean Attributes
|
|
53
|
+
|
|
54
|
+
HTML boolean attributes (`disabled`, `checked`, `hidden`, `readonly`) can be bound to contract data:
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<button disabled="isSubmitting">Submit</button>
|
|
58
|
+
<!-- disabled when isSubmitting is true -->
|
|
59
|
+
<button disabled="!inStock">Add to Cart</button>
|
|
60
|
+
<!-- disabled when inStock is false -->
|
|
61
|
+
<input type="checkbox" checked="isSelected" />
|
|
62
|
+
<!-- checked when isSelected is true -->
|
|
63
|
+
<div hidden="!isVisible">Content</div>
|
|
64
|
+
<!-- hidden when isVisible is false -->
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- Set the attribute value to a **boolean tag name** — the attribute is present when true, absent when false
|
|
68
|
+
- Use `!` prefix to negate: `disabled="!enabled"` means disabled when enabled is false
|
|
69
|
+
- Without a value (`disabled` alone), the attribute is always present (standard HTML behavior)
|
|
70
|
+
|
|
71
|
+
## Conditional Rendering
|
|
72
|
+
|
|
73
|
+
Use the `if` attribute to conditionally show elements.
|
|
74
|
+
|
|
75
|
+
### Boolean
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<span if="inStock">In Stock</span> <span if="!inStock">Out of Stock</span>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Enum Variant
|
|
82
|
+
|
|
83
|
+
No quotes around the value:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<div if="type===physical">Ships to your door</div>
|
|
87
|
+
<div if="type!==physical">Not a physical product</div>
|
|
88
|
+
<div if="status===active">Active</div>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Numeric Comparisons
|
|
92
|
+
|
|
93
|
+
Compare against numbers or other fields:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<span if="count > 0">You have {count} items</span>
|
|
97
|
+
<span if="count <= 0">No items</span>
|
|
98
|
+
<button if="currentPage <= 1" disabled>Previous</button>
|
|
99
|
+
<span if="price <= budget">Affordable</span>
|
|
100
|
+
<span if="available >= required">In stock</span>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Operators: `>`, `<`, `>=`, `<=`, `==`, `!=`
|
|
104
|
+
|
|
105
|
+
### Logical AND / OR
|
|
106
|
+
|
|
107
|
+
Combine conditions with `&&` and `||`:
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<div if="inStock && hasDiscount">Great deal!</div>
|
|
111
|
+
<span if="isPromoted || hasDiscount">Has offer</span>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use parentheses for complex expressions:
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<div if="(inStock && hasDiscount) || isPromoted">Buyable</div>
|
|
118
|
+
<div if="inStock && price > 0">Purchasable</div>
|
|
119
|
+
<button if="count <= 0 || isLoading" disabled>Checkout</button>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Rules Summary
|
|
123
|
+
|
|
124
|
+
- Boolean: `if="flag"` / `if="!flag"`
|
|
125
|
+
- Enum: `if="tag===value"` / `if="tag!==value"` (no quotes around value)
|
|
126
|
+
- Numeric: `if="count > 0"`, `if="price <= budget"`
|
|
127
|
+
- Field comparison: `if="available >= required"`
|
|
128
|
+
- Logical: `if="a && b"`, `if="a || b"`, `if="(a || b) && c"`
|
|
129
|
+
- Negation: `!` prefix on booleans
|
|
130
|
+
|
|
131
|
+
## Loops (forEach / trackBy)
|
|
132
|
+
|
|
133
|
+
Iterate over repeated sub-contracts:
|
|
134
|
+
|
|
135
|
+
```html
|
|
136
|
+
<li forEach="products" trackBy="id">
|
|
137
|
+
<a href="/products/{slug}">
|
|
138
|
+
<div>{name}</div>
|
|
139
|
+
<div>{price}</div>
|
|
140
|
+
</a>
|
|
141
|
+
</li>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `forEach` — the repeated tag name from the contract
|
|
145
|
+
- `trackBy` — stable unique key for each item (must match contract's trackBy)
|
|
146
|
+
- Inside the loop, bindings resolve to the **current item's** tags
|
|
147
|
+
|
|
148
|
+
**Nested loops:**
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<div forEach="options" trackBy="_id">
|
|
152
|
+
<h3>{name}</h3>
|
|
153
|
+
<div forEach="choices" trackBy="choiceId">
|
|
154
|
+
<button ref="choiceButton">{name}</button>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Refs (Interactions)
|
|
160
|
+
|
|
161
|
+
Map elements to contract `interactive` tags using `ref`:
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<button ref="addToCart">Add to Cart</button>
|
|
165
|
+
<input value="{quantity}" ref="quantityInput" />
|
|
166
|
+
<a ref="productLink" href="/products/{slug}">{name}</a>
|
|
167
|
+
<select ref="sizeSelector">
|
|
168
|
+
...
|
|
169
|
+
</select>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Match the element type to the contract's `elementType`:
|
|
173
|
+
|
|
174
|
+
- `HTMLButtonElement` → `<button>`
|
|
175
|
+
- `HTMLInputElement` → `<input>`
|
|
176
|
+
- `HTMLAnchorElement` → `<a>`
|
|
177
|
+
- `HTMLSelectElement` → `<select>`
|
|
178
|
+
|
|
179
|
+
**Key-based headless refs** — prefix with the key:
|
|
180
|
+
|
|
181
|
+
```html
|
|
182
|
+
<button ref="rating.submitButton">Submit</button> <button ref="mt.happy">+1 Happy</button>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Refs inside forEach** — use the tag path from the contract:
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<div forEach="options" trackBy="_id">
|
|
189
|
+
<div forEach="choices" trackBy="choiceId">
|
|
190
|
+
<button ref="choiceButton">{name}</button>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Page-Level Contract
|
|
196
|
+
|
|
197
|
+
A page can define its own data contract:
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<script type="application/jay-data" contract="./page.jay-contract"></script>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Tags from the page contract are bound directly (no key prefix).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Jay Stack Developer — Agent Kit
|
|
2
|
+
|
|
3
|
+
This folder contains guides for building jay-stack projects: project configuration, routing, page components, and wiring plugins together.
|
|
4
|
+
|
|
5
|
+
## What Does the Developer Role Do?
|
|
6
|
+
|
|
7
|
+
The developer sets up the project, configures plugins, creates page-level components (`page.ts`), defines page contracts (`page.jay-contract`), and wires everything together. This is distinct from the designer role (creates jay-html UI) and the plugin role (creates reusable headless components).
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
1. **Set up the project** — `jay-stack setup` to configure plugins
|
|
12
|
+
2. **Define routes** — create page directories under `src/pages/`
|
|
13
|
+
3. **Create page contracts** — `page.jay-contract` for page-level data
|
|
14
|
+
4. **Create page components** — `page.ts` with `makeJayStackComponent`
|
|
15
|
+
5. **Configure services** — `src/init.ts` for project-level services
|
|
16
|
+
6. **Validate** — `jay-stack validate`
|
|
17
|
+
7. **Test** — `jay-stack dev --test-mode`
|
|
18
|
+
|
|
19
|
+
## Guides
|
|
20
|
+
|
|
21
|
+
| File | Topic |
|
|
22
|
+
| -------------------------------------------- | ---------------------------------------------------------- |
|
|
23
|
+
| [project-structure.md](project-structure.md) | Directory layout, configuration files |
|
|
24
|
+
| [routing.md](routing.md) | Directory-based routing, dynamic routes |
|
|
25
|
+
| [configuration.md](configuration.md) | .jay file, plugin config, init.ts |
|
|
26
|
+
| [page-contracts.md](page-contracts.md) | Page-level contracts (page.jay-contract) |
|
|
27
|
+
| [page-components.md](page-components.md) | page.ts: makeJayStackComponent for pages |
|
|
28
|
+
| [component-state.md](component-state.md) | createSignal, createMemo, createEffect, createDerivedArray |
|
|
29
|
+
| [component-refs.md](component-refs.md) | Refs, collection refs, element types |
|
|
30
|
+
| [component-data.md](component-data.md) | Immutable data, JSON Patch, patching |
|
|
31
|
+
| [render-results.md](render-results.md) | phaseOutput, RenderPipeline, errors, redirects |
|
|
32
|
+
| [seo-guide.md](seo-guide.md) | SEO head tags: title, meta, OG, canonical via phaseOutput |
|
|
33
|
+
| [cli-commands.md](cli-commands.md) | CLI commands: setup, validate, dev, agent-kit |
|
|
34
|
+
| `../references/<plugin>/` | Plugin reference data |
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# CLI Commands Reference
|
|
2
|
+
|
|
3
|
+
## jay-stack setup
|
|
4
|
+
|
|
5
|
+
Run plugin setup. Plugins can create configuration files, generate reference data, and validate their prerequisites.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run setup for all installed plugins
|
|
9
|
+
jay-stack setup
|
|
10
|
+
|
|
11
|
+
# Run setup for a specific plugin
|
|
12
|
+
jay-stack setup wix-stores
|
|
13
|
+
|
|
14
|
+
# Re-run setup (e.g., after config change)
|
|
15
|
+
jay-stack setup wix-data --force
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Plugins declare their setup handler in `plugin.yaml`. Setup does two things:
|
|
19
|
+
|
|
20
|
+
1. **Config templates**: Creates `config/<plugin>.yaml` with placeholder credentials if missing
|
|
21
|
+
2. **Credential validation**: Attempts to initialize services, reports success or failure
|
|
22
|
+
|
|
23
|
+
Reference data (product catalogs, collection schemas) is generated by `jay-stack agent-kit`, not by setup.
|
|
24
|
+
|
|
25
|
+
Run this after installing new plugins, before `jay-stack agent-kit`.
|
|
26
|
+
|
|
27
|
+
## jay-stack agent-kit
|
|
28
|
+
|
|
29
|
+
Materialize contracts, generate discovery indexes, and produce plugin reference data. Run this after setup.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Default: writes to agent-kit/
|
|
33
|
+
jay-stack agent-kit
|
|
34
|
+
|
|
35
|
+
# Custom output directory for contracts
|
|
36
|
+
jay-stack agent-kit --output my-output/
|
|
37
|
+
|
|
38
|
+
# List contracts without writing files
|
|
39
|
+
jay-stack agent-kit --list
|
|
40
|
+
|
|
41
|
+
# Filter to specific plugin
|
|
42
|
+
jay-stack agent-kit --plugin wix-stores
|
|
43
|
+
|
|
44
|
+
# Force re-materialization
|
|
45
|
+
jay-stack agent-kit --force
|
|
46
|
+
|
|
47
|
+
# Skip reference data generation
|
|
48
|
+
jay-stack agent-kit --no-references
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Outputs:
|
|
52
|
+
|
|
53
|
+
- `plugins-index.yaml`
|
|
54
|
+
- `materialized-contracts/<plugin>/*.jay-contract` (dynamic contracts)
|
|
55
|
+
- `references/<plugin>/` — plugin reference data (product catalogs, collection schemas, etc.)
|
|
56
|
+
- Documentation files (INSTRUCTIONS.md and reference docs)
|
|
57
|
+
|
|
58
|
+
## jay-stack validate
|
|
59
|
+
|
|
60
|
+
Validate all `.jay-html` and `.jay-contract` files.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Validate entire project
|
|
64
|
+
jay-stack validate
|
|
65
|
+
|
|
66
|
+
# Validate a specific path
|
|
67
|
+
jay-stack validate src/pages/products/
|
|
68
|
+
|
|
69
|
+
# Verbose (per-file status)
|
|
70
|
+
jay-stack validate -v
|
|
71
|
+
|
|
72
|
+
# JSON output
|
|
73
|
+
jay-stack validate --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Example output:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
✅ Jay Stack validation successful!
|
|
80
|
+
Scanned 5 .jay-html files, 3 .jay-contract files
|
|
81
|
+
No errors found.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
On failure:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
❌ Jay Stack validation failed
|
|
88
|
+
|
|
89
|
+
Errors:
|
|
90
|
+
❌ src/pages/products/page.jay-html
|
|
91
|
+
Unknown ref "nonExistentRef" - not found in contract
|
|
92
|
+
|
|
93
|
+
1 error(s) found, 7 file(s) valid.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Always run validate after creating or editing jay-html and contract files.
|
|
97
|
+
|
|
98
|
+
## jay-stack params
|
|
99
|
+
|
|
100
|
+
Discover load param values for SSG route generation.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Discover slug values for product pages
|
|
104
|
+
jay-stack params wix-stores/product-page
|
|
105
|
+
|
|
106
|
+
# YAML output
|
|
107
|
+
jay-stack params wix-stores/product-page --yaml
|
|
108
|
+
|
|
109
|
+
# Verbose
|
|
110
|
+
jay-stack params wix-stores/product-page -v
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Format: `<plugin-name>/<contract-name>`
|
|
114
|
+
|
|
115
|
+
Example output:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
[
|
|
119
|
+
{ "slug": "ceramic-flower-vase" },
|
|
120
|
+
{ "slug": "blue-running-shoes" },
|
|
121
|
+
{ "slug": "organic-cotton-tshirt" }
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
✅ Found 3 param combination(s)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use this to discover what param values exist for dynamic routes like `[slug]`. Only works on contracts whose component has `loadParams`.
|
|
128
|
+
|
|
129
|
+
## jay-stack action
|
|
130
|
+
|
|
131
|
+
Run a plugin action from the CLI. Use to discover data for populating pages.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Run with default input
|
|
135
|
+
jay-stack action wix-stores/searchProducts
|
|
136
|
+
|
|
137
|
+
# Run with input
|
|
138
|
+
jay-stack action wix-stores/searchProducts --input '{"query": "shoes", "limit": 5}'
|
|
139
|
+
|
|
140
|
+
# YAML output
|
|
141
|
+
jay-stack action wix-stores/getCategories --yaml
|
|
142
|
+
|
|
143
|
+
# Verbose
|
|
144
|
+
jay-stack action wix-stores/getProductBySlug --input '{"slug": "blue-shirt"}' -v
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Format: `<plugin-name>/<action-name>`
|
|
148
|
+
|
|
149
|
+
Action names are listed in `plugins-index.yaml` under each plugin's `actions:` array. Each action entry includes a `description` and a `path` to the `.jay-action` file. Read the `.jay-action` file to see the full input/output schemas before calling an action.
|
|
150
|
+
|
|
151
|
+
Example output:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"items": [
|
|
156
|
+
{ "_id": "prod-1", "name": "Blue Shirt", "slug": "blue-shirt", "price": 29.99 },
|
|
157
|
+
{ "_id": "prod-2", "name": "Red Hat", "slug": "red-hat", "price": 19.99 }
|
|
158
|
+
],
|
|
159
|
+
"totalCount": 2
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If not found, lists available actions:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
❌ Action "badName" not found.
|
|
167
|
+
Available actions: searchProducts, getProductBySlug, getCategories
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## jay-stack dev
|
|
171
|
+
|
|
172
|
+
Start the development server.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Normal dev mode
|
|
176
|
+
jay-stack dev
|
|
177
|
+
|
|
178
|
+
# Test mode (enables health/shutdown endpoints)
|
|
179
|
+
jay-stack dev --test-mode
|
|
180
|
+
|
|
181
|
+
# Auto-timeout (implies test mode)
|
|
182
|
+
jay-stack dev --timeout 60
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Test mode endpoints
|
|
186
|
+
|
|
187
|
+
| Endpoint | Method | Response |
|
|
188
|
+
| ---------------- | ------ | --------------------------------------------------------------- |
|
|
189
|
+
| `/_jay/health` | GET | `{"status":"ready","port":3300,"editorPort":3301,"uptime":5.2}` |
|
|
190
|
+
| `/_jay/shutdown` | POST | `{"status":"shutting_down"}` |
|
|
191
|
+
|
|
192
|
+
### Wait for server ready
|
|
193
|
+
|
|
194
|
+
Poll the health endpoint:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Bash
|
|
198
|
+
for i in {1..30}; do
|
|
199
|
+
curl -s http://localhost:3300/_jay/health | grep -q "ready" && break
|
|
200
|
+
sleep 1
|
|
201
|
+
done
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// TypeScript
|
|
206
|
+
async function waitForServer(timeout = 30000): Promise<string> {
|
|
207
|
+
const start = Date.now();
|
|
208
|
+
while (Date.now() - start < timeout) {
|
|
209
|
+
try {
|
|
210
|
+
const res = await fetch('http://localhost:3300/_jay/health');
|
|
211
|
+
if (res.ok) {
|
|
212
|
+
const { port } = await res.json();
|
|
213
|
+
return `http://localhost:${port}`;
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
/* not ready */
|
|
217
|
+
}
|
|
218
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
219
|
+
}
|
|
220
|
+
throw new Error('Server not ready');
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Shutdown
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
curl -X POST http://localhost:3300/_jay/shutdown
|
|
228
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Immutable Data and Patching
|
|
2
|
+
|
|
3
|
+
In Jay, ViewState data is immutable. Never mutate objects directly — use signals and JSON Patch for updates.
|
|
4
|
+
|
|
5
|
+
## Immutable Data Model
|
|
6
|
+
|
|
7
|
+
ViewState objects passed to the render function are immutable snapshots. The framework compares old and new snapshots to determine what changed in the DOM.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// WRONG — never mutate directly
|
|
11
|
+
viewState.items.push(newItem);
|
|
12
|
+
viewState.count = 5;
|
|
13
|
+
|
|
14
|
+
// RIGHT — return new values from signals
|
|
15
|
+
const [count, setCount] = createSignal(0);
|
|
16
|
+
setCount(5);
|
|
17
|
+
|
|
18
|
+
return { render: () => ({ count: count() }) };
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## JSON Patch for Complex Updates
|
|
22
|
+
|
|
23
|
+
For objects with many fields, use `createPatchableSignal` with JSON Patch operations instead of replacing the entire object:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { createPatchableSignal } from '@jay-framework/component';
|
|
27
|
+
import { REPLACE, ADD, REMOVE } from '@jay-framework/json-patch';
|
|
28
|
+
|
|
29
|
+
const [state, setState, patchState] = createPatchableSignal({
|
|
30
|
+
title: 'Product',
|
|
31
|
+
price: 29.99,
|
|
32
|
+
tags: ['sale', 'featured'],
|
|
33
|
+
details: { color: 'red', size: 'M' },
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Patch Operations
|
|
38
|
+
|
|
39
|
+
**REPLACE** — Update an existing value:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
patchState({ op: REPLACE, path: ['price'], value: 19.99 });
|
|
43
|
+
patchState({ op: REPLACE, path: ['details', 'color'], value: 'blue' });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**ADD** — Add a new field or array item:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
patchState({ op: ADD, path: ['tags', 1], value: 'new-tag' }); // Insert at index 1
|
|
50
|
+
patchState({ op: ADD, path: ['details', 'weight'], value: '500g' });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**REMOVE** — Remove a field or array item:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
patchState({ op: REMOVE, path: ['tags', 0] }); // Remove first tag
|
|
57
|
+
patchState({ op: REMOVE, path: ['details', 'size'] });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**MOVE** — Move a value from one path to another:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { MOVE } from '@jay-framework/json-patch';
|
|
64
|
+
|
|
65
|
+
patchState({ op: MOVE, from: ['tags', 0], path: ['tags', 2] }); // Reorder array item
|
|
66
|
+
patchState({ op: MOVE, from: ['details', 'color'], path: ['primaryColor'] }); // Relocate field
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Multiple Patches
|
|
70
|
+
|
|
71
|
+
Apply multiple patches at once — the framework batches them into a single update:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
patchState(
|
|
75
|
+
{ op: REPLACE, path: ['price'], value: 19.99 },
|
|
76
|
+
{ op: REPLACE, path: ['details', 'color'], value: 'blue' },
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### When to Use Patch vs Set
|
|
81
|
+
|
|
82
|
+
- **Simple values** (number, string, boolean): use `setSignal(newValue)`
|
|
83
|
+
- **Objects with few fields**: use `setSignal({ ...old, field: newValue })`
|
|
84
|
+
- **Complex nested objects**: use `patchState` for surgical updates
|
|
85
|
+
- **Arrays with identity tracking**: use `patchState` with ADD/REMOVE
|
|
86
|
+
|
|
87
|
+
## createDerivedArray (Map Hook)
|
|
88
|
+
|
|
89
|
+
Transform an array reactively with smart caching. Only remaps items that actually changed:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { createDerivedArray } from '@jay-framework/component';
|
|
93
|
+
|
|
94
|
+
const displayProducts = createDerivedArray(
|
|
95
|
+
() => products(),
|
|
96
|
+
(item, index, length) => ({
|
|
97
|
+
label: `${item().name} - ${formatPrice(item().price)}`,
|
|
98
|
+
position: `${index() + 1} of ${length()}`,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Key behavior:
|
|
104
|
+
|
|
105
|
+
- If an item's object identity hasn't changed, the cached mapped result is reused
|
|
106
|
+
- `index()` and `length()` are tracked — if you don't call them, changes to index/length won't trigger a remap
|
|
107
|
+
- Returns a `Getter<U[]>` — read with `displayProducts()`
|
|
108
|
+
|
|
109
|
+
See [component-state.md](component-state.md) for the full hook reference.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Component Refs
|
|
2
|
+
|
|
3
|
+
Refs provide access to DOM elements declared as `interactive` in the contract. They are the second parameter of the interactive constructor.
|
|
4
|
+
|
|
5
|
+
## Single Refs
|
|
6
|
+
|
|
7
|
+
A ref maps to one DOM element:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
# Contract
|
|
11
|
+
- tag: addToCart
|
|
12
|
+
type: interactive
|
|
13
|
+
elementType: HTMLButtonElement
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Component
|
|
18
|
+
.withInteractive(function MyComp(props, refs) {
|
|
19
|
+
refs.addToCart.onClick(() => {
|
|
20
|
+
// handle click
|
|
21
|
+
});
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Ref Methods
|
|
26
|
+
|
|
27
|
+
Refs provide type-safe access to the DOM element:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
refs.submitButton.onClick(() => {
|
|
31
|
+
/* ... */
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// exec$ gives direct access to the element and current ViewState
|
|
35
|
+
refs.submitButton.exec$((element, viewState) => {
|
|
36
|
+
element.disabled = viewState.isSubmitting;
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Collection Refs
|
|
41
|
+
|
|
42
|
+
When an interactive tag is inside a `repeated` sub-contract, the ref becomes a collection. In jay-html, collection refs use the `$` suffix:
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<div forEach="items" trackBy="id">
|
|
46
|
+
<button ref="itemButton$">Click</button>
|
|
47
|
+
</div>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The `$` is stripped from the name in the contract and component code:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
# Contract
|
|
54
|
+
- tag: items
|
|
55
|
+
type: sub-contract
|
|
56
|
+
repeated: true
|
|
57
|
+
trackBy: id
|
|
58
|
+
tags:
|
|
59
|
+
- tag: id
|
|
60
|
+
type: data
|
|
61
|
+
dataType: string
|
|
62
|
+
- tag: itemButton
|
|
63
|
+
type: interactive
|
|
64
|
+
elementType: HTMLButtonElement
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Collection Ref Methods
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Map over all items in the collection
|
|
71
|
+
const labels = refs.itemButton.map((proxy, viewState, coordinate) => {
|
|
72
|
+
return viewState.name;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Find a specific item
|
|
76
|
+
const target = refs.itemButton.find((viewState) => viewState.id === 'target-id');
|
|
77
|
+
|
|
78
|
+
// Find by coordinate
|
|
79
|
+
const target = refs.itemButton.find((viewState, coordinate) =>
|
|
80
|
+
sameCoordinate(coordinate, ['item-2', 'itemButton']),
|
|
81
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Element Types
|
|
85
|
+
|
|
86
|
+
Common element types for interactive tags:
|
|
87
|
+
|
|
88
|
+
| Element Type | Use For |
|
|
89
|
+
| --------------------- | --------------------- |
|
|
90
|
+
| `HTMLButtonElement` | Buttons, clickable |
|
|
91
|
+
| `HTMLAnchorElement` | Links |
|
|
92
|
+
| `HTMLInputElement` | Text inputs, checkbox |
|
|
93
|
+
| `HTMLSelectElement` | Dropdowns |
|
|
94
|
+
| `HTMLTextAreaElement` | Multi-line text |
|
|
95
|
+
| `HTMLFormElement` | Forms |
|
|
96
|
+
| `HTMLDivElement` | Generic containers |
|
|
97
|
+
|
|
98
|
+
Multiple element types (when the same ref may bind to different elements):
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
- tag: trigger
|
|
102
|
+
type: interactive
|
|
103
|
+
elementType: HTMLButtonElement | HTMLAnchorElement
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Data + Interactive
|
|
107
|
+
|
|
108
|
+
A tag can be both data and interactive:
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
- tag: quantityInput
|
|
112
|
+
type: [data, interactive]
|
|
113
|
+
dataType: number
|
|
114
|
+
elementType: HTMLInputElement
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This generates both a ViewState field and a ref.
|