@narrative.io/jsonforms-provider-protocols 3.0.0-beta.20 → 3.0.0-beta.22

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.
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Materialize array indices targeted by `options.projection` controls in a
3
+ * UI schema, so that JSON Schema validators (AJV / cfworker) emit
4
+ * `items.required` errors on otherwise-empty arrays.
5
+ *
6
+ * Why this exists:
7
+ * `initFormDataFromSchema` omits optional fields, so an optional array
8
+ * like `data_rates: { type: 'array', items: { $ref: '#/$defs/DataRate' } }`
9
+ * produces `undefined` (or, historically, `[]`). Both shapes pass schema
10
+ * validation: `undefined` doesn't trigger `type: array`, and `[]` has no
11
+ * items to apply `items.required` to. As a result, a projection-targeted
12
+ * field rendered with `Video CPM rate *` lies — the asterisk says
13
+ * "required" but the validator never enforces it on an untouched form.
14
+ *
15
+ * This utility opts the consumer into per-item enforcement by walking the
16
+ * UI schema, finding every `Control` with `options.projection` that
17
+ * addresses an array index, and ensuring the corresponding data path has
18
+ * that index materialized as at least `{}`. With the empty object in
19
+ * place, the schema's `items.required` fires and the projected control's
20
+ * error string surfaces at form-validity time.
21
+ *
22
+ * Tradeoff:
23
+ * This re-introduces "noise" on untouched forms — required-field errors
24
+ * for fields the user hasn't seen yet. Use it when validation enforcement
25
+ * on projection targets matters more than a clean initial form state.
26
+ * The alternative is to declare these arrays as `required` + `minItems: 1`
27
+ * + `default: [{}]` at the schema level, which avoids needing this helper.
28
+ *
29
+ * Properties:
30
+ * - Idempotent: running twice yields the same result as running once.
31
+ * - Non-destructive: existing values at target paths are preserved.
32
+ * - Pure: returns a new object; does not mutate `data`.
33
+ *
34
+ * Example:
35
+ * ```ts
36
+ * const data = initFormDataFromSchema(schema);
37
+ * const seeded = seedProjectionTargets(data, uischema);
38
+ * // For uischema controls like
39
+ * // { type: 'Control', scope: '#/properties/data_rates',
40
+ * // options: { projection: '0.video_rate_usd' } }
41
+ * // seeded.data_rates is now `[{}]` (was `undefined` or `[]`).
42
+ * ```
43
+ */
44
+ export declare function seedProjectionTargets(data: unknown, uischema: UISchemaLike | UISchemaLike[] | undefined): unknown;
45
+ /**
46
+ * Minimal UI schema shape this utility traverses. Compatible with
47
+ * `@jsonforms/core`'s `UISchemaElement` but kept local to avoid a runtime
48
+ * dep on `@jsonforms/core` (it's a peer dep — types only).
49
+ */
50
+ export interface UISchemaLike {
51
+ type?: string;
52
+ scope?: string;
53
+ options?: {
54
+ projection?: string;
55
+ [key: string]: unknown;
56
+ };
57
+ elements?: UISchemaLike[];
58
+ [key: string]: unknown;
59
+ }
60
+ //# sourceMappingURL=seedProjectionTargets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seedProjectionTargets.d.ts","sourceRoot":"","sources":["../../src/core/seedProjectionTargets.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,SAAS,GAClD,OAAO,CA8BT;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
@@ -0,0 +1,50 @@
1
+ import { parseProjectionPath, getProjectedValue, setProjectedValue } from "./projection.js";
2
+ function seedProjectionTargets(data, uischema) {
3
+ if (!uischema) return data;
4
+ const controls = [];
5
+ collectProjectionControls(uischema, controls);
6
+ let result = data;
7
+ for (const { scope, projection } of controls) {
8
+ const dataPath = scopeToDataPath(scope);
9
+ const segments = parseProjectionPath(projection);
10
+ for (let i = 0; i < segments.length; i++) {
11
+ if (typeof segments[i] !== "number") continue;
12
+ const partial = segments.slice(0, i + 1).map((s) => String(s)).join(".");
13
+ const fullPath = dataPath ? `${dataPath}.${partial}` : partial;
14
+ if (getProjectedValue(result, fullPath) === void 0) {
15
+ result = setProjectedValue(result, fullPath, {});
16
+ }
17
+ }
18
+ }
19
+ return result;
20
+ }
21
+ function collectProjectionControls(ui, out) {
22
+ if (!ui) return;
23
+ if (Array.isArray(ui)) {
24
+ for (const el of ui) collectProjectionControls(el, out);
25
+ return;
26
+ }
27
+ if (typeof ui !== "object") return;
28
+ const projection = ui.options?.projection;
29
+ if (ui.type === "Control" && typeof ui.scope === "string" && typeof projection === "string") {
30
+ out.push({ scope: ui.scope, projection });
31
+ }
32
+ if (Array.isArray(ui.elements)) {
33
+ for (const el of ui.elements) collectProjectionControls(el, out);
34
+ }
35
+ }
36
+ function scopeToDataPath(scope) {
37
+ if (!scope.startsWith("#/")) return "";
38
+ const parts = scope.slice(2).split("/");
39
+ const out = [];
40
+ for (let i = 0; i < parts.length; i++) {
41
+ if (parts[i] === "properties" && i + 1 < parts.length) {
42
+ out.push(parts[++i]);
43
+ }
44
+ }
45
+ return out.join(".");
46
+ }
47
+ export {
48
+ seedProjectionTargets
49
+ };
50
+ //# sourceMappingURL=seedProjectionTargets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seedProjectionTargets.js","sources":["../../src/core/seedProjectionTargets.ts"],"sourcesContent":["import {\n getProjectedValue,\n parseProjectionPath,\n setProjectedValue,\n} from \"./projection\";\n\n/**\n * Materialize array indices targeted by `options.projection` controls in a\n * UI schema, so that JSON Schema validators (AJV / cfworker) emit\n * `items.required` errors on otherwise-empty arrays.\n *\n * Why this exists:\n * `initFormDataFromSchema` omits optional fields, so an optional array\n * like `data_rates: { type: 'array', items: { $ref: '#/$defs/DataRate' } }`\n * produces `undefined` (or, historically, `[]`). Both shapes pass schema\n * validation: `undefined` doesn't trigger `type: array`, and `[]` has no\n * items to apply `items.required` to. As a result, a projection-targeted\n * field rendered with `Video CPM rate *` lies — the asterisk says\n * \"required\" but the validator never enforces it on an untouched form.\n *\n * This utility opts the consumer into per-item enforcement by walking the\n * UI schema, finding every `Control` with `options.projection` that\n * addresses an array index, and ensuring the corresponding data path has\n * that index materialized as at least `{}`. With the empty object in\n * place, the schema's `items.required` fires and the projected control's\n * error string surfaces at form-validity time.\n *\n * Tradeoff:\n * This re-introduces \"noise\" on untouched forms — required-field errors\n * for fields the user hasn't seen yet. Use it when validation enforcement\n * on projection targets matters more than a clean initial form state.\n * The alternative is to declare these arrays as `required` + `minItems: 1`\n * + `default: [{}]` at the schema level, which avoids needing this helper.\n *\n * Properties:\n * - Idempotent: running twice yields the same result as running once.\n * - Non-destructive: existing values at target paths are preserved.\n * - Pure: returns a new object; does not mutate `data`.\n *\n * Example:\n * ```ts\n * const data = initFormDataFromSchema(schema);\n * const seeded = seedProjectionTargets(data, uischema);\n * // For uischema controls like\n * // { type: 'Control', scope: '#/properties/data_rates',\n * // options: { projection: '0.video_rate_usd' } }\n * // seeded.data_rates is now `[{}]` (was `undefined` or `[]`).\n * ```\n */\nexport function seedProjectionTargets(\n data: unknown,\n uischema: UISchemaLike | UISchemaLike[] | undefined,\n): unknown {\n if (!uischema) return data;\n\n const controls: { scope: string; projection: string }[] = [];\n collectProjectionControls(uischema, controls);\n\n let result = data;\n for (const { scope, projection } of controls) {\n const dataPath = scopeToDataPath(scope);\n const segments = parseProjectionPath(projection);\n\n // Seed each numeric prefix in the projection path. For\n // `projection: '0.bars.0.name'`, that means seeding both\n // `<dataPath>.0` and `<dataPath>.0.bars.0`.\n for (let i = 0; i < segments.length; i++) {\n if (typeof segments[i] !== \"number\") continue;\n\n const partial = segments\n .slice(0, i + 1)\n .map((s) => String(s))\n .join(\".\");\n const fullPath = dataPath ? `${dataPath}.${partial}` : partial;\n\n if (getProjectedValue(result, fullPath) === undefined) {\n result = setProjectedValue(result, fullPath, {});\n }\n }\n }\n\n return result;\n}\n\n/**\n * Minimal UI schema shape this utility traverses. Compatible with\n * `@jsonforms/core`'s `UISchemaElement` but kept local to avoid a runtime\n * dep on `@jsonforms/core` (it's a peer dep — types only).\n */\nexport interface UISchemaLike {\n type?: string;\n scope?: string;\n options?: { projection?: string; [key: string]: unknown };\n elements?: UISchemaLike[];\n [key: string]: unknown;\n}\n\nfunction collectProjectionControls(\n ui: UISchemaLike | UISchemaLike[] | undefined,\n out: { scope: string; projection: string }[],\n): void {\n if (!ui) return;\n if (Array.isArray(ui)) {\n for (const el of ui) collectProjectionControls(el, out);\n return;\n }\n if (typeof ui !== \"object\") return;\n\n const projection = ui.options?.projection;\n if (\n ui.type === \"Control\" &&\n typeof ui.scope === \"string\" &&\n typeof projection === \"string\"\n ) {\n out.push({ scope: ui.scope, projection });\n }\n\n if (Array.isArray(ui.elements)) {\n for (const el of ui.elements) collectProjectionControls(el, out);\n }\n}\n\n/**\n * Convert a JsonForms scope pointer (`#/properties/foo/properties/bar`) to\n * a dot-separated data path (`foo.bar`). Drops `items` segments — array\n * indices are added at runtime via the projection path, not the scope.\n */\nfunction scopeToDataPath(scope: string): string {\n if (!scope.startsWith(\"#/\")) return \"\";\n const parts = scope.slice(2).split(\"/\");\n const out: string[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (parts[i] === \"properties\" && i + 1 < parts.length) {\n out.push(parts[++i]!);\n }\n }\n return out.join(\".\");\n}\n"],"names":[],"mappings":";AAiDO,SAAS,sBACd,MACA,UACS;AACT,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,WAAoD,CAAA;AAC1D,4BAA0B,UAAU,QAAQ;AAE5C,MAAI,SAAS;AACb,aAAW,EAAE,OAAO,WAAA,KAAgB,UAAU;AAC5C,UAAM,WAAW,gBAAgB,KAAK;AACtC,UAAM,WAAW,oBAAoB,UAAU;AAK/C,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAI,OAAO,SAAS,CAAC,MAAM,SAAU;AAErC,YAAM,UAAU,SACb,MAAM,GAAG,IAAI,CAAC,EACd,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,EACpB,KAAK,GAAG;AACX,YAAM,WAAW,WAAW,GAAG,QAAQ,IAAI,OAAO,KAAK;AAEvD,UAAI,kBAAkB,QAAQ,QAAQ,MAAM,QAAW;AACrD,iBAAS,kBAAkB,QAAQ,UAAU,CAAA,CAAE;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAeA,SAAS,0BACP,IACA,KACM;AACN,MAAI,CAAC,GAAI;AACT,MAAI,MAAM,QAAQ,EAAE,GAAG;AACrB,eAAW,MAAM,GAAI,2BAA0B,IAAI,GAAG;AACtD;AAAA,EACF;AACA,MAAI,OAAO,OAAO,SAAU;AAE5B,QAAM,aAAa,GAAG,SAAS;AAC/B,MACE,GAAG,SAAS,aACZ,OAAO,GAAG,UAAU,YACpB,OAAO,eAAe,UACtB;AACA,QAAI,KAAK,EAAE,OAAO,GAAG,OAAO,YAAY;AAAA,EAC1C;AAEA,MAAI,MAAM,QAAQ,GAAG,QAAQ,GAAG;AAC9B,eAAW,MAAM,GAAG,SAAU,2BAA0B,IAAI,GAAG;AAAA,EACjE;AACF;AAOA,SAAS,gBAAgB,OAAuB;AAC9C,MAAI,CAAC,MAAM,WAAW,IAAI,EAAG,QAAO;AACpC,QAAM,QAAQ,MAAM,MAAM,CAAC,EAAE,MAAM,GAAG;AACtC,QAAM,MAAgB,CAAA;AACtB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,MAAM,CAAC,MAAM,gBAAgB,IAAI,IAAI,MAAM,QAAQ;AACrD,UAAI,KAAK,MAAM,EAAE,CAAC,CAAE;AAAA,IACtB;AAAA,EACF;AACA,SAAO,IAAI,KAAK,GAAG;AACrB;"}
package/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ export * from "./core/projection";
9
9
  export * from "./core/resolveScope";
