@narrative.io/jsonforms-provider-protocols 3.0.0-beta.2 → 3.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/core/initFormData.d.ts +17 -0
  2. package/dist/core/initFormData.d.ts.map +1 -0
  3. package/dist/core/initFormData.js +99 -0
  4. package/dist/core/initFormData.js.map +1 -0
  5. package/dist/core/projection.d.ts +4 -0
  6. package/dist/core/projection.d.ts.map +1 -1
  7. package/dist/core/projection.js +17 -14
  8. package/dist/core/projection.js.map +1 -1
  9. package/dist/core/refs.d.ts +58 -0
  10. package/dist/core/refs.d.ts.map +1 -0
  11. package/dist/core/refs.js +70 -0
  12. package/dist/core/refs.js.map +1 -0
  13. package/dist/core/resolveScope.d.ts +6 -0
  14. package/dist/core/resolveScope.d.ts.map +1 -1
  15. package/dist/core/resolveScope.js +14 -8
  16. package/dist/core/resolveScope.js.map +1 -1
  17. package/dist/core/transforms.d.ts.map +1 -1
  18. package/dist/core/transforms.js +3 -1
  19. package/dist/core/transforms.js.map +1 -1
  20. package/dist/core/types.d.ts +1 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/jsonforms-provider-protocols.css +2 -2
  27. package/dist/no-eval-ajv.d.ts +70 -0
  28. package/dist/no-eval-ajv.d.ts.map +1 -0
  29. package/dist/no-eval-ajv.js +247 -0
  30. package/dist/no-eval-ajv.js.map +1 -0
  31. package/dist/vue/components/ProviderAutocomplete.vue.d.ts.map +1 -1
  32. package/dist/vue/components/ProviderAutocomplete.vue.js +10 -6
  33. package/dist/vue/components/ProviderAutocomplete.vue.js.map +1 -1
  34. package/dist/vue/components/ProviderMultiSelect.vue.d.ts.map +1 -1
  35. package/dist/vue/components/ProviderMultiSelect.vue.js +1 -1
  36. package/dist/vue/components/ProviderMultiSelect.vue2.js +17 -9
  37. package/dist/vue/components/ProviderMultiSelect.vue2.js.map +1 -1
  38. package/dist/vue/components/ProviderSelect.vue.d.ts.map +1 -1
  39. package/dist/vue/components/ProviderSelect.vue.js +1 -1
  40. package/dist/vue/components/ProviderSelect.vue2.js +19 -9
  41. package/dist/vue/components/ProviderSelect.vue2.js.map +1 -1
  42. package/dist/vue/composables/useDataLayer.d.ts +1 -0
  43. package/dist/vue/composables/useDataLayer.d.ts.map +1 -1
  44. package/dist/vue/composables/useDataLayer.js +1 -0
  45. package/dist/vue/composables/useDataLayer.js.map +1 -1
  46. package/dist/vue/composables/useDerive.d.ts +1 -1
  47. package/dist/vue/composables/useDerive.d.ts.map +1 -1
  48. package/dist/vue/composables/useDerive.js +19 -2
  49. package/dist/vue/composables/useDerive.js.map +1 -1
  50. package/dist/vue/composables/useDeriveInitialValue.d.ts +36 -0
  51. package/dist/vue/composables/useDeriveInitialValue.d.ts.map +1 -0
  52. package/dist/vue/composables/useDeriveInitialValue.js +125 -0
  53. package/dist/vue/composables/useDeriveInitialValue.js.map +1 -0
  54. package/dist/vue/composables/useDirtyValidation.d.ts +3 -3
  55. package/dist/vue/composables/useDirtyValidation.d.ts.map +1 -1
  56. package/dist/vue/composables/useDirtyValidation.js +2 -2
  57. package/dist/vue/composables/useDirtyValidation.js.map +1 -1
  58. package/dist/vue/composables/useProjection.d.ts +7 -0
  59. package/dist/vue/composables/useProjection.d.ts.map +1 -1
  60. package/dist/vue/composables/useProjection.js +87 -4
  61. package/dist/vue/composables/useProjection.js.map +1 -1
  62. package/dist/vue/composables/useProvider.d.ts +2 -2
  63. package/dist/vue/composables/useProvider.d.ts.map +1 -1
  64. package/dist/vue/composables/useProvider.js +14 -10
  65. package/dist/vue/composables/useProvider.js.map +1 -1
  66. package/dist/vue/index.d.ts +2 -0
  67. package/dist/vue/index.d.ts.map +1 -1
  68. package/dist/vue/index.js +30 -10
  69. package/dist/vue/index.js.map +1 -1
  70. package/dist/vue/primevue/JfBoolean.vue.d.ts.map +1 -1
  71. package/dist/vue/primevue/JfBoolean.vue.js +17 -6
  72. package/dist/vue/primevue/JfBoolean.vue.js.map +1 -1
  73. package/dist/vue/primevue/JfEnum.vue.d.ts.map +1 -1
  74. package/dist/vue/primevue/JfEnum.vue.js +22 -10
  75. package/dist/vue/primevue/JfEnum.vue.js.map +1 -1
  76. package/dist/vue/primevue/JfEnumArray.vue.d.ts.map +1 -1
  77. package/dist/vue/primevue/JfEnumArray.vue.js +20 -10
  78. package/dist/vue/primevue/JfEnumArray.vue.js.map +1 -1
  79. package/dist/vue/primevue/JfNumber.vue.d.ts.map +1 -1
  80. package/dist/vue/primevue/JfNumber.vue.js +18 -10
  81. package/dist/vue/primevue/JfNumber.vue.js.map +1 -1
  82. package/dist/vue/primevue/JfText.vue.d.ts.map +1 -1
  83. package/dist/vue/primevue/JfText.vue.js +27 -12
  84. package/dist/vue/primevue/JfText.vue.js.map +1 -1
  85. package/dist/vue/primevue/JfTextArea.vue.d.ts.map +1 -1
  86. package/dist/vue/primevue/JfTextArea.vue.js +15 -9
  87. package/dist/vue/primevue/JfTextArea.vue.js.map +1 -1
  88. package/dist/vue/primevue/index.d.ts.map +1 -1
  89. package/dist/vue/primevue/index.js +93 -16
  90. package/dist/vue/primevue/index.js.map +1 -1
  91. package/dist/vue/utils/autoSelect.js.map +1 -1
  92. package/dist/vue/utils/placeholder.d.ts +17 -0
  93. package/dist/vue/utils/placeholder.d.ts.map +1 -0
  94. package/dist/vue/utils/placeholder.js +17 -0
  95. package/dist/vue/utils/placeholder.js.map +1 -0
  96. package/package.json +10 -2
  97. package/src/core/initFormData.ts +208 -0
  98. package/src/core/projection.ts +33 -22
  99. package/src/core/refs.ts +166 -0
  100. package/src/core/resolveScope.ts +23 -8
  101. package/src/core/transforms.ts +33 -6
  102. package/src/core/types.ts +1 -0
  103. package/src/index.ts +14 -2
  104. package/src/no-eval-ajv.ts +381 -0
  105. package/src/vue/components/ProviderAutocomplete.vue +9 -7
  106. package/src/vue/components/ProviderMultiSelect.vue +20 -15
  107. package/src/vue/components/ProviderSelect.vue +21 -14
  108. package/src/vue/composables/useDataLayer.ts +1 -1
  109. package/src/vue/composables/useDerive.ts +46 -3
  110. package/src/vue/composables/useDeriveInitialValue.ts +195 -0
  111. package/src/vue/composables/useDirtyValidation.ts +8 -3
  112. package/src/vue/composables/useProjection.ts +172 -1
  113. package/src/vue/composables/useProvider.ts +28 -11
  114. package/src/vue/index.ts +28 -9
  115. package/src/vue/primevue/JfBoolean.vue +10 -5
  116. package/src/vue/primevue/JfEnum.vue +23 -14
  117. package/src/vue/primevue/JfEnumArray.vue +22 -17
  118. package/src/vue/primevue/JfNumber.vue +20 -12
  119. package/src/vue/primevue/JfText.vue +31 -16
  120. package/src/vue/primevue/JfTextArea.vue +15 -13
  121. package/src/vue/primevue/index.ts +104 -23
  122. package/src/vue/styles.css +26 -1
  123. package/src/vue/utils/autoSelect.ts +2 -2
  124. package/src/vue/utils/placeholder.ts +42 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/vue/primevue/index.ts"],"sourcesContent":["import JfText from \"./JfText.vue\";\nimport JfTextArea from \"./JfTextArea.vue\";\nimport JfNumber from \"./JfNumber.vue\";\nimport JfEnum from \"./JfEnum.vue\";\nimport JfEnumArray from \"./JfEnumArray.vue\";\nimport JfBoolean from \"./JfBoolean.vue\";\nimport { getProjectedSchema } from \"../../core/projection\";\nimport { resolveScopeSchema } from \"../../core/resolveScope\";\n\n// Auto-inject layout styles\nconst injectLayoutStyles = () => {\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n if (!document.getElementById(\"jsonforms-primevue-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"jsonforms-primevue-styles\";\n style.textContent = `\n/* JSONForms PrimeVue Provider Protocols Layout Styles */\n.vertical-layout {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 1rem;\n width: 100%;\n}\n\n.vertical-layout-item {\n width: 100%;\n}\n `;\n document.head.appendChild(style);\n }\n }\n};\n\n// Inject styles when this module is imported\ninjectLayoutStyles();\n\n// Track whether renderers have been registered to avoid duplicate registrations\nlet renderersInitialized = false;\n\n// Return empty renderers array initially\nexport const primevueRenderers: unknown[] = [];\n\n// Export a function that can be called by the consumer to register renderers\n// This should only be called once during application initialization\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function registerPrimevueRenderers(jsonformsCore: any): unknown[] {\n // Prevent duplicate registration which causes AJV schema recompilation\n if (renderersInitialized) {\n return primevueRenderers as unknown[];\n }\n\n const {\n rankWith,\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and,\n or,\n isControl,\n schemaMatches,\n isBooleanControl,\n } = jsonformsCore;\n\n // Give PrimeVue renderers a high base rank; vanilla commonly uses small ranks (2–5)\n const PRIME = 100;\n\n // helpers for enum detection - using simple schema checks instead of JSONForms internal functions\n const isScalarEnum = (s?: object) => {\n const schema = s as {\n type?: string;\n enum?: unknown[];\n oneOf?: unknown[];\n };\n return (\n schema &&\n schema.type !== \"array\" &&\n (Array.isArray(schema.enum) || Array.isArray(schema.oneOf))\n );\n };\n\n const isEnumArray = (s?: object) => {\n const schema = s as {\n type?: string;\n items?: { enum?: unknown[]; oneOf?: unknown[] };\n };\n return (\n schema?.type === \"array\" &&\n (Array.isArray(schema.items?.enum) || Array.isArray(schema.items?.oneOf))\n );\n };\n\n // helper for multiline detection\n const isMultilineString = (uischema: unknown, schema: unknown) => {\n const controlUischema = uischema as { options?: { multi?: boolean } };\n return and(isStringControl, () => controlUischema?.options?.multi === true)(\n uischema as Parameters<typeof isStringControl>[0],\n schema as Parameters<typeof isStringControl>[1],\n {} as Parameters<typeof isStringControl>[2],\n );\n };\n\n // Projection-aware schema check: when options.projection is set,\n // resolve the projected schema and test against it instead of the original\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const projectedSchemaMatches = (check: (schema: any) => boolean) =>\n (uischema: unknown, schema: unknown): boolean => {\n const ui = uischema as { type?: string; scope?: string; options?: { projection?: string } };\n const projection = ui?.options?.projection;\n if (!projection || ui?.type !== \"Control\" || !ui?.scope) return false;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const propertySchema = resolveScopeSchema(ui.scope, schema as Record<string, any>);\n if (!propertySchema) return false;\n\n return check(getProjectedSchema(propertySchema, projection));\n };\n\n const isMultilineProjection = (uischema: unknown, schema: unknown) => {\n const ui = uischema as { options?: { multi?: boolean } };\n return ui?.options?.multi === true &&\n projectedSchemaMatches((s) => s?.type === \"string\")(uischema, schema);\n };\n\n const renderers = [\n // Multiline text has higher priority than regular text\n { tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)), renderer: JfTextArea },\n { tester: rankWith(PRIME + 3, or(isStringControl, projectedSchemaMatches((s) => s?.type === \"string\"))), renderer: JfText },\n {\n tester: rankWith(PRIME + 6, or(isIntegerControl, projectedSchemaMatches((s) => s?.type === \"integer\"))),\n renderer: JfNumber,\n },\n {\n tester: rankWith(PRIME + 4, or(isNumberControl, projectedSchemaMatches((s) => s?.type === \"number\"))),\n renderer: JfNumber,\n },\n {\n tester: rankWith(PRIME + 7, or(and(isControl, schemaMatches(isScalarEnum)), and(isControl, projectedSchemaMatches(isScalarEnum)))),\n renderer: JfEnum,\n },\n {\n tester: rankWith(PRIME + 8, or(and(isControl, schemaMatches(isEnumArray)), and(isControl, projectedSchemaMatches(isEnumArray)))),\n renderer: JfEnumArray,\n },\n { tester: rankWith(PRIME + 3, or(isBooleanControl, projectedSchemaMatches((s) => s?.type === \"boolean\"))), renderer: JfBoolean },\n ];\n\n // Update the exported array\n primevueRenderers.splice(0, primevueRenderers.length, ...renderers);\n\n // Mark as initialized to prevent duplicate registrations\n renderersInitialized = true;\n\n return renderers;\n}\n\nexport { JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean };\n"],"names":["JfTextArea","JfText","JfNumber","JfEnum","JfEnumArray","JfBoolean"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/vue/primevue/index.ts"],"sourcesContent":["import JfText from \"./JfText.vue\";\nimport JfTextArea from \"./JfTextArea.vue\";\nimport JfNumber from \"./JfNumber.vue\";\nimport JfEnum from \"./JfEnum.vue\";\nimport JfEnumArray from \"./JfEnumArray.vue\";\nimport JfBoolean from \"./JfBoolean.vue\";\nimport { getProjectedSchema } from \"../../core/projection\";\nimport { resolveScopeSchema } from \"../../core/resolveScope\";\n\n// Auto-inject layout styles\nconst injectLayoutStyles = () => {\n if (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n if (!document.getElementById(\"jsonforms-primevue-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"jsonforms-primevue-styles\";\n style.textContent = `\n/* JSONForms PrimeVue Provider Protocols Layout Styles */\n.vertical-layout {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 24px;\n width: 100%;\n}\n\n.vertical-layout-item {\n width: 100%;\n}\n\n/* Form control wrapper */\n.jf-control {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n/* Form control label typography */\n.jf-label {\n font-weight: 600;\n font-size: 14px;\n line-height: 14px;\n color: #031553;\n text-align: left;\n}\n\n/* Form control description typography */\n.jf-description {\n font-weight: 400;\n font-size: 14px;\n line-height: 14px;\n color: #415290;\n text-align: left;\n}\n `;\n document.head.appendChild(style);\n }\n }\n};\n\n// Inject styles when this module is imported\ninjectLayoutStyles();\n\n// Track whether renderers have been registered to avoid duplicate registrations\nlet renderersInitialized = false;\n\n// Return empty renderers array initially\nexport const primevueRenderers: unknown[] = [];\n\n// Export a function that can be called by the consumer to register renderers\n// This should only be called once during application initialization\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function registerPrimevueRenderers(jsonformsCore: any): unknown[] {\n // Prevent duplicate registration which causes AJV schema recompilation\n if (renderersInitialized) {\n return primevueRenderers as unknown[];\n }\n\n const {\n rankWith,\n isStringControl,\n isNumberControl,\n isIntegerControl,\n and,\n or,\n isControl,\n schemaMatches,\n isBooleanControl,\n } = jsonformsCore;\n\n // Give PrimeVue renderers a high base rank; vanilla commonly uses small ranks (2–5)\n const PRIME = 100;\n\n // helpers for enum detection - using simple schema checks instead of JSONForms internal functions\n const isScalarEnum = (s?: object) => {\n const schema = s as {\n type?: string;\n enum?: unknown[];\n oneOf?: unknown[];\n };\n return (\n schema &&\n schema.type !== \"array\" &&\n (Array.isArray(schema.enum) || Array.isArray(schema.oneOf))\n );\n };\n\n const isEnumArray = (s?: object) => {\n const schema = s as {\n type?: string;\n items?: { enum?: unknown[]; oneOf?: unknown[] };\n };\n return (\n schema?.type === \"array\" &&\n (Array.isArray(schema.items?.enum) || Array.isArray(schema.items?.oneOf))\n );\n };\n\n // helper for multiline detection\n const isMultilineString = (uischema: unknown, schema: unknown) => {\n const controlUischema = uischema as { options?: { multi?: boolean } };\n return and(isStringControl, () => controlUischema?.options?.multi === true)(\n uischema as Parameters<typeof isStringControl>[0],\n schema as Parameters<typeof isStringControl>[1],\n {} as Parameters<typeof isStringControl>[2],\n );\n };\n\n // Projection-aware schema check: when options.projection is set,\n // resolve the projected schema and test against it instead of the original\n\n const projectedSchemaMatches =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (check: (schema: any) => boolean) =>\n (uischema: unknown, schema: unknown): boolean => {\n const ui = uischema as {\n type?: string;\n scope?: string;\n options?: { projection?: string };\n };\n const projection = ui?.options?.projection;\n if (!projection || ui?.type !== \"Control\" || !ui?.scope) return false;\n\n const propertySchema = resolveScopeSchema(\n ui.scope,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema as Record<string, any>,\n );\n if (!propertySchema) return false;\n\n return check(getProjectedSchema(propertySchema, projection));\n };\n\n const isMultilineProjection = (uischema: unknown, schema: unknown) => {\n const ui = uischema as { options?: { multi?: boolean } };\n return (\n ui?.options?.multi === true &&\n projectedSchemaMatches((s) => s?.type === \"string\")(uischema, schema)\n );\n };\n\n const renderers = [\n // Multiline text has higher priority than regular text\n {\n tester: rankWith(PRIME + 4, or(isMultilineString, isMultilineProjection)),\n renderer: JfTextArea,\n },\n {\n tester: rankWith(\n PRIME + 3,\n or(\n isStringControl,\n projectedSchemaMatches((s) => s?.type === \"string\"),\n ),\n ),\n renderer: JfText,\n },\n {\n tester: rankWith(\n PRIME + 6,\n or(\n isIntegerControl,\n projectedSchemaMatches((s) => s?.type === \"integer\"),\n ),\n ),\n renderer: JfNumber,\n },\n {\n tester: rankWith(\n PRIME + 4,\n or(\n isNumberControl,\n projectedSchemaMatches((s) => s?.type === \"number\"),\n ),\n ),\n renderer: JfNumber,\n },\n {\n tester: rankWith(\n PRIME + 7,\n or(\n and(isControl, schemaMatches(isScalarEnum)),\n and(isControl, projectedSchemaMatches(isScalarEnum)),\n ),\n ),\n renderer: JfEnum,\n },\n {\n tester: rankWith(\n PRIME + 8,\n or(\n and(isControl, schemaMatches(isEnumArray)),\n and(isControl, projectedSchemaMatches(isEnumArray)),\n ),\n ),\n renderer: JfEnumArray,\n },\n {\n tester: rankWith(\n PRIME + 3,\n or(\n isBooleanControl,\n projectedSchemaMatches((s) => s?.type === \"boolean\"),\n ),\n ),\n renderer: JfBoolean,\n },\n ];\n\n // Update the exported array\n primevueRenderers.splice(0, primevueRenderers.length, ...renderers);\n\n // Mark as initialized to prevent duplicate registrations\n renderersInitialized = true;\n\n return renderers;\n}\n\nexport { JfText, JfTextArea, JfNumber, JfEnum, JfEnumArray, JfBoolean };\n"],"names":["JfTextArea","JfText","JfNumber","JfEnum","JfEnumArray","JfBoolean"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"autoSelect.js","sources":["../../../src/vue/utils/autoSelect.ts"],"sourcesContent":["import type { ProviderItem } from \"../../core/types\";\n\nexport interface AutoSelectParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown;\n}\n\n/**\n * Determines if auto-select should trigger for single-select dropdowns.\n * Returns null if auto-select should not trigger.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is enabled (default: true for single-select)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty (undefined/null) OR not in the current options\n */\nexport function shouldAutoSelect(params: AutoSelectParams): unknown | null {\n const { autoSelectSingle, isLoading, items, currentValue } = params;\n\n if (!autoSelectSingle || isLoading || items.length !== 1) {\n return null;\n }\n\n const singleItem = items[0];\n if (!singleItem) {\n return null;\n }\n\n const isValueEmpty = currentValue === undefined || currentValue === null;\n const isValueInOptions = items.some((item) => item.value === currentValue);\n\n if (isValueEmpty || !isValueInOptions) {\n return singleItem.value;\n }\n\n return null;\n}\n\nexport interface AutoSelectMultiParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown[];\n}\n\n/**\n * Determines if auto-select should trigger for multiselect dropdowns.\n * Returns null if auto-select should not trigger, otherwise returns an array with the single value.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is explicitly enabled (default: false for multiselect)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty array OR current selection is not in the current options\n */\nexport function shouldAutoSelectMulti(\n params: AutoSelectMultiParams\n): unknown[] | null {\n const { autoSelectSingle, isLoading, items, currentValue } = params;\n\n if (!autoSelectSingle || isLoading || items.length !== 1) {\n return null;\n }\n\n const singleItem = items[0];\n if (!singleItem) {\n return null;\n }\n\n const currentArray = Array.isArray(currentValue) ? currentValue : [];\n const isValueEmpty = currentArray.length === 0;\n const hasValidSelection = currentArray.some((val) =>\n items.some((item) => item.value === val)\n );\n\n if (isValueEmpty || !hasValidSelection) {\n return [singleItem.value];\n }\n\n return null;\n}\n"],"names":[],"mappings":"AAmBO,SAAS,iBAAiB,QAA0C;AACzE,QAAM,EAAE,kBAAkB,WAAW,OAAO,iBAAiB;AAE7D,MAAI,CAAC,oBAAoB,aAAa,MAAM,WAAW,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,CAAC;AAC1B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,iBAAiB,UAAa,iBAAiB;AACpE,QAAM,mBAAmB,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,YAAY;AAEzE,MAAI,gBAAgB,CAAC,kBAAkB;AACrC,WAAO,WAAW;AAAA,EACpB;AAEA,SAAO;AACT;AAmBO,SAAS,sBACd,QACkB;AAClB,QAAM,EAAE,kBAAkB,WAAW,OAAO,iBAAiB;AAE7D,MAAI,CAAC,oBAAoB,aAAa,MAAM,WAAW,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,CAAC;AAC1B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAA;AAClE,QAAM,eAAe,aAAa,WAAW;AAC7C,QAAM,oBAAoB,aAAa;AAAA,IAAK,CAAC,QAC3C,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,GAAG;AAAA,EAAA;AAGzC,MAAI,gBAAgB,CAAC,mBAAmB;AACtC,WAAO,CAAC,WAAW,KAAK;AAAA,EAC1B;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"autoSelect.js","sources":["../../../src/vue/utils/autoSelect.ts"],"sourcesContent":["import type { ProviderItem } from \"../../core/types\";\n\nexport interface AutoSelectParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown;\n}\n\n/**\n * Determines if auto-select should trigger for single-select dropdowns.\n * Returns null if auto-select should not trigger.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is enabled (default: true for single-select)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty (undefined/null) OR not in the current options\n */\nexport function shouldAutoSelect(params: AutoSelectParams): unknown | null {\n const { autoSelectSingle, isLoading, items, currentValue } = params;\n\n if (!autoSelectSingle || isLoading || items.length !== 1) {\n return null;\n }\n\n const singleItem = items[0];\n if (!singleItem) {\n return null;\n }\n\n const isValueEmpty = currentValue === undefined || currentValue === null;\n const isValueInOptions = items.some((item) => item.value === currentValue);\n\n if (isValueEmpty || !isValueInOptions) {\n return singleItem.value;\n }\n\n return null;\n}\n\nexport interface AutoSelectMultiParams {\n autoSelectSingle: boolean;\n isLoading: boolean;\n items: ProviderItem[];\n currentValue: unknown[];\n}\n\n/**\n * Determines if auto-select should trigger for multiselect dropdowns.\n * Returns null if auto-select should not trigger, otherwise returns an array with the single value.\n *\n * Auto-select triggers when:\n * - autoSelectSingle option is explicitly enabled (default: false for multiselect)\n * - Provider has finished loading\n * - Exactly one item is available\n * - Current value is empty array OR current selection is not in the current options\n */\nexport function shouldAutoSelectMulti(\n params: AutoSelectMultiParams,\n): unknown[] | null {\n const { autoSelectSingle, isLoading, items, currentValue } = params;\n\n if (!autoSelectSingle || isLoading || items.length !== 1) {\n return null;\n }\n\n const singleItem = items[0];\n if (!singleItem) {\n return null;\n }\n\n const currentArray = Array.isArray(currentValue) ? currentValue : [];\n const isValueEmpty = currentArray.length === 0;\n const hasValidSelection = currentArray.some((val) =>\n items.some((item) => item.value === val),\n );\n\n if (isValueEmpty || !hasValidSelection) {\n return [singleItem.value];\n }\n\n return null;\n}\n"],"names":[],"mappings":"AAmBO,SAAS,iBAAiB,QAA0C;AACzE,QAAM,EAAE,kBAAkB,WAAW,OAAO,iBAAiB;AAE7D,MAAI,CAAC,oBAAoB,aAAa,MAAM,WAAW,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,CAAC;AAC1B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,iBAAiB,UAAa,iBAAiB;AACpE,QAAM,mBAAmB,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,YAAY;AAEzE,MAAI,gBAAgB,CAAC,kBAAkB;AACrC,WAAO,WAAW;AAAA,EACpB;AAEA,SAAO;AACT;AAmBO,SAAS,sBACd,QACkB;AAClB,QAAM,EAAE,kBAAkB,WAAW,OAAO,iBAAiB;AAE7D,MAAI,CAAC,oBAAoB,aAAa,MAAM,WAAW,GAAG;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,CAAC;AAC1B,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAA;AAClE,QAAM,eAAe,aAAa,WAAW;AAC7C,QAAM,oBAAoB,aAAa;AAAA,IAAK,CAAC,QAC3C,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,GAAG;AAAA,EAAA;AAGzC,MAAI,gBAAgB,CAAC,mBAAmB;AACtC,WAAO,CAAC,WAAW,KAAK;AAAA,EAC1B;AAEA,SAAO;AACT;"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Placeholder resolution for form controls.
