@narrative.io/jsonforms-provider-protocols 2.11.0 → 2.12.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/README.md +193 -33
- package/dist/core/initFormData.d.ts +17 -0
- package/dist/core/initFormData.d.ts.map +1 -0
- package/dist/core/initFormData.js +99 -0
- package/dist/core/initFormData.js.map +1 -0
- package/dist/core/projection.d.ts +36 -0
- package/dist/core/projection.d.ts.map +1 -0
- package/dist/core/projection.js +77 -0
- package/dist/core/projection.js.map +1 -0
- package/dist/core/refs.d.ts +58 -0
- package/dist/core/refs.d.ts.map +1 -0
- package/dist/core/refs.js +70 -0
- package/dist/core/refs.js.map +1 -0
- package/dist/core/resolveScope.d.ts +17 -0
- package/dist/core/resolveScope.d.ts.map +1 -0
- package/dist/core/resolveScope.js +28 -0
- package/dist/core/resolveScope.js.map +1 -0
- package/dist/core/seedProjectionTargets.d.ts +60 -0
- package/dist/core/seedProjectionTargets.d.ts.map +1 -0
- package/dist/core/seedProjectionTargets.js +52 -0
- package/dist/core/seedProjectionTargets.js.map +1 -0
- package/dist/core/transforms.d.ts +8 -10
- package/dist/core/transforms.d.ts.map +1 -1
- package/dist/core/transforms.js +58 -13
- package/dist/core/transforms.js.map +1 -1
- package/dist/core/types.d.ts +8 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/jsonforms-provider-protocols.css +6 -2
- package/dist/no-eval-ajv.d.ts +70 -0
- package/dist/no-eval-ajv.d.ts.map +1 -0
- package/dist/no-eval-ajv.js +247 -0
- package/dist/no-eval-ajv.js.map +1 -0
- package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderAutocomplete.vue.js +12 -6
- package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
- package/dist/vue/components/ProviderMultiSelect.vue2.js +21 -11
- package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
- package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts +9 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.d.ts.map +1 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.js +8 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue.js.map +1 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue2.js +142 -0
- package/dist/vue/components/ProviderObjectMultiSelect.vue2.js.map +1 -0
- package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
- package/dist/vue/components/ProviderSelect.vue.js +1 -1
- package/dist/vue/components/ProviderSelect.vue2.js +22 -10
- package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
- package/dist/vue/composables/useDataLayer.d.ts +10 -0
- package/dist/vue/composables/useDataLayer.d.ts.map +1 -0
- package/dist/vue/composables/useDataLayer.js +26 -0
- package/dist/vue/composables/useDataLayer.js.map +1 -0
- package/dist/vue/composables/useDerive.d.ts +5 -2
- package/dist/vue/composables/useDerive.d.ts.map +1 -1
- package/dist/vue/composables/useDerive.js +29 -12
- package/dist/vue/composables/useDerive.js.map +1 -1
- package/dist/vue/composables/useDeriveInitialValue.d.ts +36 -0
- package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
- package/dist/vue/composables/useDeriveInitialValue.js +125 -0
- package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
- package/dist/vue/composables/useDirtyValidation.d.ts +9 -0
- package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -0
- package/dist/vue/composables/useDirtyValidation.js +15 -0
- package/dist/vue/composables/useDirtyValidation.js.map +1 -0
- package/dist/vue/composables/useProjection.d.ts +42 -0
- package/dist/vue/composables/useProjection.d.ts.map +1 -0
- package/dist/vue/composables/useProjection.js +116 -0
- package/dist/vue/composables/useProjection.js.map +1 -0
- package/dist/vue/composables/useProvider.d.ts +2 -2
- package/dist/vue/composables/useProvider.d.ts.map +1 -1
- package/dist/vue/composables/useProvider.js +14 -10
- package/dist/vue/composables/useProvider.js.map +1 -1
- package/dist/vue/index.d.ts +9 -1
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.js +72 -34
- package/dist/vue/index.js.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.d.ts +9 -0
- package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfBoolean.vue.js +44 -17
- package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.d.ts +9 -0
- package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnum.vue.js +38 -24
- package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.d.ts +9 -0
- package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfEnumArray.vue.js +40 -20
- package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.d.ts +9 -0
- package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfNumber.vue.js +33 -23
- package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
- package/dist/vue/primevue/JfText.vue.d.ts +9 -0
- package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfText.vue.js +51 -35
- package/dist/vue/primevue/JfText.vue.js.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.d.ts +9 -0
- package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
- package/dist/vue/primevue/JfTextArea.vue.js +34 -19
- package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
- package/dist/vue/primevue/index.d.ts.map +1 -1
- package/dist/vue/primevue/index.js +100 -8
- package/dist/vue/primevue/index.js.map +1 -1
- package/dist/vue/utils/objectMultiSelect.d.ts +68 -0
- package/dist/vue/utils/objectMultiSelect.d.ts.map +1 -0
- package/dist/vue/utils/objectMultiSelect.js +72 -0
- package/dist/vue/utils/objectMultiSelect.js.map +1 -0
- package/dist/vue/utils/placeholder.d.ts +17 -0
- package/dist/vue/utils/placeholder.d.ts.map +1 -0
- package/dist/vue/utils/placeholder.js +17 -0
- package/dist/vue/utils/placeholder.js.map +1 -0
- package/package.json +10 -2
- package/src/core/initFormData.ts +208 -0
- package/src/core/projection.ts +147 -0
- package/src/core/refs.ts +166 -0
- package/src/core/resolveScope.ts +54 -0
- package/src/core/seedProjectionTargets.ts +144 -0
- package/src/core/transforms.ts +118 -26
- package/src/core/types.ts +9 -0
- package/src/index.ts +22 -2
- package/src/no-eval-ajv.ts +381 -0
- package/src/vue/components/ProviderAutocomplete.vue +11 -7
- package/src/vue/components/ProviderMultiSelect.vue +22 -15
- package/src/vue/components/ProviderObjectMultiSelect.vue +169 -0
- package/src/vue/components/ProviderSelect.vue +23 -14
- package/src/vue/composables/useDataLayer.ts +43 -0
- package/src/vue/composables/useDerive.ts +62 -16
- package/src/vue/composables/useDeriveInitialValue.ts +195 -0
- package/src/vue/composables/useDirtyValidation.ts +20 -0
- package/src/vue/composables/useProjection.ts +245 -0
- package/src/vue/composables/useProvider.ts +28 -11
- package/src/vue/index.ts +83 -47
- package/src/vue/primevue/JfBoolean.vue +35 -12
- package/src/vue/primevue/JfEnum.vue +35 -26
- package/src/vue/primevue/JfEnumArray.vue +37 -20
- package/src/vue/primevue/JfNumber.vue +32 -24
- package/src/vue/primevue/JfText.vue +48 -33
- package/src/vue/primevue/JfTextArea.vue +32 -21
- package/src/vue/primevue/index.ts +114 -8
- package/src/vue/styles.css +26 -1
- package/src/vue/utils/objectMultiSelect.ts +171 -0
- package/src/vue/utils/placeholder.ts +42 -0
package/README.md
CHANGED
|
@@ -108,11 +108,9 @@ const handleChange = (event) => data.value = event.data
|
|
|
108
108
|
|
|
109
109
|
### Examples
|
|
110
110
|
- [Simple Dropdown](./docs/examples/simple-dropdown.md)
|
|
111
|
-
- [Cascading Dropdowns](./docs/examples/cascading-dropdowns.md)
|
|
112
|
-
- [Autocomplete Search](./docs/examples/autocomplete-search.md)
|
|
113
|
-
- [Custom Protocols](./docs/examples/custom-protocol.md)
|
|
114
111
|
- [Custom Renderers](./docs/examples/custom-renderer.md)
|
|
115
|
-
- [
|
|
112
|
+
- [All Examples](./docs/examples/README.md)
|
|
113
|
+
- For end-to-end usage of data layer, projection, validation gating, and the CSP-safe validator, see the demo app in `demo/`
|
|
116
114
|
|
|
117
115
|
### Help & Troubleshooting
|
|
118
116
|
- [Troubleshooting Guide](./docs/troubleshooting.md)
|
|
@@ -190,7 +188,9 @@ Recursively flattens nested tree structures into a single-level array:
|
|
|
190
188
|
- Adds `_depth`, `_parent`, and `_formattedLabel` metadata to items
|
|
191
189
|
|
|
192
190
|
**Filter Transform**
|
|
193
|
-
Filters items based on
|
|
191
|
+
Filters items based on conditions. Supports a simple single-key syntax and a multi-condition syntax with operators.
|
|
192
|
+
|
|
193
|
+
Simple syntax (single key/values):
|
|
194
194
|
|
|
195
195
|
```json
|
|
196
196
|
{
|
|
@@ -200,8 +200,31 @@ Filters items based on property values:
|
|
|
200
200
|
}
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
-
|
|
204
|
-
|
|
203
|
+
Multi-condition syntax (AND logic):
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"name": "filter",
|
|
208
|
+
"conditions": [
|
|
209
|
+
{ "key": "status", "values": ["active"] },
|
|
210
|
+
{ "key": "connections", "operator": "empty" }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Available operators:
|
|
216
|
+
|
|
217
|
+
| Operator | Description |
|
|
218
|
+
|----------|-------------|
|
|
219
|
+
| `eq` | Value matches one of `values` (default) |
|
|
220
|
+
| `neq` | Value does NOT match any of `values` |
|
|
221
|
+
| `empty` | Value is null, undefined, empty array, or empty string |
|
|
222
|
+
| `notEmpty` | Inverse of `empty` |
|
|
223
|
+
| `gt` | Value > `values[0]` |
|
|
224
|
+
| `gte` | Value >= `values[0]` |
|
|
225
|
+
| `lt` | Value < `values[0]` |
|
|
226
|
+
| `lte` | Value <= `values[0]` |
|
|
227
|
+
| `contains` | String includes substring, or array includes value |
|
|
205
228
|
|
|
206
229
|
**Combining Transforms**
|
|
207
230
|
Transforms are applied sequentially in pipeline order:
|
|
@@ -215,20 +238,6 @@ Transforms are applied sequentially in pipeline order:
|
|
|
215
238
|
}
|
|
216
239
|
```
|
|
217
240
|
|
|
218
|
-
**Custom Transforms**
|
|
219
|
-
Register custom transforms for your specific needs:
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
import { registerTransform } from '@narrative.io/jsonforms-provider-protocols'
|
|
223
|
-
|
|
224
|
-
registerTransform('uppercase', (items, config) => {
|
|
225
|
-
return items.map(item => ({
|
|
226
|
-
...item,
|
|
227
|
-
name: item.name.toUpperCase()
|
|
228
|
-
}))
|
|
229
|
-
})
|
|
230
|
-
```
|
|
231
|
-
|
|
232
241
|
### Template Variables
|
|
233
242
|
Create dynamic URLs using form data:
|
|
234
243
|
|
|
@@ -247,7 +256,7 @@ Control when data is fetched:
|
|
|
247
256
|
- `query` - Load when user types (autocomplete)
|
|
248
257
|
|
|
249
258
|
### Derive Functionality
|
|
250
|
-
Auto-populate fields from form data or
|
|
259
|
+
Auto-populate fields from form data or the dataLayer:
|
|
251
260
|
|
|
252
261
|
```json
|
|
253
262
|
{
|
|
@@ -261,33 +270,56 @@ Auto-populate fields from form data or external sources:
|
|
|
261
270
|
}
|
|
262
271
|
```
|
|
263
272
|
|
|
264
|
-
####
|
|
265
|
-
|
|
273
|
+
#### DataLayer Support
|
|
274
|
+
Inject external data into forms using the `createDataLayer` API and reference it with the `dataLayer()` derive syntax:
|
|
266
275
|
|
|
267
276
|
```vue
|
|
268
277
|
<script setup>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
})
|
|
278
|
+
import { createDataLayer } from '@narrative.io/jsonforms-provider-protocols'
|
|
279
|
+
|
|
280
|
+
const dataLayer = createDataLayer()
|
|
281
|
+
dataLayer.push({ dataset_name: "My Dataset" })
|
|
274
282
|
|
|
275
|
-
|
|
283
|
+
// Merge additional data at any time
|
|
284
|
+
dataLayer.push({ dataset_id: 42 })
|
|
276
285
|
</script>
|
|
277
286
|
```
|
|
278
287
|
|
|
279
288
|
```json
|
|
280
289
|
{
|
|
281
|
-
"type": "Control",
|
|
282
|
-
"scope": "#/properties/
|
|
290
|
+
"type": "Control",
|
|
291
|
+
"scope": "#/properties/audience",
|
|
283
292
|
"options": {
|
|
284
|
-
"derive": "
|
|
293
|
+
"derive": "dataLayer(dataset_name)",
|
|
285
294
|
"mode": "follow",
|
|
286
295
|
"readonly": true
|
|
287
296
|
}
|
|
288
297
|
}
|
|
289
298
|
```
|
|
290
299
|
|
|
300
|
+
The `ConnectorDataLayer` type defines available properties:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
interface ConnectorDataLayer {
|
|
304
|
+
dataset_name?: string
|
|
305
|
+
dataset_description?: string
|
|
306
|
+
dataset_id?: number
|
|
307
|
+
profile_id?: string
|
|
308
|
+
profile_name?: string
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
You can also read the dataLayer state directly in components:
|
|
313
|
+
|
|
314
|
+
```vue
|
|
315
|
+
<script setup>
|
|
316
|
+
import { useDataLayer } from '@narrative.io/jsonforms-provider-protocols'
|
|
317
|
+
|
|
318
|
+
const dataLayerState = useDataLayer()
|
|
319
|
+
// dataLayerState.value.dataset_name
|
|
320
|
+
</script>
|
|
321
|
+
```
|
|
322
|
+
|
|
291
323
|
### Error Handling
|
|
292
324
|
Control error display behavior with the `showError` property:
|
|
293
325
|
|
|
@@ -307,6 +339,44 @@ Control error display behavior with the `showError` property:
|
|
|
307
339
|
|
|
308
340
|
When `showError` is `false`, failed requests return empty results instead of throwing errors. Defaults to `true`.
|
|
309
341
|
|
|
342
|
+
### Projection
|
|
343
|
+
Render simple controls (text, number) against deeply nested or array-wrapped data structures using the `projection` UISchema option. The control sees a simple value, while the underlying form data maintains the full structure.
|
|
344
|
+
|
|
345
|
+
The projection path is **relative to the control's `scope`**. So when `scope` is `#/properties/data_rates` and the projection is `"0.video_rate_usd"`, it resolves to `data_rates[0].video_rate_usd` in the form data:
|
|
346
|
+
|
|
347
|
+
```json
|
|
348
|
+
{
|
|
349
|
+
"type": "Control",
|
|
350
|
+
"scope": "#/properties/data_rates",
|
|
351
|
+
"options": {
|
|
352
|
+
"placeholder": "Enter video rate...",
|
|
353
|
+
"projection": "0.video_rate_usd"
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
With form data `{ data_rates: [{ video_rate_usd: 2.5, display_rate_usd: 1.5 }] }`, the control renders `2.5` as a simple number input. When the user changes the value, only `video_rate_usd` is updated — all sibling properties are preserved.
|
|
359
|
+
|
|
360
|
+
**Path syntax:** Dot-separated segments (similar to Lodash `_.get`) where numeric segments are array indices and string segments are object keys.
|
|
361
|
+
|
|
362
|
+
| Projection Path | Data Shape | Control Sees |
|
|
363
|
+
|-----------------|-----------|-------------|
|
|
364
|
+
| `0` | `[123]` | `123` |
|
|
365
|
+
| `0.video_rate_usd` | `[{ video_rate_usd: 2.5 }]` | `2.5` |
|
|
366
|
+
| `include` | `{ include: ["a", "b"] }` | `["a", "b"]` |
|
|
367
|
+
|
|
368
|
+
The schema is also resolved through the projection path, so validation works correctly on the projected value.
|
|
369
|
+
|
|
370
|
+
Use `useProjection` directly in custom components:
|
|
371
|
+
|
|
372
|
+
```vue
|
|
373
|
+
<script setup>
|
|
374
|
+
import { useProjection } from '@narrative.io/jsonforms-provider-protocols'
|
|
375
|
+
|
|
376
|
+
const { projectedData, projectedSchema, handleProjectedChange, hasProjection } = useProjection(control, handleChange)
|
|
377
|
+
</script>
|
|
378
|
+
```
|
|
379
|
+
|
|
310
380
|
### Composables
|
|
311
381
|
Use providers directly in your components:
|
|
312
382
|
|
|
@@ -318,6 +388,96 @@ const { items, loading, error, reload } = useProvider(binding, context)
|
|
|
318
388
|
</script>
|
|
319
389
|
```
|
|
320
390
|
|
|
391
|
+
The library also exposes `useProjection`, `useDirtyValidation`, `useDataLayer`, and `useDeriveInitialValue` for custom renderers — see [Vue Components](./docs/components.md) and the [API Reference](./docs/api/README.md).
|
|
392
|
+
|
|
393
|
+
### Object Multi-Select
|
|
394
|
+
|
|
395
|
+
For arrays whose items are paired objects (e.g. `[{ dataset_id, dataset_name }]`), `ProviderObjectMultiSelect` registers automatically and translates between the form-data shape and PrimeVue's `<MultiSelect>` model:
|
|
396
|
+
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"type": "Control",
|
|
400
|
+
"scope": "#/properties/datasets",
|
|
401
|
+
"options": {
|
|
402
|
+
"objectKeys": { "value": "dataset_id", "label": "dataset_name" },
|
|
403
|
+
"autoSelectSingle": true,
|
|
404
|
+
"provider": { "ref": "datasets", "protocol": "rest_api", "config": { "...": "..." } }
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`objectKeys` is inferred from the array's `items.required` when one of the two required properties has `format: "uuid"`. Specify explicitly to override.
|
|
410
|
+
|
|
411
|
+
### Schema-Aware Initialization
|
|
412
|
+
|
|
413
|
+
`initFormDataFromSchema` walks `$ref`/`$defs`, seeds `const` and `default` values (including through optional `oneOf` containers), and omits unset optionals:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import { initFormDataFromSchema, seedProjectionTargets } from '@narrative.io/jsonforms-provider-protocols'
|
|
417
|
+
|
|
418
|
+
const data = seedProjectionTargets(initFormDataFromSchema(schema), uischema)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
`seedProjectionTargets` is needed when an `options.projection` control targets an array index — without it, `items.required` validators see no item to apply against and the asterisk on the projected control would lie. It's idempotent and preserves any existing values at the target paths.
|
|
422
|
+
|
|
423
|
+
### Validation Gating
|
|
424
|
+
|
|
425
|
+
Renderers (both built-in and custom, via `useDirtyValidation`) gate error display behind user interaction. Errors and `p-invalid` styling appear only after `blur` (text/number/select) or first `change` (checkbox/multiselect). The full AJV error array still flows through `<JsonForms @change>` unchanged, so consumers decide separately when to enable a submit button.
|
|
426
|
+
|
|
427
|
+
### Derive Initial Value (Async)
|
|
428
|
+
|
|
429
|
+
When the initial value of a field comes from an API call rather than from local form data, use `options.deriveInitialValue`:
|
|
430
|
+
|
|
431
|
+
```json
|
|
432
|
+
{
|
|
433
|
+
"type": "Control",
|
|
434
|
+
"scope": "#/properties/advertiser",
|
|
435
|
+
"options": {
|
|
436
|
+
"deriveInitialValue": {
|
|
437
|
+
"protocol": "rest_api",
|
|
438
|
+
"config": {
|
|
439
|
+
"url": "https://api.example.com/profiles/{{data.profile_id}}",
|
|
440
|
+
"items": "$",
|
|
441
|
+
"map": { "value": "$.mdm_id" }
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
The fetched value is applied on mount once template variables resolve, re-fetched when dependencies change, and not re-applied when the user has already changed the field with the same template context. See [API: useDeriveInitialValue](./docs/api/README.md#usederiveinitialvalue).
|
|
449
|
+
|
|
450
|
+
### CSP-Safe Validation
|
|
451
|
+
|
|
452
|
+
For environments that forbid `new Function` (Cloudflare Pages, strict CSP), import the AJV-shaped facade backed by `@cfworker/json-schema` from the `./no-eval-ajv` subpath:
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { createNoEvalAjv } from '@narrative.io/jsonforms-provider-protocols/no-eval-ajv'
|
|
456
|
+
|
|
457
|
+
const ajv = createNoEvalAjv({ draft: '2020-12' })
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
```vue
|
|
461
|
+
<JsonForms
|
|
462
|
+
:data="data"
|
|
463
|
+
:schema="schema"
|
|
464
|
+
:uischema="uischema"
|
|
465
|
+
:ajv="ajv"
|
|
466
|
+
:renderers="markRaw(providerRenderers)"
|
|
467
|
+
@change="handleChange"
|
|
468
|
+
/>
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Only `compile()` is functional; `addSchema`/`getSchema`/`addFormat`/`addKeyword` are no-ops kept for forward compatibility with plugins that probe the AJV interface. AJV error shapes (`instancePath`, `keyword`, `params`, `message`) are preserved so JsonForms can render errors identically.
|
|
472
|
+
|
|
473
|
+
### Migrating from Earlier Betas
|
|
474
|
+
|
|
475
|
+
| Old API | New API |
|
|
476
|
+
|---------|---------|
|
|
477
|
+
| `provide('externalData', ref)` | `createDataLayer().provide()` |
|
|
478
|
+
| `derive: "externalData(field)"` | `derive: "dataLayer(field)"` |
|
|
479
|
+
| `registerTransform(...)` | Filter transform `conditions` array (see [Filter Transform](#built-in-transforms)) |
|
|
480
|
+
|
|
321
481
|
## 🛠 Development
|
|
322
482
|
|
|
323
483
|
```bash
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialize a form data object from a JSON Schema.
|
|
3
|
+
* Resolves $ref, const, default, oneOf/discriminator, and typed empty values.
|
|
4
|
+
*
|
|
5
|
+
* Optional fields (not listed in the parent schema's `required` array) that
|
|
6
|
+
* have no `const`, no single-value `enum`, and no `default` are omitted
|
|
7
|
+
* entirely from the result. This avoids seeding values (e.g. `null` for
|
|
8
|
+
* `type: "integer"`) that fail AJV's type check and surface spurious errors
|
|
9
|
+
* on untouched fields. Required fields retain legacy typed-empty seeding
|
|
10
|
+
* (`""`, `null`, `false`, `[]`) so that "is required" surfaces cleanly.
|
|
11
|
+
*
|
|
12
|
+
* @param schema - The full JSON Schema (must include $defs if $refs are used)
|
|
13
|
+
* @param seed - Optional existing data to merge (seed values take priority)
|
|
14
|
+
* @returns A data object with schema-defined fields initialized
|
|
15
|
+
*/
|
|
16
|
+
export declare function initFormDataFromSchema(schema: Record<string, unknown>, seed?: Record<string, unknown>): Record<string, unknown>;
|
|
17
|
+
//# sourceMappingURL=initFormData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initFormData.d.ts","sourceRoot":"","sources":["../../src/core/initFormData.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { resolveRef } from "./refs.js";
|
|
2
|
+
function initFormDataFromSchema(schema, seed) {
|
|
3
|
+
const result = initProperty(schema, schema, seed, true);
|
|
4
|
+
const base = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
5
|
+
if (seed && typeof seed === "object") {
|
|
6
|
+
const schemaKeys = new Set(
|
|
7
|
+
Object.keys(
|
|
8
|
+
resolveRef(schema, schema)?.properties ?? {}
|
|
9
|
+
)
|
|
10
|
+
);
|
|
11
|
+
for (const key of Object.keys(seed)) {
|
|
12
|
+
if (!schemaKeys.has(key) && !(key in base)) {
|
|
13
|
+
base[key] = seed[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return base;
|
|
18
|
+
}
|
|
19
|
+
function initProperty(property, root, seed, required) {
|
|
20
|
+
if (!property || typeof property !== "object") {
|
|
21
|
+
return required ? null : void 0;
|
|
22
|
+
}
|
|
23
|
+
const resolved = resolveRef(property, root);
|
|
24
|
+
const type = resolved.type;
|
|
25
|
+
const isOneOf = Array.isArray(resolved.oneOf);
|
|
26
|
+
if (seed !== void 0 && seed !== null && type !== "object" && !isOneOf) {
|
|
27
|
+
return seed;
|
|
28
|
+
}
|
|
29
|
+
if ("const" in resolved) {
|
|
30
|
+
return resolved.const;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(resolved.enum) && resolved.enum.length === 1) {
|
|
33
|
+
return resolved.enum[0];
|
|
34
|
+
}
|
|
35
|
+
if ("default" in resolved) {
|
|
36
|
+
return resolved.default;
|
|
37
|
+
}
|
|
38
|
+
if (isOneOf) {
|
|
39
|
+
const value = initOneOf(resolved, root, seed, required);
|
|
40
|
+
if (!required && (value === void 0 || value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0)) {
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
if (type === "object") {
|
|
46
|
+
const obj = initObject(
|
|
47
|
+
resolved,
|
|
48
|
+
root,
|
|
49
|
+
seed,
|
|
50
|
+
required
|
|
51
|
+
);
|
|
52
|
+
if (!required && Object.keys(obj).length === 0) return void 0;
|
|
53
|
+
return obj;
|
|
54
|
+
}
|
|
55
|
+
if (!required) return void 0;
|
|
56
|
+
switch (type) {
|
|
57
|
+
case "array":
|
|
58
|
+
return seed !== void 0 && seed !== null ? seed : [];
|
|
59
|
+
case "string":
|
|
60
|
+
return "";
|
|
61
|
+
case "boolean":
|
|
62
|
+
return false;
|
|
63
|
+
case "number":
|
|
64
|
+
case "integer":
|
|
65
|
+
default:
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function initObject(schema, root, seed, parentRequired) {
|
|
70
|
+
const properties = schema.properties;
|
|
71
|
+
if (!properties) {
|
|
72
|
+
return seed && typeof seed === "object" ? { ...seed } : {};
|
|
73
|
+
}
|
|
74
|
+
const requiredSet = new Set(
|
|
75
|
+
Array.isArray(schema.required) ? schema.required : []
|
|
76
|
+
);
|
|
77
|
+
const result = {};
|
|
78
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
79
|
+
const seedValue = seed && typeof seed === "object" ? seed[key] : void 0;
|
|
80
|
+
const effectiveRequired = parentRequired && requiredSet.has(key);
|
|
81
|
+
const value = initProperty(propSchema, root, seedValue, effectiveRequired);
|
|
82
|
+
if (value !== void 0) {
|
|
83
|
+
result[key] = value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function initOneOf(schema, root, seed, required) {
|
|
89
|
+
const variants = schema.oneOf;
|
|
90
|
+
if (!variants || variants.length === 0) return required ? null : void 0;
|
|
91
|
+
const first = variants[0];
|
|
92
|
+
if (!first) return required ? null : void 0;
|
|
93
|
+
const firstVariant = resolveRef(first, root);
|
|
94
|
+
return initProperty(firstVariant, root, seed, required);
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
initFormDataFromSchema
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=initFormData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initFormData.js","sources":["../../src/core/initFormData.ts"],"sourcesContent":["/**\n * Initialize a form data object from a JSON Schema.\n * Resolves $ref, const, default, oneOf/discriminator, and typed empty values.\n *\n * Optional fields (not listed in the parent schema's `required` array) that\n * have no `const`, no single-value `enum`, and no `default` are omitted\n * entirely from the result. This avoids seeding values (e.g. `null` for\n * `type: \"integer\"`) that fail AJV's type check and surface spurious errors\n * on untouched fields. Required fields retain legacy typed-empty seeding\n * (`\"\"`, `null`, `false`, `[]`) so that \"is required\" surfaces cleanly.\n *\n * @param schema - The full JSON Schema (must include $defs if $refs are used)\n * @param seed - Optional existing data to merge (seed values take priority)\n * @returns A data object with schema-defined fields initialized\n */\nexport function initFormDataFromSchema(\n schema: Record<string, unknown>,\n seed?: Record<string, unknown>,\n): Record<string, unknown> {\n const result = initProperty(schema, schema, seed, true) as\n | Record<string, unknown>\n | undefined;\n\n const base =\n result && typeof result === \"object\" && !Array.isArray(result)\n ? result\n : {};\n\n // Preserve seed keys not described by the schema's properties.\n if (seed && typeof seed === \"object\") {\n const schemaKeys = new Set(\n Object.keys(\n (resolveRef(schema, schema) as Record<string, unknown>)?.properties ??\n {},\n ),\n );\n for (const key of Object.keys(seed)) {\n if (!schemaKeys.has(key) && !(key in base)) {\n base[key] = seed[key];\n }\n }\n }\n\n return base;\n}\n\nimport { resolveRef } from \"./refs\";\n\n/**\n * Initialize a single property value based on its schema definition.\n * Returns `undefined` when the property is optional and has nothing\n * concrete to seed — the caller then omits the key from its result.\n */\nfunction initProperty(\n property: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: unknown,\n required: boolean,\n): unknown {\n if (!property || typeof property !== \"object\") {\n return required ? null : undefined;\n }\n\n const resolved = resolveRef(property, root);\n const type = resolved.type as string | undefined;\n const isOneOf = Array.isArray(resolved.oneOf);\n\n // Priority 1: seed wins for non-object, non-oneOf types. For oneOf we still\n // descend so that a partial seed (e.g. `{value: 5}` missing the\n // discriminator) picks up the variant's const/default for untouched fields.\n if (seed !== undefined && seed !== null && type !== \"object\" && !isOneOf) {\n return seed;\n }\n\n // Priority 2: const (schema-invariant — always set).\n if (\"const\" in resolved) {\n return resolved.const;\n }\n\n // Priority 3: single-value enum (same reasoning — only one valid value).\n if (\n Array.isArray(resolved.enum) &&\n (resolved.enum as unknown[]).length === 1\n ) {\n return (resolved.enum as unknown[])[0];\n }\n\n // Priority 4: default (explicit author intent — always honored).\n if (\"default\" in resolved) {\n return resolved.default;\n }\n\n // Priority 5: oneOf. Recurse into the first variant, propagating the\n // `required` flag so that the variant's own schema-declared required fields\n // only get typed-empty seeding when the container is required. For optional\n // containers, the variant's const / default / single-value enum still seed\n // (author intent is unconditional) but typed-empty placeholders do not.\n // If recursion yields nothing forced, we collapse back to undefined.\n if (isOneOf) {\n const value = initOneOf(resolved, root, seed, required);\n if (\n !required &&\n (value === undefined ||\n (value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 0))\n ) {\n return undefined;\n }\n return value;\n }\n\n // Priority 6: objects recurse. The `required` flag propagates downward:\n // inside an optional ancestor, nested required primitives also collapse,\n // so an optional object with nested required fields stays absent instead\n // of materializing a shell that AJV would flag.\n if (type === \"object\") {\n const obj = initObject(\n resolved,\n root,\n seed as Record<string, unknown>,\n required,\n );\n if (!required && Object.keys(obj).length === 0) return undefined;\n return obj;\n }\n\n // Priority 7: optional primitives — omit.\n if (!required) return undefined;\n\n // Priority 8: required primitives — legacy typed empty.\n switch (type) {\n case \"array\":\n return seed !== undefined && seed !== null ? seed : [];\n case \"string\":\n return \"\";\n case \"boolean\":\n return false;\n case \"number\":\n case \"integer\":\n default:\n return null;\n }\n}\n\n/**\n * Initialize an object type by recursing into its properties.\n * Keys whose initialization returns `undefined` are omitted.\n *\n * `parentRequired` controls how schema.required propagates: a child is\n * treated as required only if both its parent is required AND it appears in\n * the parent's required array. Inside an optional ancestor the whole\n * subtree collapses (except for author-forced values: const, default,\n * single-value enum, or seeded values).\n */\nfunction initObject(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: Record<string, unknown> | undefined,\n parentRequired: boolean,\n): Record<string, unknown> {\n const properties = schema.properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (!properties) {\n return seed && typeof seed === \"object\" ? { ...seed } : {};\n }\n\n const requiredSet = new Set<string>(\n Array.isArray(schema.required) ? (schema.required as string[]) : [],\n );\n\n const result: Record<string, unknown> = {};\n\n for (const [key, propSchema] of Object.entries(properties)) {\n const seedValue = seed && typeof seed === \"object\" ? seed[key] : undefined;\n const effectiveRequired = parentRequired && requiredSet.has(key);\n const value = initProperty(propSchema, root, seedValue, effectiveRequired);\n if (value !== undefined) {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/**\n * Handle oneOf schemas — pick the first variant and initialize it.\n * `required` propagates: if the outer oneOf is required, the variant is\n * materialized with typed-empty seeding for its required fields; if optional,\n * only author-forced values (const / default / single-enum / seed) survive.\n */\nfunction initOneOf(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed: unknown,\n required: boolean,\n): unknown {\n const variants = schema.oneOf as Record<string, unknown>[];\n if (!variants || variants.length === 0) return required ? null : undefined;\n\n const first = variants[0];\n if (!first) return required ? null : undefined;\n\n const firstVariant = resolveRef(first, root);\n return initProperty(firstVariant, root, seed, required);\n}\n"],"names":[],"mappings":";AAeO,SAAS,uBACd,QACA,MACyB;AACzB,QAAM,SAAS,aAAa,QAAQ,QAAQ,MAAM,IAAI;AAItD,QAAM,OACJ,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IACzD,SACA,CAAA;AAGN,MAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,UAAM,aAAa,IAAI;AAAA,MACrB,OAAO;AAAA,QACJ,WAAW,QAAQ,MAAM,GAA+B,cACvD,CAAA;AAAA,MAAC;AAAA,IACL;AAEF,eAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,UAAI,CAAC,WAAW,IAAI,GAAG,KAAK,EAAE,OAAO,OAAO;AAC1C,aAAK,GAAG,IAAI,KAAK,GAAG;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AASA,SAAS,aACP,UACA,MACA,MACA,UACS;AACT,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,WAAO,WAAW,OAAO;AAAA,EAC3B;AAEA,QAAM,WAAW,WAAW,UAAU,IAAI;AAC1C,QAAM,OAAO,SAAS;AACtB,QAAM,UAAU,MAAM,QAAQ,SAAS,KAAK;AAK5C,MAAI,SAAS,UAAa,SAAS,QAAQ,SAAS,YAAY,CAAC,SAAS;AACxE,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,UAAU;AACvB,WAAO,SAAS;AAAA,EAClB;AAGA,MACE,MAAM,QAAQ,SAAS,IAAI,KAC1B,SAAS,KAAmB,WAAW,GACxC;AACA,WAAQ,SAAS,KAAmB,CAAC;AAAA,EACvC;AAGA,MAAI,aAAa,UAAU;AACzB,WAAO,SAAS;AAAA,EAClB;AAQA,MAAI,SAAS;AACX,UAAM,QAAQ,UAAU,UAAU,MAAM,MAAM,QAAQ;AACtD,QACE,CAAC,aACA,UAAU,UACR,UAAU,QACT,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,KACpB,OAAO,KAAK,KAAgC,EAAE,WAAW,IAC7D;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAMA,MAAI,SAAS,UAAU;AACrB,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAEF,QAAI,CAAC,YAAY,OAAO,KAAK,GAAG,EAAE,WAAW,EAAG,QAAO;AACvD,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,SAAU,QAAO;AAGtB,UAAQ,MAAA;AAAA,IACN,KAAK;AACH,aAAO,SAAS,UAAa,SAAS,OAAO,OAAO,CAAA;AAAA,IACtD,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EAAA;AAEb;AAYA,SAAS,WACP,QACA,MACA,MACA,gBACyB;AACzB,QAAM,aAAa,OAAO;AAG1B,MAAI,CAAC,YAAY;AACf,WAAO,QAAQ,OAAO,SAAS,WAAW,EAAE,GAAG,KAAA,IAAS,CAAA;AAAA,EAC1D;AAEA,QAAM,cAAc,IAAI;AAAA,IACtB,MAAM,QAAQ,OAAO,QAAQ,IAAK,OAAO,WAAwB,CAAA;AAAA,EAAC;AAGpE,QAAM,SAAkC,CAAA;AAExC,aAAW,CAAC,KAAK,UAAU,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC1D,UAAM,YAAY,QAAQ,OAAO,SAAS,WAAW,KAAK,GAAG,IAAI;AACjE,UAAM,oBAAoB,kBAAkB,YAAY,IAAI,GAAG;AAC/D,UAAM,QAAQ,aAAa,YAAY,MAAM,WAAW,iBAAiB;AACzE,QAAI,UAAU,QAAW;AACvB,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAQA,SAAS,UACP,QACA,MACA,MACA,UACS;AACT,QAAM,WAAW,OAAO;AACxB,MAAI,CAAC,YAAY,SAAS,WAAW,EAAG,QAAO,WAAW,OAAO;AAEjE,QAAM,QAAQ,SAAS,CAAC;AACxB,MAAI,CAAC,MAAO,QAAO,WAAW,OAAO;AAErC,QAAM,eAAe,WAAW,OAAO,IAAI;AAC3C,SAAO,aAAa,cAAc,MAAM,MAAM,QAAQ;AACxD;"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection utilities for navigating complex data structures
|
|
3
|
+
* through a dot-separated path where numeric segments are array indices.
|
|
4
|
+
*
|
|
5
|
+
* Examples:
|
|
6
|
+
* "0" → first element of an array
|
|
7
|
+
* "include" → the `include` property of an object
|
|
8
|
+
* "0.video_rate_usd" → nested property inside the first array element
|
|
9
|
+
*/
|
|
10
|
+
export type ProjectionSegment = string | number;
|
|
11
|
+
/**
|
|
12
|
+
* Parse a projection path string into typed segments.
|
|
13
|
+
* Numeric strings become numbers (array indices), others stay as strings (object keys).
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseProjectionPath(path: string): ProjectionSegment[];
|
|
16
|
+
/**
|
|
17
|
+
* Read a value from `data` by following the projection path.
|
|
18
|
+
* Returns `undefined` if any segment along the path is missing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getProjectedValue(data: unknown, path: string): unknown;
|
|
21
|
+
/**
|
|
22
|
+
* Immutably set a value at the projection path, preserving all sibling data.
|
|
23
|
+
* Constructs missing intermediate structures (arrays for numeric segments, objects for string segments).
|
|
24
|
+
*/
|
|
25
|
+
export declare function setProjectedValue(data: unknown, path: string, value: unknown): unknown;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the schema at the projected path.
|
|
28
|
+
* Numeric segments traverse into `items` (array item schema).
|
|
29
|
+
* String segments traverse into `properties[segment]`.
|
|
30
|
+
*
|
|
31
|
+
* Dereferences `$ref` nodes transparently at every step, and falls through
|
|
32
|
+
* to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
|
|
33
|
+
* directly — picks the first branch that satisfies the navigation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getProjectedSchema(schema: Record<string, any>, path: string): Record<string, any>;
|
|
36
|
+
//# sourceMappingURL=projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/core/projection.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAMrE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,OAAO,CAGT;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAEhC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GAEX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAmCrB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { deref, tryCombinatorBranches } from "./refs.js";
|
|
2
|
+
function parseProjectionPath(path) {
|
|
3
|
+
if (!path) return [];
|
|
4
|
+
return path.split(".").map((s) => {
|
|
5
|
+
const n = Number(s);
|
|
6
|
+
return Number.isInteger(n) && n >= 0 ? n : s;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function getProjectedValue(data, path) {
|
|
10
|
+
const segments = parseProjectionPath(path);
|
|
11
|
+
let current = data;
|
|
12
|
+
for (const seg of segments) {
|
|
13
|
+
if (current === null || current === void 0) return void 0;
|
|
14
|
+
if (typeof seg === "number") {
|
|
15
|
+
if (!Array.isArray(current)) return void 0;
|
|
16
|
+
current = current[seg];
|
|
17
|
+
} else {
|
|
18
|
+
if (typeof current !== "object") return void 0;
|
|
19
|
+
current = current[seg];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
24
|
+
function setProjectedValue(data, path, value) {
|
|
25
|
+
const segments = parseProjectionPath(path);
|
|
26
|
+
return setAtPath(data, segments, 0, value);
|
|
27
|
+
}
|
|
28
|
+
function setAtPath(current, segments, index, value) {
|
|
29
|
+
if (index === segments.length) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
const seg = segments[index];
|
|
33
|
+
if (typeof seg === "number") {
|
|
34
|
+
const arr = Array.isArray(current) ? [...current] : [];
|
|
35
|
+
while (arr.length <= seg) {
|
|
36
|
+
arr.push(void 0);
|
|
37
|
+
}
|
|
38
|
+
arr[seg] = setAtPath(arr[seg], segments, index + 1, value);
|
|
39
|
+
return arr;
|
|
40
|
+
} else {
|
|
41
|
+
const obj = current !== null && current !== void 0 && typeof current === "object" && !Array.isArray(current) ? { ...current } : {};
|
|
42
|
+
obj[seg] = setAtPath(obj[seg], segments, index + 1, value);
|
|
43
|
+
return obj;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getProjectedSchema(schema, path) {
|
|
47
|
+
const segments = parseProjectionPath(path);
|
|
48
|
+
let current = schema;
|
|
49
|
+
for (const seg of segments) {
|
|
50
|
+
current = deref(current, schema);
|
|
51
|
+
if (!current) return {};
|
|
52
|
+
const navigate = (node) => {
|
|
53
|
+
if (typeof seg === "number") {
|
|
54
|
+
const items = node.items;
|
|
55
|
+
return items && typeof items === "object" ? items : void 0;
|
|
56
|
+
}
|
|
57
|
+
const properties = node.properties;
|
|
58
|
+
if (properties && properties[seg]) return properties[seg];
|
|
59
|
+
return void 0;
|
|
60
|
+
};
|
|
61
|
+
let next = navigate(current);
|
|
62
|
+
if (next === void 0) {
|
|
63
|
+
next = tryCombinatorBranches(current, schema, navigate);
|
|
64
|
+
}
|
|
65
|
+
if (!next) return {};
|
|
66
|
+
current = next;
|
|
67
|
+
}
|
|
68
|
+
const resolved = deref(current, schema);
|
|
69
|
+
return resolved ?? {};
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
getProjectedSchema,
|
|
73
|
+
getProjectedValue,
|
|
74
|
+
parseProjectionPath,
|
|
75
|
+
setProjectedValue
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection.js","sources":["../../src/core/projection.ts"],"sourcesContent":["import { deref as derefSchema, tryCombinatorBranches } from \"./refs\";\n\n/**\n * Projection utilities for navigating complex data structures\n * through a dot-separated path where numeric segments are array indices.\n *\n * Examples:\n * \"0\" → first element of an array\n * \"include\" → the `include` property of an object\n * \"0.video_rate_usd\" → nested property inside the first array element\n */\n\nexport type ProjectionSegment = string | number;\n\n/**\n * Parse a projection path string into typed segments.\n * Numeric strings become numbers (array indices), others stay as strings (object keys).\n */\nexport function parseProjectionPath(path: string): ProjectionSegment[] {\n if (!path) return [];\n return path.split(\".\").map((s) => {\n const n = Number(s);\n return Number.isInteger(n) && n >= 0 ? n : s;\n });\n}\n\n/**\n * Read a value from `data` by following the projection path.\n * Returns `undefined` if any segment along the path is missing.\n */\nexport function getProjectedValue(data: unknown, path: string): unknown {\n const segments = parseProjectionPath(path);\n let current: unknown = data;\n\n for (const seg of segments) {\n if (current === null || current === undefined) return undefined;\n\n if (typeof seg === \"number\") {\n if (!Array.isArray(current)) return undefined;\n current = current[seg];\n } else {\n if (typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n }\n\n return current;\n}\n\n/**\n * Immutably set a value at the projection path, preserving all sibling data.\n * Constructs missing intermediate structures (arrays for numeric segments, objects for string segments).\n */\nexport function setProjectedValue(\n data: unknown,\n path: string,\n value: unknown,\n): unknown {\n const segments = parseProjectionPath(path);\n return setAtPath(data, segments, 0, value);\n}\n\nfunction setAtPath(\n current: unknown,\n segments: ProjectionSegment[],\n index: number,\n value: unknown,\n): unknown {\n if (index === segments.length) {\n return value;\n }\n\n const seg = segments[index]!;\n\n if (typeof seg === \"number\") {\n // Array index — ensure we have an array\n const arr = Array.isArray(current) ? [...current] : [];\n // Pad array if index is out of bounds\n while (arr.length <= seg) {\n arr.push(undefined);\n }\n arr[seg] = setAtPath(arr[seg], segments, index + 1, value);\n return arr;\n } else {\n // Object key — ensure we have an object\n const obj: Record<string, unknown> =\n current !== null &&\n current !== undefined &&\n typeof current === \"object\" &&\n !Array.isArray(current)\n ? { ...(current as Record<string, unknown>) }\n : {};\n obj[seg] = setAtPath(obj[seg], segments, index + 1, value);\n return obj;\n }\n}\n\n/**\n * Resolve the schema at the projected path.\n * Numeric segments traverse into `items` (array item schema).\n * String segments traverse into `properties[segment]`.\n *\n * Dereferences `$ref` nodes transparently at every step, and falls through\n * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve\n * directly — picks the first branch that satisfies the navigation.\n */\nexport function getProjectedSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema: Record<string, any>,\n path: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> {\n const segments = parseProjectionPath(path);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: Record<string, any> | undefined = schema;\n\n for (const seg of segments) {\n current = derefSchema(current, schema);\n if (!current) return {};\n\n const navigate = (\n node: Record<string, unknown>,\n ): Record<string, unknown> | undefined => {\n if (typeof seg === \"number\") {\n const items = (node as { items?: unknown }).items;\n return items && typeof items === \"object\"\n ? (items as Record<string, unknown>)\n : undefined;\n }\n const properties = (node as { properties?: unknown }).properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (properties && properties[seg]) return properties[seg];\n return undefined;\n };\n\n let next = navigate(current);\n if (next === undefined) {\n next = tryCombinatorBranches(current, schema, navigate);\n }\n if (!next) return {};\n current = next;\n }\n\n const resolved = derefSchema(current, schema);\n return resolved ?? {};\n}\n"],"names":["derefSchema"],"mappings":";AAkBO,SAAS,oBAAoB,MAAmC;AACrE,MAAI,CAAC,KAAM,QAAO,CAAA;AAClB,SAAO,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM;AAChC,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO,OAAO,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI;AAAA,EAC7C,CAAC;AACH;AAMO,SAAS,kBAAkB,MAAe,MAAuB;AACtE,QAAM,WAAW,oBAAoB,IAAI;AACzC,MAAI,UAAmB;AAEvB,aAAW,OAAO,UAAU;AAC1B,QAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AAEtD,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,gBAAU,QAAQ,GAAG;AAAA,IACvB,OAAO;AACL,UAAI,OAAO,YAAY,SAAU,QAAO;AACxC,gBAAW,QAAoC,GAAG;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,kBACd,MACA,MACA,OACS;AACT,QAAM,WAAW,oBAAoB,IAAI;AACzC,SAAO,UAAU,MAAM,UAAU,GAAG,KAAK;AAC3C;AAEA,SAAS,UACP,SACA,UACA,OACA,OACS;AACT,MAAI,UAAU,SAAS,QAAQ;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,SAAS,KAAK;AAE1B,MAAI,OAAO,QAAQ,UAAU;AAE3B,UAAM,MAAM,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAA;AAEpD,WAAO,IAAI,UAAU,KAAK;AACxB,UAAI,KAAK,MAAS;AAAA,IACpB;AACA,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT,OAAO;AAEL,UAAM,MACJ,YAAY,QACZ,YAAY,UACZ,OAAO,YAAY,YACnB,CAAC,MAAM,QAAQ,OAAO,IAClB,EAAE,GAAI,QAAA,IACN,CAAA;AACN,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT;AACF;AAWO,SAAS,mBAEd,QACA,MAEqB;AACrB,QAAM,WAAW,oBAAoB,IAAI;AAEzC,MAAI,UAA2C;AAE/C,aAAW,OAAO,UAAU;AAC1B,cAAUA,MAAY,SAAS,MAAM;AACrC,QAAI,CAAC,QAAS,QAAO,CAAA;AAErB,UAAM,WAAW,CACf,SACwC;AACxC,UAAI,OAAO,QAAQ,UAAU;AAC3B,cAAM,QAAS,KAA6B;AAC5C,eAAO,SAAS,OAAO,UAAU,WAC5B,QACD;AAAA,MACN;AACA,YAAM,aAAc,KAAkC;AAGtD,UAAI,cAAc,WAAW,GAAG,EAAG,QAAO,WAAW,GAAG;AACxD,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,SAAS,OAAO;AAC3B,QAAI,SAAS,QAAW;AACtB,aAAO,sBAAsB,SAAS,QAAQ,QAAQ;AAAA,IACxD;AACA,QAAI,CAAC,KAAM,QAAO,CAAA;AAClB,cAAU;AAAA,EACZ;AAEA,QAAM,WAAWA,MAAY,SAAS,MAAM;AAC5C,SAAO,YAAY,CAAA;AACrB;"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema $ref resolution helpers.
|
|
3
|
+
*
|
|
4
|
+
* Supports the pointer grammar used across consumer schemas:
|
|
5
|
+
* - "#/$defs/Name"
|
|
6
|
+
* - "#/properties/foo/items"
|
|
7
|
+
*
|
|
8
|
+
* External refs (URIs, file refs) are intentionally out of scope. A ref that
|
|
9
|
+
* doesn't resolve leaves the node untouched — callers can still inspect the
|
|
10
|
+
* unresolved `$ref` for debugging.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a JSON pointer (`#/a/b/c`) against an object. Returns the node at
|
|
14
|
+
* that path, or `undefined` if any segment is missing or the pointer doesn't
|
|
15
|
+
* start with `#/`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolvePointer(obj: Record<string, unknown>, pointer: string): unknown;
|
|
18
|
+
/**
|
|
19
|
+
* Dereference a schema node along a chain of `$ref`s. Follows `A → B → C`
|
|
20
|
+
* transitively. A cycle (same `$ref` seen twice in one chain) returns the
|
|
21
|
+
* last unresolved node rather than hanging. An unresolvable pointer returns
|
|
22
|
+
* the current node unchanged.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveRef(property: Record<string, unknown>, root: Record<string, unknown>, seen?: Set<string>): Record<string, unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* Convenience wrapper around `resolveRef` that starts a fresh cycle-detection
|
|
27
|
+
* set. Intended for schema walkers that need to dereference at every step;
|
|
28
|
+
* each call is an independent resolution.
|
|
29
|
+
*/
|
|
30
|
+
export declare function deref(node: Record<string, any> | undefined, root: Record<string, any>): Record<string, any> | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Try to navigate a segment through a schema node's combinator branches
|
|
33
|
+
* (`oneOf` / `anyOf` / `allOf`) when direct navigation has failed.
|
|
34
|
+
*
|
|
35
|
+
* Semantics: for walker purposes (renderer-tester matching), we only need
|
|
36
|
+
* ONE concrete schema that satisfies the next navigation step. First-match
|
|
37
|
+
* by structural shape wins, same convention as `initOneOf` uses for seeding.
|
|
38
|
+
*
|
|
39
|
+
* @param node the schema node to search (already dereffed by caller)
|
|
40
|
+
* @param root the root schema, for dereferencing branch `$ref`s
|
|
41
|
+
* @param tryFn predicate that attempts navigation on a candidate branch
|
|
42
|
+
* and returns the navigated value, or `undefined` if the
|
|
43
|
+
* branch doesn't have what the caller's looking for
|
|
44
|
+
* @param depth recursion depth (capped at `COMBINATOR_DEPTH_LIMIT`)
|
|
45
|
+
*/
|
|
46
|
+
export declare function tryCombinatorBranches<T>(node: Record<string, any> | undefined, root: Record<string, any>, tryFn: (candidate: Record<string, any>) => T | undefined, depth?: number): T | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Recursively dereference every `$ref` in a schema subtree, producing a
|
|
49
|
+
* concrete schema with no remaining refs. Cycles along any single chain are
|
|
50
|
+
* handled by leaving the first recursion back into a seen ref as the
|
|
51
|
+
* unresolved node — matching `resolveRef`'s semantics.
|
|
52
|
+
*
|
|
53
|
+
* Intended for use at API boundaries (e.g. `resolveScopeSchema`'s return)
|
|
54
|
+
* so downstream walkers can operate on self-contained schemas without
|
|
55
|
+
* needing the original root.
|
|
56
|
+
*/
|
|
57
|
+
export declare function deepDeref(node: any, root: Record<string, any>, seen?: Set<string>): any;
|
|
58
|
+
//# sourceMappingURL=refs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/core/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,GACd,OAAO,CAYT;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAEnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAErC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAExB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAGjC;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAErC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAErC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAEzB,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,EACxD,KAAK,SAAI,GACR,CAAC,GAAG,SAAS,CAoBf;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAEvB,IAAI,EAAE,GAAG,EAET,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,IAAI,GAAE,GAAG,CAAC,MAAM,CAAa,GAE5B,GAAG,CAoBL"}
|