@narrative.io/jsonforms-provider-protocols 3.0.0-beta.6 → 3.0.0-beta.8

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,10 @@
1
+ /**
2
+ * Initialize a form data object from a JSON Schema.
3
+ * Resolves $ref, const, default, oneOf/discriminator, and typed empty values.
4
+ *
5
+ * @param schema - The full JSON Schema (must include $defs if $refs are used)
6
+ * @param seed - Optional existing data to merge (seed values take priority)
7
+ * @returns A data object with all schema-defined fields initialized
8
+ */
9
+ export declare function initFormDataFromSchema(schema: Record<string, unknown>, seed?: Record<string, unknown>): Record<string, unknown>;
10
+ //# sourceMappingURL=initFormData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initFormData.d.ts","sourceRoot":"","sources":["../../src/core/initFormData.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;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,CAyBzB"}
@@ -0,0 +1,99 @@
1
+ function initFormDataFromSchema(schema, seed) {
2
+ const result = initProperty(schema, schema, seed);
3
+ if (result && typeof result === "object" && !Array.isArray(result) && seed && typeof seed === "object") {
4
+ const schemaKeys = new Set(
5
+ Object.keys(
6
+ resolveRef(schema, schema)?.properties ?? {}
7
+ )
8
+ );
9
+ for (const key of Object.keys(seed)) {
10
+ if (!schemaKeys.has(key) && !(key in result)) {
11
+ result[key] = seed[key];
12
+ }
13
+ }
14
+ }
15
+ return result ?? {};
16
+ }
17
+ function resolveRef(property, root, seen) {
18
+ if (!property || typeof property !== "object") return property;
19
+ const ref = property.$ref;
20
+ if (!ref) return property;
21
+ const visited = seen ?? /* @__PURE__ */ new Set();
22
+ if (visited.has(ref)) return property;
23
+ visited.add(ref);
24
+ const resolved = resolvePointer(root, ref);
25
+ if (!resolved) return property;
26
+ return resolveRef(resolved, root, visited);
27
+ }
28
+ function resolvePointer(obj, pointer) {
29
+ if (!pointer.startsWith("#/")) return void 0;
30
+ const parts = pointer.slice(2).split("/");
31
+ let current = obj;
32
+ for (const part of parts) {
33
+ if (current && typeof current === "object" && part in current) {
34
+ current = current[part];
35
+ } else {
36
+ return void 0;
37
+ }
38
+ }
39
+ return current;
40
+ }
41
+ function initProperty(property, root, seed) {
42
+ if (!property || typeof property !== "object") return null;
43
+ const resolved = resolveRef(property, root);
44
+ if (Array.isArray(resolved.oneOf)) {
45
+ return initOneOf(resolved, root, seed);
46
+ }
47
+ const type = resolved.type;
48
+ if (seed !== void 0 && seed !== null && type !== "object") {
49
+ return seed;
50
+ }
51
+ if ("const" in resolved) {
52
+ return resolved.const;
53
+ }
54
+ if (Array.isArray(resolved.enum) && resolved.enum.length === 1) {
55
+ return resolved.enum[0];
56
+ }
57
+ if ("default" in resolved) {
58
+ return resolved.default;
59
+ }
60
+ switch (type) {
61
+ case "object":
62
+ return initObject(resolved, root, seed);
63
+ case "array":
64
+ return seed !== void 0 && seed !== null ? seed : [];
65
+ case "string":
66
+ return "";
67
+ case "boolean":
68
+ return false;
69
+ case "number":
70
+ case "integer":
71
+ return null;
72
+ default:
73
+ return null;
74
+ }
75
+ }
76
+ function initObject(schema, root, seed) {
77
+ const properties = schema.properties;
78
+ if (!properties) {
79
+ return seed && typeof seed === "object" ? { ...seed } : {};
80
+ }
81
+ const result = {};
82
+ for (const [key, propSchema] of Object.entries(properties)) {
83
+ const seedValue = seed && typeof seed === "object" ? seed[key] : void 0;
84
+ result[key] = initProperty(propSchema, root, seedValue);
85
+ }
86
+ return result;
87
+ }
88
+ function initOneOf(schema, root, seed) {
89
+ const variants = schema.oneOf;
90
+ if (!variants || variants.length === 0) return null;
91
+ const first = variants[0];
92
+ if (!first) return null;
93
+ const firstVariant = resolveRef(first, root);
94
+ return initProperty(firstVariant, root, seed);
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 * @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 all 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) as Record<string, unknown>;\n\n // If result is an object and seed has extra keys not in schema, preserve them\n if (\n result &&\n typeof result === \"object\" &&\n !Array.isArray(result) &&\n seed &&\n typeof seed === \"object\"\n ) {\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 result)) {\n result[key] = seed[key];\n }\n }\n }\n\n return result ?? {};\n}\n\n/**\n * Resolve a $ref pointer against the root schema's $defs.\n * Supports nested $ref chains.\n */\nfunction resolveRef(\n property: Record<string, unknown>,\n root: Record<string, unknown>,\n seen?: Set<string>,\n): Record<string, unknown> {\n if (!property || typeof property !== \"object\") return property;\n\n const ref = property.$ref as string | undefined;\n if (!ref) return property;\n\n // Guard against circular refs\n const visited = seen ?? new Set<string>();\n if (visited.has(ref)) return property;\n visited.add(ref);\n\n const resolved = resolvePointer(root, ref);\n if (!resolved) return property;\n\n // Continue resolving if the result itself has a $ref\n return resolveRef(resolved as Record<string, unknown>, root, visited);\n}\n\n/**\n * Resolve a JSON pointer like \"#/$defs/Price\" against an object.\n */\nfunction resolvePointer(\n obj: Record<string, unknown>,\n pointer: string,\n): unknown {\n if (!pointer.startsWith(\"#/\")) return undefined;\n const parts = pointer.slice(2).split(\"/\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current && typeof current === \"object\" && part in current) {\n current = (current as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n return current;\n}\n\n/**\n * Initialize a single property value based on its schema definition.\n */\nfunction initProperty(\n property: Record<string, unknown>,\n root: Record<string, unknown>,\n seed?: unknown,\n): unknown {\n if (!property || typeof property !== \"object\") return null;\n\n // Resolve $ref first\n const resolved = resolveRef(property, root);\n\n // Handle oneOf with discriminator — pick first variant\n if (Array.isArray(resolved.oneOf)) {\n return initOneOf(resolved, root, seed);\n }\n\n // Priority 1: seed wins (for object types, we merge recursively below)\n // For non-object types, return seed directly if present\n const type = resolved.type as string | undefined;\n if (seed !== undefined && seed !== null && type !== \"object\") {\n return seed;\n }\n\n // Priority 2: const\n if (\"const\" in resolved) {\n return resolved.const;\n }\n\n // Priority 3: single-value enum\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\n if (\"default\" in resolved) {\n return resolved.default;\n }\n\n // Priority 5: typed empty values\n switch (type) {\n case \"object\":\n return initObject(resolved, root, seed as Record<string, unknown>);\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 return null;\n default:\n return null;\n }\n}\n\n/**\n * Initialize an object type by recursing into its properties.\n */\nfunction initObject(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed?: Record<string, unknown>,\n): Record<string, unknown> {\n const properties = schema.properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (!properties) {\n // Object with no defined properties — return seed or empty object\n return seed && typeof seed === \"object\" ? { ...seed } : {};\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 result[key] = initProperty(propSchema, root, seedValue);\n }\n\n return result;\n}\n\n/**\n * Handle oneOf schemas — pick the first variant and initialize it.\n */\nfunction initOneOf(\n schema: Record<string, unknown>,\n root: Record<string, unknown>,\n seed?: unknown,\n): unknown {\n const variants = schema.oneOf as Record<string, unknown>[];\n if (!variants || variants.length === 0) return null;\n\n const first = variants[0];\n if (!first) return null;\n\n // Pick first variant, resolve its $ref if needed\n const firstVariant = resolveRef(first, root);\n return initProperty(firstVariant, root, seed);\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from "./core/transforms";
8
8
  export * from "./core/projection";