3
+ *
4
+ * Precedence:
5
+ * 1. `uischema.options.placeholder` (explicit author intent — always wins)
6
+ * 2. `Select ${label}` or `Enter ${label}` when a label is resolvable
7
+ * 3. Kind-appropriate bare fallback
8
+ *
9
+ * Never falls back to `schema.description` — descriptions are rendered as
10
+ * prose above the field by our renderers, so re-using them as placeholder
11
+ * produces a duplicated, truncated string inside the input.
12
+ */
13
+ export type PlaceholderKind = "select" | "input";
14
+ export declare function resolvePlaceholder(uischema: {
15
+ options?: unknown;
16
+ } | undefined, resolvedLabel: string | undefined, kind: PlaceholderKind): string | undefined;
17
+ //# sourceMappingURL=placeholder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholder.d.ts","sourceRoot":"","sources":["../../../src/vue/utils/placeholder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC;AAUjD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC3C,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,IAAI,EAAE,eAAe,GACpB,MAAM,GAAG,SAAS,CAcpB"}
@@ -0,0 +1,17 @@
1
+ function stripRequiredMarker(label) {
2
+ return label.replace(/\s*\*\s*$/, "").trim();
3
+ }
4
+ function resolvePlaceholder(uischema, resolvedLabel, kind) {
5
+ const options = uischema?.options;
6
+ const explicit = options && typeof options === "object" ? options.placeholder : void 0;
7
+ if (typeof explicit === "string" && explicit.length > 0) return explicit;
8
+ const label = resolvedLabel ? stripRequiredMarker(resolvedLabel) : "";
9
+ if (label) {
10
+ return kind === "select" ? `Select ${label}` : `Enter ${label}`;
11
+ }
12
+ return kind === "select" ? "Select…" : void 0;
13
+ }
14
+ export {
15
+ resolvePlaceholder
16
+ };
17
+ //# sourceMappingURL=placeholder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholder.js","sources":["../../../src/vue/utils/placeholder.ts"],"sourcesContent":["/**\n * Placeholder resolution for form controls.\n *\n * Precedence:\n * 1. `uischema.options.placeholder` (explicit author intent — always wins)\n * 2. `Select ${label}` or `Enter ${label}` when a label is resolvable\n * 3. Kind-appropriate bare fallback\n *\n * Never falls back to `schema.description` — descriptions are rendered as\n * prose above the field by our renderers, so re-using them as placeholder\n * produces a duplicated, truncated string inside the input.\n */\n\nexport type PlaceholderKind = \"select\" | \"input\";\n\n/**\n * Strip a trailing required-indicator asterisk (\" *\" or \"*\") that `resolveLabel`\n * appends for required fields, so composed placeholders read naturally.\n */\nfunction stripRequiredMarker(label: string): string {\n return label.replace(/\\s*\\*\\s*$/, \"\").trim();\n}\n\nexport function resolvePlaceholder(\n uischema: { options?: unknown } | undefined,\n resolvedLabel: string | undefined,\n kind: PlaceholderKind,\n): string | undefined {\n const options = uischema?.options;\n const explicit =\n options && typeof options === \"object\"\n ? (options as Record<string, unknown>).placeholder\n : undefined;\n if (typeof explicit === \"string\" && explicit.length > 0) return explicit;\n\n const label = resolvedLabel ? stripRequiredMarker(resolvedLabel) : \"\";\n if (label) {\n return kind === \"select\" ? `Select ${label}` : `Enter ${label}`;\n }\n\n return kind === \"select\" ? \"Select…\" : undefined;\n}\n"],"names":[],"mappings":"AAmBA,SAAS,oBAAoB,OAAuB;AAClD,SAAO,MAAM,QAAQ,aAAa,EAAE,EAAE,KAAA;AACxC;AAEO,SAAS,mBACd,UACA,eACA,MACoB;AACpB,QAAM,UAAU,UAAU;AAC1B,QAAM,WACJ,WAAW,OAAO,YAAY,WACzB,QAAoC,cACrC;AACN,MAAI,OAAO,aAAa,YAAY,SAAS,SAAS,EAAG,QAAO;AAEhE,QAAM,QAAQ,gBAAgB,oBAAoB,aAAa,IAAI;AACnE,MAAI,OAAO;AACT,WAAO,SAAS,WAAW,UAAU,KAAK,KAAK,SAAS,KAAK;AAAA,EAC/D;AAEA,SAAO,SAAS,WAAW,YAAY;AACzC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "3.0.0-beta.2",
3
+ "version": "3.0.0-beta.21",
4
4
  "description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
