@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,112 @@
|
|
|
1
|
+
# Render Results
|
|
2
|
+
|
|
3
|
+
Each rendering phase (slow, fast) returns a render result indicating success, error, or redirect.
|
|
4
|
+
|
|
5
|
+
## phaseOutput — Success
|
|
6
|
+
|
|
7
|
+
Returns ViewState data and optional carry-forward data for the next phase:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { phaseOutput } from '@jay-framework/fullstack-component';
|
|
11
|
+
|
|
12
|
+
return phaseOutput(
|
|
13
|
+
{ title: 'My Product', price: 29.99 }, // ViewState — sent to template
|
|
14
|
+
{ productId: 'abc123' }, // CarryForward — passed to next phase only
|
|
15
|
+
);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
CarryForward is available in the next phase via `props.carryForward` but is not part of the ViewState.
|
|
19
|
+
|
|
20
|
+
## Error Results
|
|
21
|
+
|
|
22
|
+
Return errors to stop rendering and show an error page:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import {
|
|
26
|
+
notFound,
|
|
27
|
+
badRequest,
|
|
28
|
+
unauthorized,
|
|
29
|
+
forbidden,
|
|
30
|
+
serverError5xx,
|
|
31
|
+
clientError4xx,
|
|
32
|
+
} from '@jay-framework/fullstack-component';
|
|
33
|
+
|
|
34
|
+
// 404
|
|
35
|
+
if (!product) return notFound('Product not found');
|
|
36
|
+
|
|
37
|
+
// 400
|
|
38
|
+
if (!input.query) return badRequest('Query is required');
|
|
39
|
+
|
|
40
|
+
// 401
|
|
41
|
+
if (!session) return unauthorized('Please log in');
|
|
42
|
+
|
|
43
|
+
// 403
|
|
44
|
+
if (!canAccess) return forbidden('Access denied');
|
|
45
|
+
|
|
46
|
+
// Custom 4xx
|
|
47
|
+
return clientError4xx(429, 'Rate limit exceeded');
|
|
48
|
+
|
|
49
|
+
// 5xx
|
|
50
|
+
return serverError5xx(500, 'Database connection failed');
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Redirects
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { redirect3xx } from '@jay-framework/fullstack-component';
|
|
57
|
+
|
|
58
|
+
return redirect3xx(301, '/new-location');
|
|
59
|
+
return redirect3xx(302, `/products/${product.slug}`);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## RenderPipeline — Composable Rendering
|
|
63
|
+
|
|
64
|
+
For complex render logic, `RenderPipeline` chains operations with automatic error propagation:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { RenderPipeline } from '@jay-framework/fullstack-component';
|
|
68
|
+
|
|
69
|
+
const Pipeline = RenderPipeline.for<SlowViewState, CarryForward>();
|
|
70
|
+
|
|
71
|
+
return Pipeline.try(() => db.getProduct(props.slug))
|
|
72
|
+
.map((product) => product ?? Pipeline.notFound('Product not found'))
|
|
73
|
+
.map(async (product) => ({
|
|
74
|
+
...product,
|
|
75
|
+
reviews: await db.getReviews(product.id),
|
|
76
|
+
}))
|
|
77
|
+
.toPhaseOutput((product) => ({
|
|
78
|
+
viewState: { title: product.name, price: product.price },
|
|
79
|
+
carryForward: { productId: product.id },
|
|
80
|
+
}));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Pipeline Factory Methods
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const P = RenderPipeline.for<VS, CF>();
|
|
87
|
+
|
|
88
|
+
P.ok(value); // Wrap a value
|
|
89
|
+
P.try(() => fetchData()); // Wrap a function (catches errors)
|
|
90
|
+
P.from(previousPhaseResult); // Continue from a prior phase result
|
|
91
|
+
P.notFound('message'); // Error pipeline
|
|
92
|
+
P.badRequest('message'); // Error pipeline
|
|
93
|
+
P.unauthorized('message'); // Error pipeline
|
|
94
|
+
P.forbidden('message'); // Error pipeline
|
|
95
|
+
P.serverError(500, 'message'); // Error pipeline
|
|
96
|
+
P.redirect(301, '/path'); // Redirect pipeline
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Pipeline Chain Methods
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
pipeline
|
|
103
|
+
.map(value => transform(value)) // Transform the value
|
|
104
|
+
.map(async value => await asyncOp(value)) // Async transform
|
|
105
|
+
.recover(error => P.ok(fallbackValue)) // Recover from errors
|
|
106
|
+
.toPhaseOutput(value => ({ // Convert to PhaseOutput
|
|
107
|
+
viewState: { ... },
|
|
108
|
+
carryForward: { ... },
|
|
109
|
+
}));
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Errors short-circuit — if any step returns an error pipeline, subsequent `.map()` calls are skipped.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# SEO Head Tags
|
|
2
|
+
|
|
3
|
+
Components inject `<title>`, `<meta>`, `<link>` tags into the HTML `<head>` during SSR by returning `headTags` from `phaseOutput()`.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
return phaseOutput(
|
|
9
|
+
{ title: product.name, price: product.price },
|
|
10
|
+
{ productId: product.id },
|
|
11
|
+
{
|
|
12
|
+
headTags: [
|
|
13
|
+
{ tag: 'title', children: `${product.name} | My Store` },
|
|
14
|
+
{ tag: 'meta', attrs: { name: 'description', content: product.description } },
|
|
15
|
+
{ tag: 'meta', attrs: { property: 'og:title', content: product.name } },
|
|
16
|
+
{ tag: 'meta', attrs: { property: 'og:description', content: product.description } },
|
|
17
|
+
{ tag: 'meta', attrs: { property: 'og:image', content: product.imageUrl } },
|
|
18
|
+
{ tag: 'link', attrs: { rel: 'canonical', href: canonicalUrl } },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## HeadTag Type
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
interface HeadTag {
|
|
28
|
+
tag: string; // 'title', 'meta', 'link', etc.
|
|
29
|
+
attrs?: Record<string, string>; // HTML attributes
|
|
30
|
+
children?: string; // Text content (for <title>, <script>, etc.)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Common SEO Tags
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Page title
|
|
38
|
+
{ tag: 'title', children: 'Product Name | Store' }
|
|
39
|
+
|
|
40
|
+
// Meta description
|
|
41
|
+
{ tag: 'meta', attrs: { name: 'description', content: 'Product description here' } }
|
|
42
|
+
|
|
43
|
+
// Open Graph
|
|
44
|
+
{ tag: 'meta', attrs: { property: 'og:title', content: 'Product Name' } }
|
|
45
|
+
{ tag: 'meta', attrs: { property: 'og:description', content: 'Description' } }
|
|
46
|
+
{ tag: 'meta', attrs: { property: 'og:image', content: 'https://...' } }
|
|
47
|
+
{ tag: 'meta', attrs: { property: 'og:type', content: 'product' } }
|
|
48
|
+
|
|
49
|
+
// Twitter Card
|
|
50
|
+
{ tag: 'meta', attrs: { name: 'twitter:card', content: 'summary_large_image' } }
|
|
51
|
+
{ tag: 'meta', attrs: { name: 'twitter:title', content: 'Product Name' } }
|
|
52
|
+
|
|
53
|
+
// Canonical URL
|
|
54
|
+
{ tag: 'link', attrs: { rel: 'canonical', href: 'https://example.com/products/slug' } }
|
|
55
|
+
|
|
56
|
+
// JSON-LD structured data
|
|
57
|
+
{ tag: 'script', attrs: { type: 'application/ld+json' }, children: JSON.stringify(structuredData) }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Mapping Generic SEO Data
|
|
61
|
+
|
|
62
|
+
If the data source provides a generic structure (array of tags with type/props/children), map it:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const headTags = seoData.tags.map((tag) => ({
|
|
66
|
+
tag: tag.type,
|
|
67
|
+
attrs: Object.fromEntries(tag.props.map((p) => [p.key, p.value])),
|
|
68
|
+
children: tag.children,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
return phaseOutput(viewState, carryForward, { headTags });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Phase Rules
|
|
75
|
+
|
|
76
|
+
- Return headTags from **slow** phase for build-time SEO data (product name, description)
|
|
77
|
+
- Return headTags from **fast** phase for per-request data (pricing, availability)
|
|
78
|
+
- Fast phase headTags **replace** slow phase entirely (no merge)
|
|
79
|
+
- No interactive phase — head tags are SSR-only
|
|
80
|
+
|
|
81
|
+
## Collision Rules
|
|
82
|
+
|
|
83
|
+
- `<title>` — singleton, last-write-wins
|
|
84
|
+
- `<meta name="X">` — keyed by `name`, last-write-wins
|
|
85
|
+
- `<meta property="X">` — keyed by `property`, last-write-wins
|
|
86
|
+
- `<link rel="canonical">` — singleton, last-write-wins
|
|
87
|
+
- Other tags — always included (no dedup)
|
|
88
|
+
- A warning is logged on collision between different components
|
|
89
|
+
|
|
90
|
+
## Restrictions
|
|
91
|
+
|
|
92
|
+
- Head tags from components inside `forEach` are ignored
|
|
93
|
+
- The framework handles HTML escaping automatically
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Services and Initialization
|
|
2
|
+
|
|
3
|
+
Services provide dependency injection for server-side resources (databases, APIs, etc.).
|
|
4
|
+
|
|
5
|
+
## createJayService
|
|
6
|
+
|
|
7
|
+
Create a typed service marker:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { createJayService } from '@jay-framework/fullstack-component';
|
|
11
|
+
|
|
12
|
+
export interface ProductsDatabase {
|
|
13
|
+
getProduct(slug: string): Promise<Product | null>;
|
|
14
|
+
search(query: string): Promise<Product[]>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const PRODUCTS_DB = createJayService<ProductsDatabase>('ProductsDatabase');
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The marker is a Symbol-based key — it provides type safety without coupling to a specific implementation.
|
|
21
|
+
|
|
22
|
+
## makeJayInit
|
|
23
|
+
|
|
24
|
+
Initialize services and client-side state during app startup:
|
|
25
|
+
|
|
26
|
+
### Server-only init
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { makeJayInit, registerService } from '@jay-framework/fullstack-component';
|
|
30
|
+
|
|
31
|
+
export const init = makeJayInit().withServer(async () => {
|
|
32
|
+
const db = await connectToDatabase();
|
|
33
|
+
registerService(PRODUCTS_DB, db);
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Server + Client init
|
|
38
|
+
|
|
39
|
+
The server can pass data to the client via the return value:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
export const init = makeJayInit()
|
|
43
|
+
.withServer(async () => {
|
|
44
|
+
registerService(PRODUCTS_DB, await connectDb());
|
|
45
|
+
return { currency: 'USD', storeId: 'store-123' };
|
|
46
|
+
})
|
|
47
|
+
.withClient((data) => {
|
|
48
|
+
// data is typed: { currency: string; storeId: string }
|
|
49
|
+
registerReactiveGlobalContext(STORE_CONFIG, () => ({
|
|
50
|
+
currency: data.currency,
|
|
51
|
+
storeId: data.storeId,
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Client-only init
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
export const init = makeJayInit().withClient(() => {
|
|
60
|
+
initAnalytics();
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Using Services
|
|
65
|
+
|
|
66
|
+
### In Components
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
makeJayStackComponent<MyContract>()
|
|
70
|
+
.withServices(PRODUCTS_DB)
|
|
71
|
+
.withSlowlyRender(async (props, db) => {
|
|
72
|
+
const product = await db.getProduct(props.slug);
|
|
73
|
+
return phaseOutput({ title: product.name }, {});
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### In Actions
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
makeJayAction('products.search')
|
|
81
|
+
.withServices(PRODUCTS_DB)
|
|
82
|
+
.withHandler(async (input, db) => {
|
|
83
|
+
return db.search(input.query);
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### In loadParams
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
.withServices(PRODUCTS_DB)
|
|
91
|
+
.withLoadParams(async function* (db) {
|
|
92
|
+
const products = await db.getAll();
|
|
93
|
+
yield products.map(p => ({ slug: p.slug }));
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Listing in plugin.yaml
|
|
98
|
+
|
|
99
|
+
If your plugin provides services for other plugins to consume, list them in `plugin.yaml`:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
services:
|
|
103
|
+
- name: products-db
|
|
104
|
+
marker: PRODUCTS_DB
|
|
105
|
+
description: Product catalog database API (query, search, get by slug)
|
|
106
|
+
doc: ./docs/products-db-service.md # optional — markdown documentation
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This makes the service discoverable in `plugins-index.yaml`. If `doc` is provided, the file must exist and be exported from the package.
|
|
110
|
+
|
|
111
|
+
## Service Lifecycle
|
|
112
|
+
|
|
113
|
+
- Services are registered during `makeJayInit().withServer()` callbacks
|
|
114
|
+
- Registration order follows plugin dependency order
|
|
115
|
+
- Services are available to all components and actions after init
|
|
116
|
+
- Services are server-side only — they are not sent to the client
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Plugin Validation
|
|
2
|
+
|
|
3
|
+
Run `jay-stack validate-plugin` to check your plugin for errors.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Validate the current plugin
|
|
9
|
+
jay-stack validate-plugin
|
|
10
|
+
|
|
11
|
+
# Validate a specific plugin path
|
|
12
|
+
jay-stack validate-plugin --path ./src/plugins/my-plugin
|
|
13
|
+
|
|
14
|
+
# Verbose output
|
|
15
|
+
jay-stack validate-plugin -v
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## What It Checks
|
|
19
|
+
|
|
20
|
+
### Contract Validation
|
|
21
|
+
|
|
22
|
+
- YAML structure is valid
|
|
23
|
+
- All required fields are present (`name`, `tags`)
|
|
24
|
+
- Tag types are valid (`data`, `variant`, `interactive`, `sub-contract`)
|
|
25
|
+
- `dataType` is valid for the tag type
|
|
26
|
+
- `trackBy` references an existing data tag with string/number type
|
|
27
|
+
- `repeated` sub-contracts have `trackBy`
|
|
28
|
+
- Phase constraints are satisfied (children >= parent for arrays)
|
|
29
|
+
- No duplicate tag names at the same level
|
|
30
|
+
- Props have valid types and unique names
|
|
31
|
+
|
|
32
|
+
### Plugin Structure
|
|
33
|
+
|
|
34
|
+
- `plugin.yaml` exists and is valid YAML
|
|
35
|
+
- Contract files referenced in `plugin.yaml` exist
|
|
36
|
+
- Component export names are valid strings (not file paths)
|
|
37
|
+
- Action metadata files (`.jay-action`) exist
|
|
38
|
+
|
|
39
|
+
### Type Generation
|
|
40
|
+
|
|
41
|
+
- Contracts compile to valid TypeScript types
|
|
42
|
+
- Generated ViewState, Refs, Props, and Params interfaces are correct
|
|
43
|
+
|
|
44
|
+
## Common Errors
|
|
45
|
+
|
|
46
|
+
### "trackBy references non-existent tag"
|
|
47
|
+
|
|
48
|
+
The `trackBy` field must reference a `data` tag within the same sub-contract:
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
# Wrong — trackBy references a tag that doesn't exist
|
|
52
|
+
- tag: items
|
|
53
|
+
type: sub-contract
|
|
54
|
+
repeated: true
|
|
55
|
+
trackBy: itemId # No tag named "itemId"
|
|
56
|
+
tags:
|
|
57
|
+
- tag: id # Should be "itemId" or trackBy should be "id"
|
|
58
|
+
type: data
|
|
59
|
+
dataType: string
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### "Child phase earlier than parent"
|
|
63
|
+
|
|
64
|
+
Array children must have phase >= parent:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
# Wrong
|
|
68
|
+
- tag: items
|
|
69
|
+
type: sub-contract
|
|
70
|
+
repeated: true
|
|
71
|
+
trackBy: id
|
|
72
|
+
phase: fast
|
|
73
|
+
tags:
|
|
74
|
+
- tag: name
|
|
75
|
+
type: data
|
|
76
|
+
phase: slow # Error: slow < fast
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### "Interactive tag cannot have explicit phase"
|
|
80
|
+
|
|
81
|
+
Interactive tags are always `fast+interactive`:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
# Wrong
|
|
85
|
+
- tag: button
|
|
86
|
+
type: interactive
|
|
87
|
+
elementType: HTMLButtonElement
|
|
88
|
+
phase: slow # Error: remove this line
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### "Component looks like a path"
|
|
92
|
+
|
|
93
|
+
The `component` field in `plugin.yaml` should be an export name, not a file path:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
# Wrong
|
|
97
|
+
component: ./lib/components/product-page.ts
|
|
98
|
+
|
|
99
|
+
# Right
|
|
100
|
+
component: productPage
|
|
101
|
+
```
|