@jay-framework/jay-stack-cli 0.15.5 → 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.
Files changed (36) hide show
  1. package/agent-kit-template/{INSTRUCTIONS.md → designer/INSTRUCTIONS.md} +11 -8
  2. package/agent-kit-template/{jay-html-syntax.md → designer/jay-html-components.md} +89 -158
  3. package/agent-kit-template/designer/jay-html-styling.md +97 -0
  4. package/agent-kit-template/designer/jay-html-syntax.md +44 -0
  5. package/agent-kit-template/designer/jay-html-template-syntax.md +203 -0
  6. package/agent-kit-template/developer/INSTRUCTIONS.md +34 -0
  7. package/agent-kit-template/developer/cli-commands.md +228 -0
  8. package/agent-kit-template/developer/component-data.md +109 -0
  9. package/agent-kit-template/developer/component-refs.md +117 -0
  10. package/agent-kit-template/developer/component-state.md +140 -0
  11. package/agent-kit-template/developer/configuration.md +76 -0
  12. package/agent-kit-template/developer/page-components.md +103 -0
  13. package/agent-kit-template/developer/page-contracts.md +114 -0
  14. package/agent-kit-template/developer/project-structure.md +242 -0
  15. package/agent-kit-template/developer/render-results.md +112 -0
  16. package/agent-kit-template/developer/routing.md +161 -0
  17. package/agent-kit-template/developer/seo-guide.md +93 -0
  18. package/agent-kit-template/plugin/INSTRUCTIONS.md +40 -0
  19. package/agent-kit-template/plugin/actions-guide.md +125 -0
  20. package/agent-kit-template/plugin/component-context.md +103 -0
  21. package/agent-kit-template/plugin/component-data.md +109 -0
  22. package/agent-kit-template/plugin/component-refs.md +117 -0
  23. package/agent-kit-template/plugin/component-state.md +140 -0
  24. package/agent-kit-template/plugin/component-structure.md +174 -0
  25. package/agent-kit-template/plugin/contracts-guide.md +193 -0
  26. package/agent-kit-template/plugin/plugin-structure.md +194 -0
  27. package/agent-kit-template/plugin/render-results.md +112 -0
  28. package/agent-kit-template/plugin/seo-guide.md +93 -0
  29. package/agent-kit-template/plugin/services-guide.md +116 -0
  30. package/agent-kit-template/plugin/validation.md +101 -0
  31. package/dist/index.js +678 -60
  32. package/package.json +10 -10
  33. /package/agent-kit-template/{cli-commands.md → designer/cli-commands.md} +0 -0
  34. /package/agent-kit-template/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +0 -0
  35. /package/agent-kit-template/{project-structure.md → designer/project-structure.md} +0 -0
  36. /package/agent-kit-template/{routing.md → designer/routing.md} +0 -0