5
5
  "type": "module",
6
6
  "author": "Narrative I/O",
@@ -47,6 +47,10 @@
47
47
  "import": "./dist/protocols/rest_api.js",
48
48
  "types": "./dist/protocols/rest_api.d.ts"
49
49
  },
50
+ "./no-eval-ajv": {
51
+ "import": "./dist/no-eval-ajv.js",
52
+ "types": "./dist/no-eval-ajv.d.ts"
53
+ },
50
54
  "./style.css": "./dist/jsonforms-provider-protocols.css"
51
55
  },
52
56
  "files": [
@@ -84,12 +88,14 @@
84
88
  "@vitejs/plugin-vue": "^6.0.1",
85
89
  "@vue/eslint-config-typescript": "^14.6.0",
86
90
  "@vue/runtime-core": "^3.5.20",
91
+ "@vue/test-utils": "^2.4.6",
87
92
  "bun-git-hooks": "^0.2.19",
88
93
  "commitlint": "^20.0.0",
89
94
  "eslint": "^9.34.0",
90
95
  "eslint-config-prettier": "^10.1.8",
91
96
  "eslint-plugin-vue": "^10.4.0",
92
97
  "globals": "^16.3.0",
98
+ "happy-dom": "^20.6.3",
93
99
  "prettier": "^3.6.2",
94
100
  "primevue": "^4.3.8",
95
101
  "typescript": "^5.9.2",
@@ -105,5 +111,7 @@
105
111
  "@jsonforms/vue": "^3.6.0",
106
112
  "@jsonforms/vue-vanilla": "^3.6.0"
107
113
  },