10
10
  export * from "./core/types";
11
11
  export { initFormDataFromSchema } from "./core/initFormData";
12
+ export { seedProjectionTargets } from "./core/seedProjectionTargets";
13
+ export type { UISchemaLike } from "./core/seedProjectionTargets";
12
14
  export { RestApiProtocol } from "./protocols/rest_api";
13
15
  export { createNoEvalAjv, transformUnit, transformErrors, } from "./no-eval-ajv";
14
16
  export type { NoEvalAjv, NoEvalErrorObject, NoEvalValidateFunction, CreateNoEvalAjvOptions, } from "./no-eval-ajv";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAG7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AAEpC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EACL,eAAe,EACf,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,MAAM,EACN,UAAU,EACV,QAAQ,EACR,MAAM,EACN,WAAW,EACX,SAAS,GACV,MAAM,OAAO,CAAC;AACf,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;;iBAGc,GAAG,SAAS,cAAc;;AADzC,wBAYE"}
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { applyTransformPipeline } from "./core/transforms.js";
6
6
  import { getProjectedSchema, getProjectedValue, parseProjectionPath, setProjectedValue } from "./core/projection.js";
7
7
  import { resolveScopeSchema } from "./core/resolveScope.js";
8
8
  import { initFormDataFromSchema } from "./core/initFormData.js";