@@ -0,0 +1,194 @@
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
+ setup:
39
+ handler: setup-handler
40
+ references: references-handler
41
+ configTemplate:
42
+ - source: templates/config.yaml
43
+ target: my-plugin.yaml
44
+ ```
45
+
46
+ ### Contract Entry Fields
47
+
48
+ - `name` — Contract name (used in `contract="..."` in jay-html)
49
+ - `contract` — Path to `.jay-contract` file (relative to plugin root)
50
+ - `component` — Export name of the component (e.g., `productPage`)
51
+ - `description` — What this component does and when to use it
52
+
53
+ ### Action Entry Fields
54
+
55
+ - `name` — Action name (used with `jay-stack action <plugin>/<action>`)
56
+ - `action` — Path to `.jay-action` metadata file
57
+
58
+ ### Service Entry Fields
59
+
60
+ - `name` — Service name (for identification in plugins-index)
61
+ - `marker` — Exported service marker constant (e.g., `MY_STORE_SERVICE_MARKER`)
62
+ - `description` — What APIs this service provides
63
+ - `doc` — (optional) Path to a markdown file documenting the service API
64
+
65
+ Services are server-side APIs created with `createJayService`. Other plugins and page components consume them via `.withServices(MARKER)`.
66
+
67
+ ### Context Entry Fields
68
+
69
+ - `name` — Context name (for identification in plugins-index)
70
+ - `marker` — Exported context marker constant (e.g., `MY_CART_CONTEXT`)
71
+ - `description` — What reactive state this context provides
72
+ - `doc` — (optional) Path to a markdown file documenting the context API
73
+
74
+ Contexts are client-side reactive state. Other plugins and page components consume them via `.withContexts(MARKER)`.
75
+
76
+ ### Documentation Files
77
+
78
+ When `doc` is specified, the markdown file must exist and (for NPM packages) be exported in `package.json`:
79
+
80
+ ```yaml
81
+ services:
82
+ - name: my-store
83
+ marker: MY_STORE_SERVICE_MARKER
84
+ description: Product catalog API
85
+ doc: ./docs/my-store-service.md
86
+ ```
87
+
88
+ ```json
89
+ {
90
+ "exports": {
91
+ "./docs/my-store-service.md": "./docs/my-store-service.md"
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### Setup Fields
97
+
98
+ - `handler` — Setup handler for `jay-stack setup` (handles config, credentials)
99
+ - `references` — Reference generator for `jay-stack agent-kit` (generates discovery data)
100
+ - `configTemplate` — Config file templates to copy during setup
101
+
102
+ ## Package Layout
103
+
104
+ ### Standalone NPM Package
105
+
106
+ ```
107
+ my-plugin/
108
+ ├── plugin.yaml
109
+ ├── package.json
110
+ ├── lib/
111
+ │ ├── contracts/
112
+ │ │ ├── product-page.jay-contract
113
+ │ │ └── product-search.jay-contract
114
+ │ ├── actions/
115
+ │ │ ├── search-products.jay-action
116
+ │ │ └── add-to-cart.jay-action
117
+ │ ├── components/
118
+ │ │ ├── product-page.ts
119
+ │ │ └── product-search.ts
120
+ │ ├── services/
121
+ │ │ └── products-db.ts
122
+ │ └── init.ts
123
+ ├── agent-kit/ # Optional: plugin-contributed guides
124
+ │ ├── designer/
125
+ │ │ └── my-plugin-usage.md
126
+ │ └── developer/
127
+ │ └── my-plugin-config.md
128
+ └── dist/
129
+ ```
130
+
131
+ ### Inline Plugin (within a project)
132
+
133
+ ```
134
+ my-project/
135
+ ├── src/
136
+ │ ├── pages/
137
+ │ │ └── ...
138
+ │ └── plugins/
139
+ │ └── my-plugin/
140
+ │ ├── plugin.yaml
141
+ │ ├── product-page.jay-contract
142
+ │ ├── product-page.ts
143
+ │ └── init.ts
144
+ ```
145
+
146
+ See `examples/jay-stack/fake-shop` for a working example.
147
+
148
+ ## package.json Exports
149
+
150
+ For NPM packages, declare exports so the framework can resolve the plugin:
151
+
152
+ ```json
153
+ {
154
+ "name": "@my-org/my-plugin",
155
+ "type": "module",
156
+ "exports": {
157
+ ".": "./dist/index.js",
158
+ "./plugin.yaml": "./plugin.yaml"
159
+ },
160
+ "files": ["dist", "plugin.yaml", "lib/contracts", "lib/actions"]
161
+ }
162
+ ```
163
+
164
+ ## Plugin-Contributed Agent-Kit Guides
165
+
166
+ 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:
167
+
168
+ ```
169
+ my-plugin/
170
+ └── agent-kit/
171
+ ├── designer/
172
+ │ └── my-plugin-usage.md # How to use contracts in jay-html
173
+ ├── developer/
174
+ │ └── my-plugin-config.md # How to configure the plugin
175
+ └── plugin/
176
+ └── my-plugin-extending.md # How to extend the plugin
177
+ ```
178
+
179
+ ## Reference Declarations
180
+
181
+ Plugins can declare reference data generated by `jay-stack agent-kit`:
182
+
183
+ ```yaml
184
+ # In plugin.yaml
185
+ references:
186
+ - name: product-catalog
187
+ description: All products with IDs, slugs, names, and prices
188
+ file: product-catalog.json
189
+ - name: collection-schemas
190
+ description: Collection field schemas for filtering
191
+ file: collection-schemas.json
192
+ ```
193
+
194
+ The `agent-kit` command generates `references/<plugin>/INDEX.md` from these declarations.
@@ -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
+ ```