@jay-framework/compiler-jay-html 0.8.0 → 0.9.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/dist/index.d.cts +19 -1
- package/dist/index.js +741 -140
- package/docs/contract-file-format.md +214 -0
- package/docs/jay-html-docs.md +61 -0
- package/package.json +8 -8
- package/test/fixtures/recursive-html/README.md +10 -1
|
@@ -178,6 +178,220 @@ In this example:
|
|
|
178
178
|
4. **Document dependencies** - Make it clear which contracts are required
|
|
179
179
|
5. **Version contracts** - Consider versioning for breaking changes
|
|
180
180
|
|
|
181
|
+
## Recursive Links
|
|
182
|
+
|
|
183
|
+
Recursive links allow you to create self-referential data structures within a contract, such as trees, nested menus, or hierarchical data. They use the special `$/` syntax to reference parts of the same contract.
|
|
184
|
+
|
|
185
|
+
### Basic Recursive Link
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
name: folder-tree
|
|
189
|
+
tags:
|
|
190
|
+
- tag: name
|
|
191
|
+
type: data
|
|
192
|
+
dataType: string
|
|
193
|
+
- tag: children
|
|
194
|
+
type: sub-contract
|
|
195
|
+
repeated: true
|
|
196
|
+
link: $/
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The `link: $/` references the root of the current contract, creating a recursive structure.
|
|
200
|
+
|
|
201
|
+
**Generated TypeScript:**
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
export interface FolderTreeViewState {
|
|
205
|
+
name: string;
|
|
206
|
+
children: Array<FolderTreeViewState>;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Nested Path Recursive Links
|
|
211
|
+
|
|
212
|
+
You can reference nested properties within the contract using path syntax:
|
|
213
|
+
|
|
214
|
+
```yaml
|
|
215
|
+
name: document
|
|
216
|
+
tags:
|
|
217
|
+
- tag: title
|
|
218
|
+
type: data
|
|
219
|
+
dataType: string
|
|
220
|
+
- tag: metadata
|
|
221
|
+
type: sub-contract
|
|
222
|
+
tags:
|
|
223
|
+
- tag: category
|
|
224
|
+
type: data
|
|
225
|
+
dataType: string
|
|
226
|
+
- tag: tags
|
|
227
|
+
type: data
|
|
228
|
+
dataType: string
|
|
229
|
+
- tag: relatedDocuments
|
|
230
|
+
type: sub-contract
|
|
231
|
+
repeated: true
|
|
232
|
+
link: $/metadata
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The `link: $/metadata` references the `metadata` sub-contract within the same contract.
|
|
236
|
+
|
|
237
|
+
**Generated TypeScript:**
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
export interface MetadataOfDocumentViewState {
|
|
241
|
+
category: string;
|
|
242
|
+
tags: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface DocumentViewState {
|
|
246
|
+
title: string;
|
|
247
|
+
metadata: MetadataOfDocumentViewState;
|
|
248
|
+
relatedDocuments: Array<MetadataOfDocumentViewState>;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Array Item Unwrapping with `[]`
|
|
253
|
+
|
|
254
|
+
When a recursive link points to an array property, you can use the `[]` suffix to unwrap and link to the item type instead of the full array:
|
|
255
|
+
|
|
256
|
+
```yaml
|
|
257
|
+
name: product-list
|
|
258
|
+
tags:
|
|
259
|
+
- tag: title
|
|
260
|
+
type: data
|
|
261
|
+
dataType: string
|
|
262
|
+
- tag: products
|
|
263
|
+
type: sub-contract
|
|
264
|
+
repeated: true
|
|
265
|
+
tags:
|
|
266
|
+
- tag: id
|
|
267
|
+
type: data
|
|
268
|
+
dataType: string
|
|
269
|
+
- tag: name
|
|
270
|
+
type: data
|
|
271
|
+
dataType: string
|
|
272
|
+
- tag: price
|
|
273
|
+
type: data
|
|
274
|
+
dataType: number
|
|
275
|
+
- tag: featuredProduct
|
|
276
|
+
type: sub-contract
|
|
277
|
+
link: $/products[]
|
|
278
|
+
description: Links to a single product item (not the array)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Without `[]` - Links to the Array:**
|
|
282
|
+
|
|
283
|
+
- `link: $/products` → `Array<ProductOfProductListViewState>`
|
|
284
|
+
|
|
285
|
+
**With `[]` - Links to the Array Item:**
|
|
286
|
+
|
|
287
|
+
- `link: $/products[]` → `ProductOfProductListViewState | null`
|
|
288
|
+
|
|
289
|
+
**Generated TypeScript:**
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
export interface ProductOfProductListViewState {
|
|
293
|
+
id: string;
|
|
294
|
+
name: string;
|
|
295
|
+
price: number;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface ProductListViewState {
|
|
299
|
+
title: string;
|
|
300
|
+
products: Array<ProductOfProductListViewState>;
|
|
301
|
+
featuredProduct: ProductOfProductListViewState | null;
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Recursive Link Syntax Reference
|
|
306
|
+
|
|
307
|
+
| Syntax | Resolves To | Generated Type |
|
|
308
|
+
| ------------------- | ---------------------- | --------------------------- |
|
|
309
|
+
| `$/` | Root contract | `ContractViewState \| null` |
|
|
310
|
+
| `$/propertyName` | Nested property | `PropertyType \| null` |
|
|
311
|
+
| `$/arrayProperty` | Array property | `Array<ItemType>` |
|
|
312
|
+
| `$/arrayProperty[]` | Array item type | `ItemType \| null` |
|
|
313
|
+
| `$/nested/path` | Deeply nested property | `PropertyType \| null` |
|
|
314
|
+
|
|
315
|
+
### Type Nullability Rules
|
|
316
|
+
|
|
317
|
+
- **Non-array types** get `| null` since they may not exist
|
|
318
|
+
- **Array types** don't get `| null` since an empty array (`[]`) can be used
|
|
319
|
+
- **Repeated tags** with `repeated: true` always generate arrays
|
|
320
|
+
|
|
321
|
+
### Common Use Cases
|
|
322
|
+
|
|
323
|
+
**1. Tree Structures (Direct Recursion)**
|
|
324
|
+
|
|
325
|
+
```yaml
|
|
326
|
+
name: tree-node
|
|
327
|
+
tags:
|
|
328
|
+
- tag: value
|
|
329
|
+
type: data
|
|
330
|
+
dataType: string
|
|
331
|
+
- tag: children
|
|
332
|
+
type: sub-contract
|
|
333
|
+
repeated: true
|
|
334
|
+
link: $/
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**2. Linked Lists (Single Recursion)**
|
|
338
|
+
|
|
339
|
+
```yaml
|
|
340
|
+
name: linked-list
|
|
341
|
+
tags:
|
|
342
|
+
- tag: value
|
|
343
|
+
type: data
|
|
344
|
+
dataType: string
|
|
345
|
+
- tag: next
|
|
346
|
+
type: sub-contract
|
|
347
|
+
link: $/
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**3. Nested Menus (Indirect Recursion)**
|
|
351
|
+
|
|
352
|
+
```yaml
|
|
353
|
+
name: menu
|
|
354
|
+
tags:
|
|
355
|
+
- tag: label
|
|
356
|
+
type: data
|
|
357
|
+
dataType: string
|
|
358
|
+
- tag: submenu
|
|
359
|
+
type: sub-contract
|
|
360
|
+
tags:
|
|
361
|
+
- tag: items
|
|
362
|
+
type: sub-contract
|
|
363
|
+
repeated: true
|
|
364
|
+
link: $/
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**4. Featured Item from Array (Array Unwrapping)**
|
|
368
|
+
|
|
369
|
+
```yaml
|
|
370
|
+
name: catalog
|
|
371
|
+
tags:
|
|
372
|
+
- tag: items
|
|
373
|
+
type: sub-contract
|
|
374
|
+
repeated: true
|
|
375
|
+
tags:
|
|
376
|
+
- tag: id
|
|
377
|
+
type: data
|
|
378
|
+
dataType: string
|
|
379
|
+
- tag: title
|
|
380
|
+
type: data
|
|
381
|
+
dataType: string
|
|
382
|
+
- tag: featured
|
|
383
|
+
type: sub-contract
|
|
384
|
+
link: $/items[]
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Best Practices for Recursive Links
|
|
388
|
+
|
|
389
|
+
1. **Use descriptive paths** - Make it clear what you're referencing
|
|
390
|
+
2. **Document recursion** - Add descriptions explaining the recursive relationship
|
|
391
|
+
3. **Consider depth limits** - Be aware of potential deeply nested structures
|
|
392
|
+
4. **Use `[]` for single items** - When referencing one item from an array, use `[]` unwrapping
|
|
393
|
+
5. **Validate paths** - Ensure the referenced paths exist in your contract
|
|
394
|
+
|
|
181
395
|
## Data Types
|
|
182
396
|
|
|
183
397
|
### Basic Types
|
package/docs/jay-html-docs.md
CHANGED
|
@@ -211,6 +211,67 @@ data:
|
|
|
211
211
|
|
|
212
212
|
This creates a recursive type at `root.nested` where `children` has the same type as the parent `nested` object.
|
|
213
213
|
|
|
214
|
+
### Array Recursion vs. Array Item References
|
|
215
|
+
|
|
216
|
+
When working with arrays, you can use two different syntaxes:
|
|
217
|
+
|
|
218
|
+
**Array Recursion** - Use `array<$/data/path>` to create arrays of recursive items:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
data:
|
|
222
|
+
name: string
|
|
223
|
+
id: string
|
|
224
|
+
children: array<$/data>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This creates an array where each item has the same type as the root data structure.
|
|
228
|
+
|
|
229
|
+
**Generated TypeScript:**
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
export interface TreeViewState {
|
|
233
|
+
name: string;
|
|
234
|
+
id: string;
|
|
235
|
+
children: Array<TreeViewState>;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Array Item Unwrapping** - Use `$/data/path[]` to reference a single item from an array property:
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
data:
|
|
243
|
+
products:
|
|
244
|
+
- id: string
|
|
245
|
+
name: string
|
|
246
|
+
price: number
|
|
247
|
+
featuredProduct: $/data/products[]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The `[]` suffix unwraps the array and links to the item type instead of the full array.
|
|
251
|
+
|
|
252
|
+
**Generated TypeScript:**
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
export interface ProductOfProductListViewState {
|
|
256
|
+
id: string;
|
|
257
|
+
name: string;
|
|
258
|
+
price: number;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface ProductListViewState {
|
|
262
|
+
products: Array<ProductOfProductListViewState>;
|
|
263
|
+
featuredProduct: ProductOfProductListViewState | null;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Comparison:**
|
|
268
|
+
|
|
269
|
+
| Syntax | Use Case | Generated Type |
|
|
270
|
+
| -------------------- | ------------------------- | ------------------ |
|
|
271
|
+
| `array<$/data>` | Multiple items (array) | `Array<ItemType>` |
|
|
272
|
+
| `$/data/arrayProp` | Reference to entire array | `Array<ItemType>` |
|
|
273
|
+
| `$/data/arrayProp[]` | Single item from array | `ItemType \| null` |
|
|
274
|
+
|
|
214
275
|
### Creating Recursive Regions
|
|
215
276
|
|
|
216
277
|
Use the `ref` attribute to mark an element as a recursive region, then use `<recurse>` to trigger recursion:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/compiler-jay-html",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
},
|
|
35
35
|
"author": "",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@jay-framework/compiler-analyze-exported-types": "^0.
|
|
38
|
-
"@jay-framework/compiler-shared": "^0.
|
|
39
|
-
"@jay-framework/component": "^0.
|
|
40
|
-
"@jay-framework/runtime": "^0.
|
|
41
|
-
"@jay-framework/secure": "^0.
|
|
37
|
+
"@jay-framework/compiler-analyze-exported-types": "^0.9.0",
|
|
38
|
+
"@jay-framework/compiler-shared": "^0.9.0",
|
|
39
|
+
"@jay-framework/component": "^0.9.0",
|
|
40
|
+
"@jay-framework/runtime": "^0.9.0",
|
|
41
|
+
"@jay-framework/secure": "^0.9.0",
|
|
42
42
|
"@types/js-yaml": "^4.0.9",
|
|
43
43
|
"change-case": "^4.1.2",
|
|
44
44
|
"js-yaml": "^4.1.0",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@caiogondim/strip-margin": "^1.0.0",
|
|
53
|
-
"@jay-framework/4-react": "^0.
|
|
54
|
-
"@jay-framework/dev-environment": "^0.
|
|
53
|
+
"@jay-framework/4-react": "^0.9.0",
|
|
54
|
+
"@jay-framework/dev-environment": "^0.9.0",
|
|
55
55
|
"@testing-library/jest-dom": "^6.2.0",
|
|
56
56
|
"@types/js-beautify": "^1",
|
|
57
57
|
"@types/node": "^20.11.5",
|
|
@@ -9,6 +9,7 @@ The recursive jay-html feature allows marking HTML subtrees as recursive regions
|
|
|
9
9
|
- `ref="regionName"` on an element to mark it as a recursive region
|
|
10
10
|
- `<recurse ref="regionName" />` to trigger recursion at that point
|
|
11
11
|
- `array<$/data>` type syntax to define recursive type references
|
|
12
|
+
- `$/data/path[]` syntax to unwrap array items and reference the item type
|
|
12
13
|
|
|
13
14
|
## Test Cases
|
|
14
15
|
|
|
@@ -126,8 +127,17 @@ const render = (viewState: TreeViewState) =>
|
|
|
126
127
|
2. **Type Consistency**: The array/property type must match the recursive type reference
|
|
127
128
|
- Array recursion: `array<$/data>` → `Array<ViewState>`
|
|
128
129
|
- Single recursion: `$/data` → `ViewState | null`
|
|
130
|
+
- Array reference: `$/data/arrayProp` → `Array<ItemType>`
|
|
131
|
+
- Array item unwrap: `$/data/arrayProp[]` → `ItemType | null`
|
|
129
132
|
3. **Valid Region References**: `<recurse ref="name">` must reference an existing `ref="name"` element
|
|
130
133
|
4. **Descendant Requirement**: `<recurse>` must be a descendant of the referenced region
|
|
134
|
+
5. **Array Unwrap Validation**: The `[]` syntax must point to an actual array property
|
|
135
|
+
|
|
136
|
+
## Type Nullability Rules
|
|
137
|
+
|
|
138
|
+
- **Non-array types** get `| null` since they may not exist
|
|
139
|
+
- **Array types** don't get `| null` since an empty array (`[]`) can be used
|
|
140
|
+
- **Repeated tags** with `repeated: true` always generate arrays
|
|
131
141
|
|
|
132
142
|
## Not Yet Tested
|
|
133
143
|
|
|
@@ -137,6 +147,5 @@ These fixtures do **not** yet test:
|
|
|
137
147
|
- Referencing nested types (`array<$/data/metadata>`)
|
|
138
148
|
- Error cases (missing guards, invalid references, etc.)
|
|
139
149
|
- React target generation for recursive structures
|
|
140
|
-
- Contract file recursion with `link: $/`
|
|
141
150
|
|
|
142
151
|
Additional test fixtures should be added for these scenarios.
|