@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.
Files changed (39) 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/dev-server-service.md +126 -0
  13. package/agent-kit-template/developer/page-components.md +103 -0
  14. package/agent-kit-template/developer/page-contracts.md +114 -0
  15. package/agent-kit-template/developer/project-structure.md +242 -0
  16. package/agent-kit-template/developer/render-results.md +112 -0
  17. package/agent-kit-template/developer/routing.md +175 -0
  18. package/agent-kit-template/developer/seo-guide.md +93 -0
  19. package/agent-kit-template/plugin/INSTRUCTIONS.md +43 -0
  20. package/agent-kit-template/plugin/actions-guide.md +184 -0
  21. package/agent-kit-template/plugin/component-context.md +103 -0
  22. package/agent-kit-template/plugin/component-data.md +109 -0
  23. package/agent-kit-template/plugin/component-refs.md +117 -0
  24. package/agent-kit-template/plugin/component-state.md +140 -0
  25. package/agent-kit-template/plugin/component-structure.md +174 -0
  26. package/agent-kit-template/plugin/contracts-guide.md +193 -0
  27. package/agent-kit-template/plugin/dev-server-service.md +137 -0
  28. package/agent-kit-template/plugin/plugin-routes.md +146 -0
  29. package/agent-kit-template/plugin/plugin-structure.md +210 -0
  30. package/agent-kit-template/plugin/render-results.md +112 -0
  31. package/agent-kit-template/plugin/seo-guide.md +93 -0
  32. package/agent-kit-template/plugin/services-guide.md +116 -0
  33. package/agent-kit-template/plugin/validation.md +101 -0
  34. package/dist/index.js +791 -60
  35. package/package.json +10 -10
  36. /package/agent-kit-template/{cli-commands.md → designer/cli-commands.md} +0 -0
  37. /package/agent-kit-template/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +0 -0
  38. /package/agent-kit-template/{project-structure.md → designer/project-structure.md} +0 -0
  39. /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
+ ```