108
- "dependencies": {}
114
+ "dependencies": {
115
+ "@cfworker/json-schema": "4.1.1"
116
+ }
109
117
  }
@@ -0,0 +1,208 @@
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 function initFormDataFromSchema(
17
+ schema: Record<string, unknown>,
18
+ seed?: Record<string, unknown>,
19
+ ): Record<string, unknown> {
20
+ const result = initProperty(schema, schema, seed, true) as
21
+ | Record<string, unknown>
22
+ | undefined;
23
+
24
+ const base =
25
+ result && typeof result === "object" && !Array.isArray(result)
26
+ ? result
27
+ : {};
28
+
29
+ // Preserve seed keys not described by the schema's properties.
30
+ if (seed && typeof seed === "object") {
31
+ const schemaKeys = new Set(
32
+ Object.keys(
33
+ (resolveRef(schema, schema) as Record<string, unknown>)?.properties ??
34
+ {},
35
+ ),
36
+ );
37
+ for (const key of Object.keys(seed)) {
38
+ if (!schemaKeys.has(key) && !(key in base)) {
39
+ base[key] = seed[key];
40
+ }
41
+ }
42
+ }
43
+
44
+ return base;
45
+ }
46
+
47
+ import { resolveRef } from "./refs";
48
+
49
+ /**
50
+ * Initialize a single property value based on its schema definition.
51
+ * Returns `undefined` when the property is optional and has nothing
52
+ * concrete to seed — the caller then omits the key from its result.
53
+ */
54
+ function initProperty(
55
+ property: Record<string, unknown>,
56
+ root: Record<string, unknown>,
57
+ seed: unknown,
58
+ required: boolean,
59
+ ): unknown {
60
+ if (!property || typeof property !== "object") {
61
+ return required ? null : undefined;
62
+ }
63
+
64
+ const resolved = resolveRef(property, root);
65
+ const type = resolved.type as string | undefined;
66
+ const isOneOf = Array.isArray(resolved.oneOf);
67
+
68
+ // Priority 1: seed wins for non-object, non-oneOf types. For oneOf we still
69
+ // descend so that a partial seed (e.g. `{value: 5}` missing the
70
+ // discriminator) picks up the variant's const/default for untouched fields.
71
+ if (seed !== undefined && seed !== null && type !== "object" && !isOneOf) {
72
+ return seed;
73
+ }
74
+
75
+ // Priority 2: const (schema-invariant — always set).
76
+ if ("const" in resolved) {
77
+ return resolved.const;
78
+ }
79
+
80
+ // Priority 3: single-value enum (same reasoning — only one valid value).
81
+ if (
82
+ Array.isArray(resolved.enum) &&
83
+ (resolved.enum as unknown[]).length === 1
84
+ ) {
85
+ return (resolved.enum as unknown[])[0];
86
+ }
87
+
88
+ // Priority 4: default (explicit author intent — always honored).
89
+ if ("default" in resolved) {
90
+ return resolved.default;
91
+ }
92
+
93
+ // Priority 5: oneOf. Recurse into the first variant, propagating the
94
+ // `required` flag so that the variant's own schema-declared required fields
95
+ // only get typed-empty seeding when the container is required. For optional
96
+ // containers, the variant's const / default / single-value enum still seed
97
+ // (author intent is unconditional) but typed-empty placeholders do not.
98
+ // If recursion yields nothing forced, we collapse back to undefined.
99
+ if (isOneOf) {
100
+ const value = initOneOf(resolved, root, seed, required);
101
+ if (
102
+ !required &&
103
+ (value === undefined ||
104
+ (value !== null &&
105
+ typeof value === "object" &&
106
+ !Array.isArray(value) &&
107
+ Object.keys(value as Record<string, unknown>).length === 0))
108
+ ) {
109
+ return undefined;
110
+ }
111
+ return value;
112
+ }
113
+
114
+ // Priority 6: objects recurse. The `required` flag propagates downward:
115
+ // inside an optional ancestor, nested required primitives also collapse,
116
+ // so an optional object with nested required fields stays absent instead
117
+ // of materializing a shell that AJV would flag.
118
+ if (type === "object") {
119
+ const obj = initObject(
120
+ resolved,
121
+ root,
122
+ seed as Record<string, unknown>,
123
+ required,
124
+ );
125
+ if (!required && Object.keys(obj).length === 0) return undefined;
126
+ return obj;
127
+ }
128
+
129
+ // Priority 7: optional primitives — omit.
130
+ if (!required) return undefined;
131
+
132
+ // Priority 8: required primitives — legacy typed empty.
133
+ switch (type) {
134
+ case "array":
135
+ return seed !== undefined && seed !== null ? seed : [];
136
+ case "string":
137
+ return "";
138
+ case "boolean":
139
+ return false;
140
+ case "number":
141
+ case "integer":
142
+ default:
143
+ return null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Initialize an object type by recursing into its properties.
149
+ * Keys whose initialization returns `undefined` are omitted.
150
+ *
151
+ * `parentRequired` controls how schema.required propagates: a child is
152
+ * treated as required only if both its parent is required AND it appears in
153
+ * the parent's required array. Inside an optional ancestor the whole
154
+ * subtree collapses (except for author-forced values: const, default,
155
+ * single-value enum, or seeded values).
156
+ */
157
+ function initObject(
158
+ schema: Record<string, unknown>,
159
+ root: Record<string, unknown>,
160
+ seed: Record<string, unknown> | undefined,
161
+ parentRequired: boolean,
162
+ ): Record<string, unknown> {
163
+ const properties = schema.properties as
164
+ | Record<string, Record<string, unknown>>
165
+ | undefined;
166
+ if (!properties) {
167
+ return seed && typeof seed === "object" ? { ...seed } : {};
168
+ }
169
+
170
+ const requiredSet = new Set<string>(
171
+ Array.isArray(schema.required) ? (schema.required as string[]) : [],
172
+ );
173
+
174
+ const result: Record<string, unknown> = {};
175
+
176
+ for (const [key, propSchema] of Object.entries(properties)) {
177
+ const seedValue = seed && typeof seed === "object" ? seed[key] : undefined;
178
+ const effectiveRequired = parentRequired && requiredSet.has(key);
179
+ const value = initProperty(propSchema, root, seedValue, effectiveRequired);
180
+ if (value !== undefined) {
181
+ result[key] = value;
182
+ }
183
+ }
184
+
185
+ return result;
186
+ }
187
+
188
+ /**
189
+ * Handle oneOf schemas — pick the first variant and initialize it.
190
+ * `required` propagates: if the outer oneOf is required, the variant is
191
+ * materialized with typed-empty seeding for its required fields; if optional,
192
+ * only author-forced values (const / default / single-enum / seed) survive.
193
+ */
194
+ function initOneOf(
195
+ schema: Record<string, unknown>,
196
+ root: Record<string, unknown>,
197
+ seed: unknown,
198
+ required: boolean,
199
+ ): unknown {
200
+ const variants = schema.oneOf as Record<string, unknown>[];
201
+ if (!variants || variants.length === 0) return required ? null : undefined;
202
+
203
+ const first = variants[0];
204
+ if (!first) return required ? null : undefined;
205
+
206
+ const firstVariant = resolveRef(first, root);
207
+ return initProperty(firstVariant, root, seed, required);
208
+ }
@@ -1,3 +1,5 @@
1
+ import { deref as derefSchema, tryCombinatorBranches } from "./refs";
2
+
1
3
  /**
2
4
  * Projection utilities for navigating complex data structures
3
5
  * through a dot-separated path where numeric segments are array indices.
@@ -26,10 +28,7 @@ export function parseProjectionPath(path: string): ProjectionSegment[] {
26
28
  * Read a value from `data` by following the projection path.
27
29
  * Returns `undefined` if any segment along the path is missing.
28
30
  */
29
- export function getProjectedValue(
30
- data: unknown,
31
- path: string,
32
- ): unknown {
31
+ export function getProjectedValue(data: unknown, path: string): unknown {
33
32
  const segments = parseProjectionPath(path);
34
33
  let current: unknown = data;
35
34
 
@@ -85,7 +84,10 @@ function setAtPath(
85
84
  } else {
86
85
  // Object key — ensure we have an object
87
86
  const obj: Record<string, unknown> =
88
- current !== null && current !== undefined && typeof current === "object" && !Array.isArray(current)
87
+ current !== null &&
88
+ current !== undefined &&
89
+ typeof current === "object" &&
90
+ !Array.isArray(current)
89
91
  ? { ...(current as Record<string, unknown>) }
90
92
  : {};
91
93
  obj[seg] = setAtPath(obj[seg], segments, index + 1, value);
@@ -97,6 +99,10 @@ function setAtPath(
97
99
  * Resolve the schema at the projected path.
98
100
  * Numeric segments traverse into `items` (array item schema).
99
101
  * String segments traverse into `properties[segment]`.
102
+ *
103
+ * Dereferences `$ref` nodes transparently at every step, and falls through
104
+ * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
105
+ * directly — picks the first branch that satisfies the navigation.
100
106
  */
101
107
  export function getProjectedSchema(
102
108
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -106,31 +112,36 @@ export function getProjectedSchema(
106
112
  ): Record<string, any> {
107
113
  const segments = parseProjectionPath(path);
108
114
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
- let current: Record<string, any> = schema;
115
+ let current: Record<string, any> | undefined = schema;
110
116
 
111
117
  for (const seg of segments) {
118
+ current = derefSchema(current, schema);
112
119
  if (!current) return {};
113
120
 
114
- if (typeof seg === "number") {
115
- // Array index → traverse into items schema
116
- const items = current.items;
117
- if (items && typeof items === "object") {
118
- current = items as Record<string, unknown>;
119
- } else {
120
- return {};
121
+ const navigate = (
122
+ node: Record<string, unknown>,
123
+ ): Record<string, unknown> | undefined => {
124
+ if (typeof seg === "number") {
125
+ const items = (node as { items?: unknown }).items;
126
+ return items && typeof items === "object"
127
+ ? (items as Record<string, unknown>)
128
+ : undefined;
121
129
  }
122
- } else {
123
- // Object key → traverse into properties[key]
124
- const properties = current.properties as
130
+ const properties = (node as { properties?: unknown }).properties as
125
131
  | Record<string, Record<string, unknown>>
126
132
  | undefined;
127
- if (properties && properties[seg]) {
128
- current = properties[seg];
129
- } else {
130
- return {};
131
- }
133
+ if (properties && properties[seg]) return properties[seg];
134
+ return undefined;
135
+ };
136
+
137
+ let next = navigate(current);
138
+ if (next === undefined) {
139
+ next = tryCombinatorBranches(current, schema, navigate);
132
140
  }
141
+ if (!next) return {};
142
+ current = next;
133
143
  }
134
144
 
135
- return current;
145
+ const resolved = derefSchema(current, schema);
146
+ return resolved ?? {};
136
147
  }
@@ -0,0 +1,166 @@
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
+ /**
14
+ * Resolve a JSON pointer (`#/a/b/c`) against an object. Returns the node at
15
+ * that path, or `undefined` if any segment is missing or the pointer doesn't
16
+ * start with `#/`.
17
+ */
18
+ export function resolvePointer(
19
+ obj: Record<string, unknown>,
20
+ pointer: string,
21
+ ): unknown {
22
+ if (!pointer.startsWith("#/")) return undefined;
23
+ const parts = pointer.slice(2).split("/");
24
+ let current: unknown = obj;
25
+ for (const part of parts) {
26
+ if (current && typeof current === "object" && part in current) {
27
+ current = (current as Record<string, unknown>)[part];
28
+ } else {
29
+ return undefined;
30
+ }
31
+ }
32
+ return current;
33
+ }
34
+
35
+ /**
36
+ * Dereference a schema node along a chain of `$ref`s. Follows `A → B → C`
37
+ * transitively. A cycle (same `$ref` seen twice in one chain) returns the
38
+ * last unresolved node rather than hanging. An unresolvable pointer returns
39
+ * the current node unchanged.
40
+ */
41
+ export function resolveRef(
42
+ property: Record<string, unknown>,
43
+ root: Record<string, unknown>,
44
+ seen?: Set<string>,
45
+ ): Record<string, unknown> {
46
+ if (!property || typeof property !== "object") return property;
47
+
48
+ const ref = property.$ref as string | undefined;
49
+ if (!ref) return property;
50
+
51
+ const visited = seen ?? new Set<string>();
52
+ if (visited.has(ref)) return property;
53
+ visited.add(ref);
54
+
55
+ const resolved = resolvePointer(root, ref);
56
+ if (!resolved) return property;
57
+
58
+ return resolveRef(resolved as Record<string, unknown>, root, visited);
59
+ }
60
+
61
+ /**
62
+ * Convenience wrapper around `resolveRef` that starts a fresh cycle-detection
63
+ * set. Intended for schema walkers that need to dereference at every step;
64
+ * each call is an independent resolution.
65
+ */
66
+ export function deref(
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ node: Record<string, any> | undefined,
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ root: Record<string, any>,
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ ): Record<string, any> | undefined {
73
+ if (!node || typeof node !== "object") return node;
74
+ return resolveRef(node, root) as Record<string, unknown> | undefined;
75
+ }
76
+
77
+ /**
78
+ * Depth limit for combinator (oneOf/anyOf/allOf) branch descent. Schemas
79
+ * rarely nest combinators beyond one or two levels; this guard protects
80
+ * against pathological nesting and cycles (e.g. `oneOf: [$ref back to self]`).
81
+ */
82
+ const COMBINATOR_DEPTH_LIMIT = 8;
83
+
84
+ /**
85
+ * Try to navigate a segment through a schema node's combinator branches
86
+ * (`oneOf` / `anyOf` / `allOf`) when direct navigation has failed.
87
+ *
88
+ * Semantics: for walker purposes (renderer-tester matching), we only need
89
+ * ONE concrete schema that satisfies the next navigation step. First-match
90
+ * by structural shape wins, same convention as `initOneOf` uses for seeding.
91
+ *
92
+ * @param node the schema node to search (already dereffed by caller)
93
+ * @param root the root schema, for dereferencing branch `$ref`s
94
+ * @param tryFn predicate that attempts navigation on a candidate branch
95
+ * and returns the navigated value, or `undefined` if the
96
+ * branch doesn't have what the caller's looking for
97
+ * @param depth recursion depth (capped at `COMBINATOR_DEPTH_LIMIT`)
98
+ */
99
+ export function tryCombinatorBranches<T>(
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ node: Record<string, any> | undefined,
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ root: Record<string, any>,
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ tryFn: (candidate: Record<string, any>) => T | undefined,
106
+ depth = 0,
107
+ ): T | undefined {
108
+ if (depth > COMBINATOR_DEPTH_LIMIT) return undefined;
109
+ if (!node || typeof node !== "object") return undefined;
110
+
111
+ const branches = (node.oneOf || node.anyOf || node.allOf) as
112
+ | Record<string, unknown>[]
113
+ | undefined;
114
+ if (!Array.isArray(branches)) return undefined;
115
+
116
+ for (const raw of branches) {
117
+ const branch = deref(raw as Record<string, unknown>, root);
118
+ if (!branch || typeof branch !== "object") continue;
119
+
120
+ const direct = tryFn(branch);
121
+ if (direct !== undefined) return direct;
122
+
123
+ const nested = tryCombinatorBranches(branch, root, tryFn, depth + 1);
124
+ if (nested !== undefined) return nested;
125
+ }
126
+ return undefined;
127
+ }
128
+
129
+ /**
130
+ * Recursively dereference every `$ref` in a schema subtree, producing a
131
+ * concrete schema with no remaining refs. Cycles along any single chain are
132
+ * handled by leaving the first recursion back into a seen ref as the
133
+ * unresolved node — matching `resolveRef`'s semantics.
134
+ *
135
+ * Intended for use at API boundaries (e.g. `resolveScopeSchema`'s return)
136
+ * so downstream walkers can operate on self-contained schemas without
137
+ * needing the original root.
138
+ */
139
+ export function deepDeref(
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ node: any,
142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
+ root: Record<string, any>,
144
+ seen: Set<string> = new Set(),
145
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
146
+ ): any {
147
+ if (!node || typeof node !== "object") return node;
148
+ if (Array.isArray(node)) return node.map((n) => deepDeref(n, root, seen));
149
+
150
+ const ref = (node as Record<string, unknown>).$ref as string | undefined;
151
+ if (typeof ref === "string") {
152
+ if (seen.has(ref)) return node;
153
+ const resolved = resolvePointer(root, ref);
154
+ if (!resolved || typeof resolved !== "object") return node;
155
+ const nextSeen = new Set(seen);
156
+ nextSeen.add(ref);
157
+ return deepDeref(resolved, root, nextSeen);
158
+ }
159
+
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
+ const result: Record<string, any> = {};
162
+ for (const [key, value] of Object.entries(node)) {
163
+ result[key] = deepDeref(value, root, seen);
164
+ }
165
+ return result;
166
+ }