9
+ import { seedProjectionTargets } from "./core/seedProjectionTargets.js";
9
10
  import { RestApiProtocol } from "./protocols/rest_api.js";
10
11
  import { createNoEvalAjv, transformErrors, transformUnit } from "./no-eval-ajv.js";
11
12
  import { providerRenderers } from "./vue/index.js";
@@ -61,6 +62,7 @@ export {
61
62
  renderObj,
62
63
  renderTpl,
63
64
  resolveScopeSchema,
65
+ seedProjectionTargets,
64
66
  setProjectedValue,
65
67
  transformErrors,
66
68
  transformUnit,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\nexport * from \"./core/transforms\";\nexport * from \"./core/projection\";\nexport * from \"./core/resolveScope\";\n// Core exports\nexport * from \"./core/types\";\nexport { initFormDataFromSchema } from \"./core/initFormData\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// CSP-safe validator\nexport {\n createNoEvalAjv,\n transformUnit,\n transformErrors,\n} from \"./no-eval-ajv\";\nexport type {\n NoEvalAjv,\n NoEvalErrorObject,\n NoEvalValidateFunction,\n CreateNoEvalAjvOptions,\n} from \"./no-eval-ajv\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n useProvider,\n createDataLayer,\n useDataLayer,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\nexport type { DataLayer } from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAA,QAAe;AAAA,EACb,QAAQ,KAAU,MAAuB;AACvC,UAAM,MAAMA;AACZ,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,KAAK,WAAW;AAC9B,YAAI,SAAS,CAAC;AAAA,MAChB;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB,GAAG;AACnC,QAAI,QAAQ,iBAAiBC,KAAW;AACxC,QAAI,QAAQ,gBAAgB,MAAM,QAAQ,CAAA,CAAE;AAAA,EAC9C;AACF;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { App } from \"vue\";\nimport { cache as globalCache } from \"./core/cache\";\nimport { registry as globalRegistry } from \"./core/registry\";\nimport type { Protocol, AuthConfig } from \"./core/types\";\n\nexport { cache } from \"./core/cache\";\nexport * from \"./core/jsonpath\";\nexport { registry } from \"./core/registry\";\nexport * from \"./core/templating\";\nexport * from \"./core/transforms\";\nexport * from \"./core/projection\";\nexport * from \"./core/resolveScope\";\n// Core exports\nexport * from \"./core/types\";\nexport { initFormDataFromSchema } from \"./core/initFormData\";\nexport { seedProjectionTargets } from \"./core/seedProjectionTargets\";\nexport type { UISchemaLike } from \"./core/seedProjectionTargets\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\n\n// CSP-safe validator\nexport {\n createNoEvalAjv,\n transformUnit,\n transformErrors,\n} from \"./no-eval-ajv\";\nexport type {\n NoEvalAjv,\n NoEvalErrorObject,\n NoEvalValidateFunction,\n CreateNoEvalAjvOptions,\n} from \"./no-eval-ajv\";\n\n// Vue exports - using named imports to avoid potential bundling issues\nexport {\n providerRenderers,\n primevueRenderers,\n ProviderAutocomplete,\n ProviderSelect,\n ProviderMultiSelect,\n useProvider,\n createDataLayer,\n useDataLayer,\n JfText,\n JfTextArea,\n JfNumber,\n JfEnum,\n JfEnumArray,\n JfBoolean,\n} from \"./vue\";\nexport type { DataLayer } from \"./vue\";\n\nexport interface ProviderConfig {\n protocols?: Protocol[];\n auth?: AuthConfig;\n}\n\nexport default {\n install(app: App, opts?: ProviderConfig) {\n const reg = globalRegistry;\n if (opts?.protocols) {\n for (const p of opts.protocols) {\n reg.register(p);\n }\n }\n app.provide(\"providerRegistry\", reg);\n app.provide(\"providerCache\", globalCache);\n app.provide(\"providerAuth\", opts?.auth ?? {});\n },\n};\n"],"names":["globalRegistry","globalCache"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0DA,MAAA,QAAe;AAAA,EACb,QAAQ,KAAU,MAAuB;AACvC,UAAM,MAAMA;AACZ,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,KAAK,WAAW;AAC9B,YAAI,SAAS,CAAC;AAAA,MAChB;AAAA,IACF;AACA,QAAI,QAAQ,oBAAoB,GAAG;AACnC,QAAI,QAAQ,iBAAiBC,KAAW;AACxC,QAAI,QAAQ,gBAAgB,MAAM,QAAQ,CAAA,CAAE;AAAA,EAC9C;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "3.0.0-beta.20",
3
+ "version": "3.0.0-beta.22",
4
4
  "description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
5
5
  "type": "module",
6
6
  "author": "Narrative I/O",
@@ -0,0 +1,138 @@
1
+ import {
2
+ getProjectedValue,
3
+ parseProjectionPath,
4
+ setProjectedValue,
5
+ } from "./projection";
6
+
7
+ /**
8
+ * Materialize array indices targeted by `options.projection` controls in a
9
+ * UI schema, so that JSON Schema validators (AJV / cfworker) emit
10
+ * `items.required` errors on otherwise-empty arrays.
11
+ *
12
+ * Why this exists:
13
+ * `initFormDataFromSchema` omits optional fields, so an optional array
14
+ * like `data_rates: { type: 'array', items: { $ref: '#/$defs/DataRate' } }`
15
+ * produces `undefined` (or, historically, `[]`). Both shapes pass schema
16
+ * validation: `undefined` doesn't trigger `type: array`, and `[]` has no
17
+ * items to apply `items.required` to. As a result, a projection-targeted
18
+ * field rendered with `Video CPM rate *` lies — the asterisk says
19
+ * "required" but the validator never enforces it on an untouched form.
20
+ *
21
+ * This utility opts the consumer into per-item enforcement by walking the
22
+ * UI schema, finding every `Control` with `options.projection` that
23
+ * addresses an array index, and ensuring the corresponding data path has
24
+ * that index materialized as at least `{}`. With the empty object in
25
+ * place, the schema's `items.required` fires and the projected control's
26
+ * error string surfaces at form-validity time.
27
+ *
28
+ * Tradeoff:
29
+ * This re-introduces "noise" on untouched forms — required-field errors
30
+ * for fields the user hasn't seen yet. Use it when validation enforcement
31
+ * on projection targets matters more than a clean initial form state.
32
+ * The alternative is to declare these arrays as `required` + `minItems: 1`
33
+ * + `default: [{}]` at the schema level, which avoids needing this helper.
34
+ *
35
+ * Properties:
36
+ * - Idempotent: running twice yields the same result as running once.
37
+ * - Non-destructive: existing values at target paths are preserved.
38
+ * - Pure: returns a new object; does not mutate `data`.
39
+ *
40
+ * Example:
41
+ * ```ts
42
+ * const data = initFormDataFromSchema(schema);
43
+ * const seeded = seedProjectionTargets(data, uischema);
44
+ * // For uischema controls like
45
+ * // { type: 'Control', scope: '#/properties/data_rates',
46
+ * // options: { projection: '0.video_rate_usd' } }
47
+ * // seeded.data_rates is now `[{}]` (was `undefined` or `[]`).
48
+ * ```
49
+ */
50
+ export function seedProjectionTargets(
51
+ data: unknown,
52
+ uischema: UISchemaLike | UISchemaLike[] | undefined,
53
+ ): unknown {
54
+ if (!uischema) return data;
55
+
56
+ const controls: { scope: string; projection: string }[] = [];
57
+ collectProjectionControls(uischema, controls);
58
+
59
+ let result = data;
60
+ for (const { scope, projection } of controls) {
61
+ const dataPath = scopeToDataPath(scope);
62
+ const segments = parseProjectionPath(projection);
63
+
64
+ // Seed each numeric prefix in the projection path. For
65
+ // `projection: '0.bars.0.name'`, that means seeding both
66
+ // `<dataPath>.0` and `<dataPath>.0.bars.0`.
67
+ for (let i = 0; i < segments.length; i++) {
68
+ if (typeof segments[i] !== "number") continue;
69
+
70
+ const partial = segments
71
+ .slice(0, i + 1)
72
+ .map((s) => String(s))
73
+ .join(".");
74
+ const fullPath = dataPath ? `${dataPath}.${partial}` : partial;
75
+
76
+ if (getProjectedValue(result, fullPath) === undefined) {
77
+ result = setProjectedValue(result, fullPath, {});
78
+ }
79
+ }
80
+ }
81
+
82
+ return result;
83
+ }
84
+
85
+ /**
86
+ * Minimal UI schema shape this utility traverses. Compatible with
87
+ * `@jsonforms/core`'s `UISchemaElement` but kept local to avoid a runtime
88
+ * dep on `@jsonforms/core` (it's a peer dep — types only).
89
+ */
90
+ export interface UISchemaLike {
91
+ type?: string;
92
+ scope?: string;
93
+ options?: { projection?: string; [key: string]: unknown };
94
+ elements?: UISchemaLike[];
95
+ [key: string]: unknown;
96
+ }
97
+
98
+ function collectProjectionControls(
99
+ ui: UISchemaLike | UISchemaLike[] | undefined,
100
+ out: { scope: string; projection: string }[],
101
+ ): void {
102
+ if (!ui) return;
103
+ if (Array.isArray(ui)) {
104
+ for (const el of ui) collectProjectionControls(el, out);
105
+ return;
106
+ }
107
+ if (typeof ui !== "object") return;
108
+
109
+ const projection = ui.options?.projection;
110
+ if (
111
+ ui.type === "Control" &&
112
+ typeof ui.scope === "string" &&
113
+ typeof projection === "string"
114
+ ) {
115
+ out.push({ scope: ui.scope, projection });
116
+ }
117
+
118
+ if (Array.isArray(ui.elements)) {
119
+ for (const el of ui.elements) collectProjectionControls(el, out);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Convert a JsonForms scope pointer (`#/properties/foo/properties/bar`) to
125
+ * a dot-separated data path (`foo.bar`). Drops `items` segments — array
126
+ * indices are added at runtime via the projection path, not the scope.
127
+ */
128
+ function scopeToDataPath(scope: string): string {
129
+ if (!scope.startsWith("#/")) return "";
130
+ const parts = scope.slice(2).split("/");
131
+ const out: string[] = [];
132
+ for (let i = 0; i < parts.length; i++) {
133
+ if (parts[i] === "properties" && i + 1 < parts.length) {
134
+ out.push(parts[++i]!);
135
+ }
136
+ }
137
+ return out.join(".");
138
+ }
package/src/index.ts CHANGED
@@ -13,6 +13,8 @@ export * from "./core/resolveScope";
13
13
  // Core exports
14
14
  export * from "./core/types";
15
15
  export { initFormDataFromSchema } from "./core/initFormData";
16
+ export { seedProjectionTargets } from "./core/seedProjectionTargets";
17
+ export type { UISchemaLike } from "./core/seedProjectionTargets";
16
18
 
17
19
  // Protocol exports
18
20
  export { RestApiProtocol } from "./protocols/rest_api";