9
9
  export * from "./core/resolveScope";
10
10
  export * from "./core/types";
11
+ export { initFormDataFromSchema } from "./core/initFormData";
11
12
  export { RestApiProtocol } from "./protocols/rest_api";
12
13
  export { providerRenderers, primevueRenderers, ProviderAutocomplete, ProviderSelect, ProviderMultiSelect, useProvider, createDataLayer, useDataLayer, JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean, } from "./vue";
13
14
  export type { DataLayer } from "./vue";
@@ -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;AAIzD,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;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,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;AAIzD,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,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
@@ -5,6 +5,7 @@ import { renderObj, renderTpl } from "./core/templating.js";
5
5
  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
+ import { initFormDataFromSchema } from "./core/initFormData.js";
8
9
  import { RestApiProtocol } from "./protocols/rest_api.js";
9
10
  import { providerRenderers } from "./vue/index.js";
10
11
  import { default as default2 } from "./vue/components/ProviderAutocomplete.vue.js";
@@ -50,6 +51,7 @@ export {
50
51
  index as default,
51
52
  getProjectedSchema,
52
53
  getProjectedValue,
54
+ initFormDataFromSchema,
53
55
  jp,
54
56
  parseProjectionPath,
55
57
  primevueRenderers,
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\nconsole.log(\"[jsonforms-provider-protocols] loaded v2.10.0 (symlink)\");\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\";\n\n// Protocol exports\nexport { RestApiProtocol } from \"./protocols/rest_api\";\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":";;;;;;;;;;;;;;;;;;;;;AAKA,QAAQ,IAAI,yDAAyD;AAuCrE,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\nconsole.log(\"[jsonforms-provider-protocols] loaded v2.10.0 (symlink)\");\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// 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":";;;;;;;;;;;;;;;;;;;;;;AAKA,QAAQ,IAAI,yDAAyD;AAwCrE,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 +1 @@
1
- {"version":3,"file":"useDerive.d.ts","sourceRoot":"","sources":["../../../src/vue/composables/useDerive.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,GAAG,EACR,KAAK,WAAW,EACjB,MAAM,KAAK,CAAC;AACb,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,UAAU,aAAa;IACrB,OAAO,EAAE,GAAG,CAAC;QACX,QAAQ,EAAE,cAAc,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;KACf,CAAC,CAAC;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD;8EAC0E;IAC1E,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;CAC5C;AAED,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,YAAY,EACZ,IAAI,EAAE,YAAY,GACnB,EAAE,aAAa,QAmDf"}
1
+ {"version":3,"file":"useDerive.d.ts","sourceRoot":"","sources":["../../../src/vue/composables/useDerive.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,GAAG,EACR,KAAK,WAAW,EACjB,MAAM,KAAK,CAAC;AACb,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,UAAU,aAAa;IACrB,OAAO,EAAE,GAAG,CAAC;QACX,QAAQ,EAAE,cAAc,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;KACf,CAAC,CAAC;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD;8EAC0E;IAC1E,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;CAC5C;AAED,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,YAAY,EACZ,IAAI,EAAE,YAAY,GACnB,EAAE,aAAa,QA4Df"}
@@ -1,4 +1,4 @@
1
- import { inject, computed, watch, unref } from "vue";
1
+ import { inject, computed, ref, watch, unref } from "vue";
2
2
  import "@jsonforms/core";
3
3
  import { useDataLayer } from "./useDataLayer.js";
4
4
  function useDerive({
@@ -20,6 +20,7 @@ function useDerive({
20
20
  // 'follow' = auto-update, 'manual' = user controlled
21
21
  };
22
22
  });
23
+ const lastDerivedValue = ref(void 0);
23
24
  watch(
24
25
  [rootData, dataLayerData, deriveConfig],
25
26
  ([data, extData, config]) => {
@@ -32,6 +33,8 @@ function useDerive({
32
33
  data,
33
34
  extData
34
35
  );
36
+ if (derivedValue === lastDerivedValue.value) return;
37
+ lastDerivedValue.value = derivedValue;
35
38
  const compareData = dataOverride ? unref(dataOverride) : control.value.data;
36
39
  if (derivedValue !== compareData) {
37
40
  handleChange(control.value.path, derivedValue);
@@ -1 +1 @@
1
- {"version":3,"file":"useDerive.js","sources":["../../../src/vue/composables/useDerive.ts"],"sourcesContent":["import {\n computed,\n watch,\n unref,\n inject,\n type Ref,\n type ComputedRef,\n} from \"vue\";\nimport { type ControlElement } from \"@jsonforms/core\";\nimport { useDataLayer } from \"./useDataLayer\";\n\ninterface DeriveOptions {\n control: Ref<{\n uischema: ControlElement;\n path: string;\n data: unknown;\n }>;\n handleChange: (path: string, value: unknown) => void;\n /** When projection is active, pass projectedData so the comparison\n * matches the projected (unwrapped) value rather than raw scope data. */\n data?: Ref<unknown> | ComputedRef<unknown>;\n}\n\nexport function useDerive({\n control,\n handleChange,\n data: dataOverride,\n}: DeriveOptions) {\n // Get the root form data from JSONForms context\n const injectedFormData = inject<{ value: unknown }>(\"formData\", {\n value: {},\n });\n const rootData = computed(() => injectedFormData.value || {});\n\n // Get data from the dataLayer\n const dataLayerState = useDataLayer();\n const dataLayerData = computed(() => dataLayerState.value || {});\n\n // Extract derive configuration from uischema options\n const deriveConfig = computed(() => {\n const options = control.value.uischema?.options as\n | { derive?: string; mode?: string }\n | undefined;\n return {\n expression: options?.derive,\n mode: options?.mode || \"follow\", // 'follow' = auto-update, 'manual' = user controlled\n };\n });\n\n // Watch for changes in form data and dataLayer and update derived field\n watch(\n [rootData, dataLayerData, deriveConfig],\n ([data, extData, config]) => {\n if (!config.expression || config.mode !== \"follow\") {\n return;\n }\n\n try {\n const derivedValue = resolveDeriveExpression(\n config.expression,\n data,\n extData,\n );\n const compareData = dataOverride\n ? unref(dataOverride)\n : control.value.data;\n if (derivedValue !== compareData) {\n handleChange(control.value.path, derivedValue);\n }\n } catch (error) {\n console.warn(\n `Failed to derive value for ${control.value.path}:`,\n error,\n );\n }\n },\n { deep: true, immediate: true },\n );\n}\n\nfunction resolveDeriveExpression(\n expression: string,\n data: unknown,\n dataLayerData?: unknown,\n): unknown {\n // Handle dataLayer() syntax\n if (expression.startsWith(\"dataLayer(\") && expression.endsWith(\")\")) {\n const propertyPath = expression.slice(10, -1); // Remove \"dataLayer(\" and \")\"\n return resolvePropertyPath(propertyPath, dataLayerData);\n }\n\n // Handle simple property paths like \"country.name\"\n if (\n !expression.includes(\"(\") &&\n !expression.includes(\"+\") &&\n !expression.includes(\"?\")\n ) {\n return resolvePropertyPath(expression, data);\n }\n\n // For now, we'll only support simple property paths and dataLayer() calls\n // Complex expressions would require a safe expression evaluator\n return resolvePropertyPath(expression, data);\n}\n\nfunction resolvePropertyPath(path: string, data: unknown): unknown {\n if (!path || !data) return \"\";\n\n const keys = path.split(\".\");\n let value: unknown = data;\n\n for (const key of keys) {\n if (value && typeof value === \"object\" && key in value) {\n value = (value as Record<string, unknown>)[key];\n } else {\n return null;\n }\n }\n\n return value;\n}\n"],"names":[],"mappings":";;;AAuBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,MAAM;AACR,GAAkB;AAEhB,QAAM,mBAAmB,OAA2B,YAAY;AAAA,IAC9D,OAAO,CAAA;AAAA,EAAC,CACT;AACD,QAAM,WAAW,SAAS,MAAM,iBAAiB,SAAS,CAAA,CAAE;AAG5D,QAAM,iBAAiB,aAAA;AACvB,QAAM,gBAAgB,SAAS,MAAM,eAAe,SAAS,CAAA,CAAE;AAG/D,QAAM,eAAe,SAAS,MAAM;AAClC,UAAM,UAAU,QAAQ,MAAM,UAAU;AAGxC,WAAO;AAAA,MACL,YAAY,SAAS;AAAA,MACrB,MAAM,SAAS,QAAQ;AAAA;AAAA,IAAA;AAAA,EAE3B,CAAC;AAGD;AAAA,IACE,CAAC,UAAU,eAAe,YAAY;AAAA,IACtC,CAAC,CAAC,MAAM,SAAS,MAAM,MAAM;AAC3B,UAAI,CAAC,OAAO,cAAc,OAAO,SAAS,UAAU;AAClD;AAAA,MACF;AAEA,UAAI;AACF,cAAM,eAAe;AAAA,UACnB,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QAAA;AAEF,cAAM,cAAc,eAChB,MAAM,YAAY,IAClB,QAAQ,MAAM;AAClB,YAAI,iBAAiB,aAAa;AAChC,uBAAa,QAAQ,MAAM,MAAM,YAAY;AAAA,QAC/C;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ;AAAA,UACN,8BAA8B,QAAQ,MAAM,IAAI;AAAA,UAChD;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,EAAE,MAAM,MAAM,WAAW,KAAA;AAAA,EAAK;AAElC;AAEA,SAAS,wBACP,YACA,MACA,eACS;AAET,MAAI,WAAW,WAAW,YAAY,KAAK,WAAW,SAAS,GAAG,GAAG;AACnE,UAAM,eAAe,WAAW,MAAM,IAAI,EAAE;AAC5C,WAAO,oBAAoB,cAAc,aAAa;AAAA,EACxD;AAGA,MACE,CAAC,WAAW,SAAS,GAAG,KACxB,CAAC,WAAW,SAAS,GAAG,KACxB,CAAC,WAAW,SAAS,GAAG,GACxB;AACA,WAAO,oBAAoB,YAAY,IAAI;AAAA,EAC7C;AAIA,SAAO,oBAAoB,YAAY,IAAI;AAC7C;AAEA,SAAS,oBAAoB,MAAc,MAAwB;AACjE,MAAI,CAAC,QAAQ,CAAC,KAAM,QAAO;AAE3B,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,MAAI,QAAiB;AAErB,aAAW,OAAO,MAAM;AACtB,QAAI,SAAS,OAAO,UAAU,YAAY,OAAO,OAAO;AACtD,cAAS,MAAkC,GAAG;AAAA,IAChD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"useDerive.js","sources":["../../../src/vue/composables/useDerive.ts"],"sourcesContent":["import {\n computed,\n ref,\n watch,\n unref,\n inject,\n type Ref,\n type ComputedRef,\n} from \"vue\";\nimport { type ControlElement } from \"@jsonforms/core\";\nimport { useDataLayer } from \"./useDataLayer\";\n\ninterface DeriveOptions {\n control: Ref<{\n uischema: ControlElement;\n path: string;\n data: unknown;\n }>;\n handleChange: (path: string, value: unknown) => void;\n /** When projection is active, pass projectedData so the comparison\n * matches the projected (unwrapped) value rather than raw scope data. */\n data?: Ref<unknown> | ComputedRef<unknown>;\n}\n\nexport function useDerive({\n control,\n handleChange,\n data: dataOverride,\n}: DeriveOptions) {\n // Get the root form data from JSONForms context\n const injectedFormData = inject<{ value: unknown }>(\"formData\", {\n value: {},\n });\n const rootData = computed(() => injectedFormData.value || {});\n\n // Get data from the dataLayer\n const dataLayerState = useDataLayer();\n const dataLayerData = computed(() => dataLayerState.value || {});\n\n // Extract derive configuration from uischema options\n const deriveConfig = computed(() => {\n const options = control.value.uischema?.options as\n | { derive?: string; mode?: string }\n | undefined;\n return {\n expression: options?.derive,\n mode: options?.mode || \"follow\", // 'follow' = auto-update, 'manual' = user controlled\n };\n });\n\n // Track the last resolved source value so we only update the field\n // when the source itself changes, not on every form data mutation.\n const lastDerivedValue = ref<unknown>(undefined);\n\n // Watch for changes in form data and dataLayer and update derived field\n watch(\n [rootData, dataLayerData, deriveConfig],\n ([data, extData, config]) => {\n if (!config.expression || config.mode !== \"follow\") {\n return;\n }\n\n try {\n const derivedValue = resolveDeriveExpression(\n config.expression,\n data,\n extData,\n );\n\n // Only update if the SOURCE value changed, not just the field value\n if (derivedValue === lastDerivedValue.value) return;\n lastDerivedValue.value = derivedValue;\n\n const compareData = dataOverride\n ? unref(dataOverride)\n : control.value.data;\n if (derivedValue !== compareData) {\n handleChange(control.value.path, derivedValue);\n }\n } catch (error) {\n console.warn(\n `Failed to derive value for ${control.value.path}:`,\n error,\n );\n }\n },\n { deep: true, immediate: true },\n );\n}\n\nfunction resolveDeriveExpression(\n expression: string,\n data: unknown,\n dataLayerData?: unknown,\n): unknown {\n // Handle dataLayer() syntax\n if (expression.startsWith(\"dataLayer(\") && expression.endsWith(\")\")) {\n const propertyPath = expression.slice(10, -1); // Remove \"dataLayer(\" and \")\"\n return resolvePropertyPath(propertyPath, dataLayerData);\n }\n\n // Handle simple property paths like \"country.name\"\n if (\n !expression.includes(\"(\") &&\n !expression.includes(\"+\") &&\n !expression.includes(\"?\")\n ) {\n return resolvePropertyPath(expression, data);\n }\n\n // For now, we'll only support simple property paths and dataLayer() calls\n // Complex expressions would require a safe expression evaluator\n return resolvePropertyPath(expression, data);\n}\n\nfunction resolvePropertyPath(path: string, data: unknown): unknown {\n if (!path || !data) return \"\";\n\n const keys = path.split(\".\");\n let value: unknown = data;\n\n for (const key of keys) {\n if (value && typeof value === \"object\" && key in value) {\n value = (value as Record<string, unknown>)[key];\n } else {\n return null;\n }\n }\n\n return value;\n}\n"],"names":[],"mappings":";;;AAwBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,MAAM;AACR,GAAkB;AAEhB,QAAM,mBAAmB,OAA2B,YAAY;AAAA,IAC9D,OAAO,CAAA;AAAA,EAAC,CACT;AACD,QAAM,WAAW,SAAS,MAAM,iBAAiB,SAAS,CAAA,CAAE;AAG5D,QAAM,iBAAiB,aAAA;AACvB,QAAM,gBAAgB,SAAS,MAAM,eAAe,SAAS,CAAA,CAAE;AAG/D,QAAM,eAAe,SAAS,MAAM;AAClC,UAAM,UAAU,QAAQ,MAAM,UAAU;AAGxC,WAAO;AAAA,MACL,YAAY,SAAS;AAAA,MACrB,MAAM,SAAS,QAAQ;AAAA;AAAA,IAAA;AAAA,EAE3B,CAAC;AAID,QAAM,mBAAmB,IAAa,MAAS;AAG/C;AAAA,IACE,CAAC,UAAU,eAAe,YAAY;AAAA,IACtC,CAAC,CAAC,MAAM,SAAS,MAAM,MAAM;AAC3B,UAAI,CAAC,OAAO,cAAc,OAAO,SAAS,UAAU;AAClD;AAAA,MACF;AAEA,UAAI;AACF,cAAM,eAAe;AAAA,UACnB,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QAAA;AAIF,YAAI,iBAAiB,iBAAiB,MAAO;AAC7C,yBAAiB,QAAQ;AAEzB,cAAM,cAAc,eAChB,MAAM,YAAY,IAClB,QAAQ,MAAM;AAClB,YAAI,iBAAiB,aAAa;AAChC,uBAAa,QAAQ,MAAM,MAAM,YAAY;AAAA,QAC/C;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ;AAAA,UACN,8BAA8B,QAAQ,MAAM,IAAI;AAAA,UAChD;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,EAAE,MAAM,MAAM,WAAW,KAAA;AAAA,EAAK;AAElC;AAEA,SAAS,wBACP,YACA,MACA,eACS;AAET,MAAI,WAAW,WAAW,YAAY,KAAK,WAAW,SAAS,GAAG,GAAG;AACnE,UAAM,eAAe,WAAW,MAAM,IAAI,EAAE;AAC5C,WAAO,oBAAoB,cAAc,aAAa;AAAA,EACxD;AAGA,MACE,CAAC,WAAW,SAAS,GAAG,KACxB,CAAC,WAAW,SAAS,GAAG,KACxB,CAAC,WAAW,SAAS,GAAG,GACxB;AACA,WAAO,oBAAoB,YAAY,IAAI;AAAA,EAC7C;AAIA,SAAO,oBAAoB,YAAY,IAAI;AAC7C;AAEA,SAAS,oBAAoB,MAAc,MAAwB;AACjE,MAAI,CAAC,QAAQ,CAAC,KAAM,QAAO;AAE3B,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,MAAI,QAAiB;AAErB,aAAW,OAAO,MAAM;AACtB,QAAI,SAAS,OAAO,UAAU,YAAY,OAAO,OAAO;AACtD,cAAS,MAAkC,GAAG;AAAA,IAChD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "3.0.0-beta.6",
3
+ "version": "3.0.0-beta.8",
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,189 @@
1
+ /**
2
+ * Initialize a form data object from a JSON Schema.
3
+ * Resolves $ref, const, default, oneOf/discriminator, and typed empty values.
4
+ *
5
+ * @param schema - The full JSON Schema (must include $defs if $refs are used)
6
+ * @param seed - Optional existing data to merge (seed values take priority)
7
+ * @returns A data object with all schema-defined fields initialized
8
+ */
9
+ export function initFormDataFromSchema(
10
+ schema: Record<string, unknown>,
11
+ seed?: Record<string, unknown>,
12
+ ): Record<string, unknown> {
13
+ const result = initProperty(schema, schema, seed) as Record<string, unknown>;
14
+
15
+ // If result is an object and seed has extra keys not in schema, preserve them
16
+ if (
17
+ result &&
18
+ typeof result === "object" &&
19
+ !Array.isArray(result) &&
20
+ seed &&
21
+ typeof seed === "object"
22
+ ) {
23
+ const schemaKeys = new Set(
24
+ Object.keys(
25
+ (resolveRef(schema, schema) as Record<string, unknown>)?.properties ??
26
+ {},
27
+ ),
28
+ );
29
+ for (const key of Object.keys(seed)) {
30
+ if (!schemaKeys.has(key) && !(key in result)) {
31
+ result[key] = seed[key];
32
+ }
33
+ }
34
+ }
35
+
36
+ return result ?? {};
37
+ }
38
+
39
+ /**
40
+ * Resolve a $ref pointer against the root schema's $defs.
41
+ * Supports nested $ref chains.
42
+ */
43
+ function resolveRef(
44
+ property: Record<string, unknown>,
45
+ root: Record<string, unknown>,
46
+ seen?: Set<string>,
47
+ ): Record<string, unknown> {
48
+ if (!property || typeof property !== "object") return property;
49
+
50
+ const ref = property.$ref as string | undefined;
51
+ if (!ref) return property;
52
+
53
+ // Guard against circular refs
54
+ const visited = seen ?? new Set<string>();
55
+ if (visited.has(ref)) return property;
56
+ visited.add(ref);
57
+
58
+ const resolved = resolvePointer(root, ref);
59
+ if (!resolved) return property;
60
+
61
+ // Continue resolving if the result itself has a $ref
62
+ return resolveRef(resolved as Record<string, unknown>, root, visited);
63
+ }
64
+
65
+ /**
66
+ * Resolve a JSON pointer like "#/$defs/Price" against an object.
67
+ */
68
+ function resolvePointer(
69
+ obj: Record<string, unknown>,
70
+ pointer: string,
71
+ ): unknown {
72
+ if (!pointer.startsWith("#/")) return undefined;
73
+ const parts = pointer.slice(2).split("/");
74
+ let current: unknown = obj;
75
+ for (const part of parts) {
76
+ if (current && typeof current === "object" && part in current) {
77
+ current = (current as Record<string, unknown>)[part];
78
+ } else {
79
+ return undefined;
80
+ }
81
+ }
82
+ return current;
83
+ }
84
+
85
+ /**
86
+ * Initialize a single property value based on its schema definition.
87
+ */
88
+ function initProperty(
89
+ property: Record<string, unknown>,
90
+ root: Record<string, unknown>,
91
+ seed?: unknown,
92
+ ): unknown {
93
+ if (!property || typeof property !== "object") return null;
94
+
95
+ // Resolve $ref first
96
+ const resolved = resolveRef(property, root);
97
+
98
+ // Handle oneOf with discriminator — pick first variant
99
+ if (Array.isArray(resolved.oneOf)) {
100
+ return initOneOf(resolved, root, seed);
101
+ }
102
+
103
+ // Priority 1: seed wins (for object types, we merge recursively below)
104
+ // For non-object types, return seed directly if present
105
+ const type = resolved.type as string | undefined;
106
+ if (seed !== undefined && seed !== null && type !== "object") {
107
+ return seed;
108
+ }
109
+
110
+ // Priority 2: const
111
+ if ("const" in resolved) {
112
+ return resolved.const;
113
+ }
114
+
115
+ // Priority 3: single-value enum
116
+ if (
117
+ Array.isArray(resolved.enum) &&
118
+ (resolved.enum as unknown[]).length === 1
119
+ ) {
120
+ return (resolved.enum as unknown[])[0];
121
+ }
122
+
123
+ // Priority 4: default
124
+ if ("default" in resolved) {
125
+ return resolved.default;
126
+ }
127
+
128
+ // Priority 5: typed empty values
129
+ switch (type) {
130
+ case "object":
131
+ return initObject(resolved, root, seed as Record<string, unknown>);
132
+ case "array":
133
+ return seed !== undefined && seed !== null ? seed : [];
134
+ case "string":
135
+ return "";
136
+ case "boolean":
137
+ return false;
138
+ case "number":
139
+ case "integer":
140
+ return null;
141
+ default:
142
+ return null;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Initialize an object type by recursing into its properties.
148
+ */
149
+ function initObject(
150
+ schema: Record<string, unknown>,
151
+ root: Record<string, unknown>,
152
+ seed?: Record<string, unknown>,
153
+ ): Record<string, unknown> {
154
+ const properties = schema.properties as
155
+ | Record<string, Record<string, unknown>>
156
+ | undefined;
157
+ if (!properties) {
158
+ // Object with no defined properties — return seed or empty object
159
+ return seed && typeof seed === "object" ? { ...seed } : {};
160
+ }
161
+
162
+ const result: Record<string, unknown> = {};
163
+
164
+ for (const [key, propSchema] of Object.entries(properties)) {
165
+ const seedValue = seed && typeof seed === "object" ? seed[key] : undefined;
166
+ result[key] = initProperty(propSchema, root, seedValue);
167
+ }
168
+
169
+ return result;
170
+ }
171
+
172
+ /**
173
+ * Handle oneOf schemas — pick the first variant and initialize it.
174
+ */
175
+ function initOneOf(
176
+ schema: Record<string, unknown>,
177
+ root: Record<string, unknown>,
178
+ seed?: unknown,
179
+ ): unknown {
180
+ const variants = schema.oneOf as Record<string, unknown>[];
181
+ if (!variants || variants.length === 0) return null;
182
+
183
+ const first = variants[0];
184
+ if (!first) return null;
185
+
186
+ // Pick first variant, resolve its $ref if needed
187
+ const firstVariant = resolveRef(first, root);
188
+ return initProperty(firstVariant, root, seed);
189
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./core/projection";
14
14
  export * from "./core/resolveScope";
15
15
  // Core exports
16
16
  export * from "./core/types";
17
+ export { initFormDataFromSchema } from "./core/initFormData";
17
18
 
18
19
  // Protocol exports
19
20
  export { RestApiProtocol } from "./protocols/rest_api";
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  computed,
3
+ ref,
3
4
  watch,
4
5
  unref,
5
6
  inject,
@@ -47,6 +48,10 @@ export function useDerive({
47
48
  };
48
49
  });
49
50
 
51
+ // Track the last resolved source value so we only update the field
52
+ // when the source itself changes, not on every form data mutation.
53
+ const lastDerivedValue = ref<unknown>(undefined);
54
+
50
55
  // Watch for changes in form data and dataLayer and update derived field
51
56
  watch(
52
57
  [rootData, dataLayerData, deriveConfig],
@@ -61,6 +66,11 @@ export function useDerive({
61
66
  data,
62
67
  extData,
63
68
  );
69
+
70
+ // Only update if the SOURCE value changed, not just the field value
71
+ if (derivedValue === lastDerivedValue.value) return;
72
+ lastDerivedValue.value = derivedValue;
73
+
64
74
  const compareData = dataOverride
65
75
  ? unref(dataOverride)
66
76
  : control.value.data;