@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.
Files changed (36) hide show
  1. package/agent-kit-template/{INSTRUCTIONS.md → designer/INSTRUCTIONS.md} +11 -8
  2. package/agent-kit-template/{contracts-and-plugins.md → designer/contracts-and-plugins.md} +1 -0
  3. package/agent-kit-template/{jay-html-syntax.md → designer/jay-html-components.md} +89 -158
  4. package/agent-kit-template/designer/jay-html-styling.md +97 -0
  5. package/agent-kit-template/designer/jay-html-syntax.md +44 -0
  6. package/agent-kit-template/designer/jay-html-template-syntax.md +203 -0
  7. package/agent-kit-template/developer/INSTRUCTIONS.md +34 -0
  8. package/agent-kit-template/developer/cli-commands.md +228 -0
  9. package/agent-kit-template/developer/component-data.md +109 -0
  10. package/agent-kit-template/developer/component-refs.md +117 -0
  11. package/agent-kit-template/developer/component-state.md +140 -0
  12. package/agent-kit-template/developer/configuration.md +76 -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 +161 -0
  18. package/agent-kit-template/developer/seo-guide.md +93 -0
  19. package/agent-kit-template/plugin/INSTRUCTIONS.md +40 -0
  20. package/agent-kit-template/plugin/actions-guide.md +125 -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/plugin-structure.md +194 -0
  28. package/agent-kit-template/plugin/render-results.md +112 -0
  29. package/agent-kit-template/plugin/seo-guide.md +93 -0
  30. package/agent-kit-template/plugin/services-guide.md +116 -0
  31. package/agent-kit-template/plugin/validation.md +101 -0
  32. package/dist/index.js +805 -61
  33. package/package.json +10 -10
  34. /package/agent-kit-template/{cli-commands.md → designer/cli-commands.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,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,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