@narrative.io/jsonforms-provider-protocols 3.0.0-beta.17 → 3.0.0-beta.18

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.
@@ -28,9 +28,9 @@ export declare function setProjectedValue(data: unknown, path: string, value: un
28
28
  * Numeric segments traverse into `items` (array item schema).
29
29
  * String segments traverse into `properties[segment]`.
30
30
  *
31
- * `$ref` nodes are dereferenced transparently at every step, so projections
32
- * that cross a `$ref` boundary (e.g. `items: { $ref: "#/$defs/X" }`) resolve
33
- * to the target schema rather than collapsing to `{}`.
31
+ * Dereferences `$ref` nodes transparently at every step, and falls through
32
+ * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
33
+ * directly — picks the first branch that satisfies the navigation.
34
34
  */
35
35
  export declare function getProjectedSchema(schema: Record<string, any>, path: string): Record<string, any>;
36
36
  //# sourceMappingURL=projection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/core/projection.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAMrE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,OAAO,CAGT;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAEhC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GAEX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgCrB"}
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/core/projection.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AAEH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAMrE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,GACb,OAAO,CAGT;AAqCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAEhC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GAEX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAmCrB"}
@@ -1,4 +1,4 @@
1
- import { deref } from "./refs.js";
1
+ import { deref, tryCombinatorBranches } from "./refs.js";
2
2
  function parseProjectionPath(path) {
3
3
  if (!path) return [];
4
4
  return path.split(".").map((s) => {
@@ -49,21 +49,21 @@ function getProjectedSchema(schema, path) {
49
49
  for (const seg of segments) {
50
50
  current = deref(current, schema);
51
51
  if (!current) return {};
52
- if (typeof seg === "number") {
53
- const items = current.items;
54
- if (items && typeof items === "object") {
55
- current = items;
56
- } else {
57
- return {};
58
- }
59
- } else {
60
- const properties = current.properties;
61
- if (properties && properties[seg]) {
62
- current = properties[seg];
63
- } else {
64
- return {};
52
+ const navigate = (node) => {
53
+ if (typeof seg === "number") {
54
+ const items = node.items;
55
+ return items && typeof items === "object" ? items : void 0;
65
56
  }
57
+ const properties = node.properties;
58
+ if (properties && properties[seg]) return properties[seg];
59
+ return void 0;
60
+ };
61
+ let next = navigate(current);
62
+ if (next === void 0) {
63
+ next = tryCombinatorBranches(current, schema, navigate);
66
64
  }
65
+ if (!next) return {};
66
+ current = next;
67
67
  }
68
68
  const resolved = deref(current, schema);
69
69
  return resolved ?? {};
@@ -1 +1 @@
1
- {"version":3,"file":"projection.js","sources":["../../src/core/projection.ts"],"sourcesContent":["import { deref as derefSchema } from \"./refs\";\n\n/**\n * Projection utilities for navigating complex data structures\n * through a dot-separated path where numeric segments are array indices.\n *\n * Examples:\n * \"0\" → first element of an array\n * \"include\" → the `include` property of an object\n * \"0.video_rate_usd\" → nested property inside the first array element\n */\n\nexport type ProjectionSegment = string | number;\n\n/**\n * Parse a projection path string into typed segments.\n * Numeric strings become numbers (array indices), others stay as strings (object keys).\n */\nexport function parseProjectionPath(path: string): ProjectionSegment[] {\n if (!path) return [];\n return path.split(\".\").map((s) => {\n const n = Number(s);\n return Number.isInteger(n) && n >= 0 ? n : s;\n });\n}\n\n/**\n * Read a value from `data` by following the projection path.\n * Returns `undefined` if any segment along the path is missing.\n */\nexport function getProjectedValue(data: unknown, path: string): unknown {\n const segments = parseProjectionPath(path);\n let current: unknown = data;\n\n for (const seg of segments) {\n if (current === null || current === undefined) return undefined;\n\n if (typeof seg === \"number\") {\n if (!Array.isArray(current)) return undefined;\n current = current[seg];\n } else {\n if (typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n }\n\n return current;\n}\n\n/**\n * Immutably set a value at the projection path, preserving all sibling data.\n * Constructs missing intermediate structures (arrays for numeric segments, objects for string segments).\n */\nexport function setProjectedValue(\n data: unknown,\n path: string,\n value: unknown,\n): unknown {\n const segments = parseProjectionPath(path);\n return setAtPath(data, segments, 0, value);\n}\n\nfunction setAtPath(\n current: unknown,\n segments: ProjectionSegment[],\n index: number,\n value: unknown,\n): unknown {\n if (index === segments.length) {\n return value;\n }\n\n const seg = segments[index]!;\n\n if (typeof seg === \"number\") {\n // Array index — ensure we have an array\n const arr = Array.isArray(current) ? [...current] : [];\n // Pad array if index is out of bounds\n while (arr.length <= seg) {\n arr.push(undefined);\n }\n arr[seg] = setAtPath(arr[seg], segments, index + 1, value);\n return arr;\n } else {\n // Object key — ensure we have an object\n const obj: Record<string, unknown> =\n current !== null &&\n current !== undefined &&\n typeof current === \"object\" &&\n !Array.isArray(current)\n ? { ...(current as Record<string, unknown>) }\n : {};\n obj[seg] = setAtPath(obj[seg], segments, index + 1, value);\n return obj;\n }\n}\n\n/**\n * Resolve the schema at the projected path.\n * Numeric segments traverse into `items` (array item schema).\n * String segments traverse into `properties[segment]`.\n *\n * `$ref` nodes are dereferenced transparently at every step, so projections\n * that cross a `$ref` boundary (e.g. `items: { $ref: \"#/$defs/X\" }`) resolve\n * to the target schema rather than collapsing to `{}`.\n */\nexport function getProjectedSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema: Record<string, any>,\n path: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> {\n const segments = parseProjectionPath(path);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: Record<string, any> | undefined = schema;\n\n for (const seg of segments) {\n current = derefSchema(current, schema);\n if (!current) return {};\n\n if (typeof seg === \"number\") {\n // Array index traverse into items schema\n const items = current.items;\n if (items && typeof items === \"object\") {\n current = items as Record<string, unknown>;\n } else {\n return {};\n }\n } else {\n // Object key traverse into properties[key]\n const properties = current.properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (properties && properties[seg]) {\n current = properties[seg];\n } else {\n return {};\n }\n }\n }\n\n const resolved = derefSchema(current, schema);\n return resolved ?? {};\n}\n"],"names":["derefSchema"],"mappings":";AAkBO,SAAS,oBAAoB,MAAmC;AACrE,MAAI,CAAC,KAAM,QAAO,CAAA;AAClB,SAAO,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM;AAChC,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO,OAAO,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI;AAAA,EAC7C,CAAC;AACH;AAMO,SAAS,kBAAkB,MAAe,MAAuB;AACtE,QAAM,WAAW,oBAAoB,IAAI;AACzC,MAAI,UAAmB;AAEvB,aAAW,OAAO,UAAU;AAC1B,QAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AAEtD,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,gBAAU,QAAQ,GAAG;AAAA,IACvB,OAAO;AACL,UAAI,OAAO,YAAY,SAAU,QAAO;AACxC,gBAAW,QAAoC,GAAG;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,kBACd,MACA,MACA,OACS;AACT,QAAM,WAAW,oBAAoB,IAAI;AACzC,SAAO,UAAU,MAAM,UAAU,GAAG,KAAK;AAC3C;AAEA,SAAS,UACP,SACA,UACA,OACA,OACS;AACT,MAAI,UAAU,SAAS,QAAQ;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,SAAS,KAAK;AAE1B,MAAI,OAAO,QAAQ,UAAU;AAE3B,UAAM,MAAM,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAA;AAEpD,WAAO,IAAI,UAAU,KAAK;AACxB,UAAI,KAAK,MAAS;AAAA,IACpB;AACA,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT,OAAO;AAEL,UAAM,MACJ,YAAY,QACZ,YAAY,UACZ,OAAO,YAAY,YACnB,CAAC,MAAM,QAAQ,OAAO,IAClB,EAAE,GAAI,QAAA,IACN,CAAA;AACN,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT;AACF;AAWO,SAAS,mBAEd,QACA,MAEqB;AACrB,QAAM,WAAW,oBAAoB,IAAI;AAEzC,MAAI,UAA2C;AAE/C,aAAW,OAAO,UAAU;AAC1B,cAAUA,MAAY,SAAS,MAAM;AACrC,QAAI,CAAC,QAAS,QAAO,CAAA;AAErB,QAAI,OAAO,QAAQ,UAAU;AAE3B,YAAM,QAAQ,QAAQ;AACtB,UAAI,SAAS,OAAO,UAAU,UAAU;AACtC,kBAAU;AAAA,MACZ,OAAO;AACL,eAAO,CAAA;AAAA,MACT;AAAA,IACF,OAAO;AAEL,YAAM,aAAa,QAAQ;AAG3B,UAAI,cAAc,WAAW,GAAG,GAAG;AACjC,kBAAU,WAAW,GAAG;AAAA,MAC1B,OAAO;AACL,eAAO,CAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAWA,MAAY,SAAS,MAAM;AAC5C,SAAO,YAAY,CAAA;AACrB;"}
1
+ {"version":3,"file":"projection.js","sources":["../../src/core/projection.ts"],"sourcesContent":["import {\n deref as derefSchema,\n tryCombinatorBranches,\n} from \"./refs\";\n\n/**\n * Projection utilities for navigating complex data structures\n * through a dot-separated path where numeric segments are array indices.\n *\n * Examples:\n * \"0\" → first element of an array\n * \"include\" → the `include` property of an object\n * \"0.video_rate_usd\" → nested property inside the first array element\n */\n\nexport type ProjectionSegment = string | number;\n\n/**\n * Parse a projection path string into typed segments.\n * Numeric strings become numbers (array indices), others stay as strings (object keys).\n */\nexport function parseProjectionPath(path: string): ProjectionSegment[] {\n if (!path) return [];\n return path.split(\".\").map((s) => {\n const n = Number(s);\n return Number.isInteger(n) && n >= 0 ? n : s;\n });\n}\n\n/**\n * Read a value from `data` by following the projection path.\n * Returns `undefined` if any segment along the path is missing.\n */\nexport function getProjectedValue(data: unknown, path: string): unknown {\n const segments = parseProjectionPath(path);\n let current: unknown = data;\n\n for (const seg of segments) {\n if (current === null || current === undefined) return undefined;\n\n if (typeof seg === \"number\") {\n if (!Array.isArray(current)) return undefined;\n current = current[seg];\n } else {\n if (typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[seg];\n }\n }\n\n return current;\n}\n\n/**\n * Immutably set a value at the projection path, preserving all sibling data.\n * Constructs missing intermediate structures (arrays for numeric segments, objects for string segments).\n */\nexport function setProjectedValue(\n data: unknown,\n path: string,\n value: unknown,\n): unknown {\n const segments = parseProjectionPath(path);\n return setAtPath(data, segments, 0, value);\n}\n\nfunction setAtPath(\n current: unknown,\n segments: ProjectionSegment[],\n index: number,\n value: unknown,\n): unknown {\n if (index === segments.length) {\n return value;\n }\n\n const seg = segments[index]!;\n\n if (typeof seg === \"number\") {\n // Array index — ensure we have an array\n const arr = Array.isArray(current) ? [...current] : [];\n // Pad array if index is out of bounds\n while (arr.length <= seg) {\n arr.push(undefined);\n }\n arr[seg] = setAtPath(arr[seg], segments, index + 1, value);\n return arr;\n } else {\n // Object key — ensure we have an object\n const obj: Record<string, unknown> =\n current !== null &&\n current !== undefined &&\n typeof current === \"object\" &&\n !Array.isArray(current)\n ? { ...(current as Record<string, unknown>) }\n : {};\n obj[seg] = setAtPath(obj[seg], segments, index + 1, value);\n return obj;\n }\n}\n\n/**\n * Resolve the schema at the projected path.\n * Numeric segments traverse into `items` (array item schema).\n * String segments traverse into `properties[segment]`.\n *\n * Dereferences `$ref` nodes transparently at every step, and falls through\n * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve\n * directly — picks the first branch that satisfies the navigation.\n */\nexport function getProjectedSchema(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n schema: Record<string, any>,\n path: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> {\n const segments = parseProjectionPath(path);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: Record<string, any> | undefined = schema;\n\n for (const seg of segments) {\n current = derefSchema(current, schema);\n if (!current) return {};\n\n const navigate = (\n node: Record<string, unknown>,\n ): Record<string, unknown> | undefined => {\n if (typeof seg === \"number\") {\n const items = (node as { items?: unknown }).items;\n return items && typeof items === \"object\"\n ? (items as Record<string, unknown>)\n : undefined;\n }\n const properties = (node as { properties?: unknown }).properties as\n | Record<string, Record<string, unknown>>\n | undefined;\n if (properties && properties[seg]) return properties[seg];\n return undefined;\n };\n\n let next = navigate(current);\n if (next === undefined) {\n next = tryCombinatorBranches(current, schema, navigate);\n }\n if (!next) return {};\n current = next;\n }\n\n const resolved = derefSchema(current, schema);\n return resolved ?? {};\n}\n"],"names":["derefSchema"],"mappings":";AAqBO,SAAS,oBAAoB,MAAmC;AACrE,MAAI,CAAC,KAAM,QAAO,CAAA;AAClB,SAAO,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM;AAChC,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO,OAAO,UAAU,CAAC,KAAK,KAAK,IAAI,IAAI;AAAA,EAC7C,CAAC;AACH;AAMO,SAAS,kBAAkB,MAAe,MAAuB;AACtE,QAAM,WAAW,oBAAoB,IAAI;AACzC,MAAI,UAAmB;AAEvB,aAAW,OAAO,UAAU;AAC1B,QAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AAEtD,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,gBAAU,QAAQ,GAAG;AAAA,IACvB,OAAO;AACL,UAAI,OAAO,YAAY,SAAU,QAAO;AACxC,gBAAW,QAAoC,GAAG;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,kBACd,MACA,MACA,OACS;AACT,QAAM,WAAW,oBAAoB,IAAI;AACzC,SAAO,UAAU,MAAM,UAAU,GAAG,KAAK;AAC3C;AAEA,SAAS,UACP,SACA,UACA,OACA,OACS;AACT,MAAI,UAAU,SAAS,QAAQ;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,SAAS,KAAK;AAE1B,MAAI,OAAO,QAAQ,UAAU;AAE3B,UAAM,MAAM,MAAM,QAAQ,OAAO,IAAI,CAAC,GAAG,OAAO,IAAI,CAAA;AAEpD,WAAO,IAAI,UAAU,KAAK;AACxB,UAAI,KAAK,MAAS;AAAA,IACpB;AACA,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT,OAAO;AAEL,UAAM,MACJ,YAAY,QACZ,YAAY,UACZ,OAAO,YAAY,YACnB,CAAC,MAAM,QAAQ,OAAO,IAClB,EAAE,GAAI,QAAA,IACN,CAAA;AACN,QAAI,GAAG,IAAI,UAAU,IAAI,GAAG,GAAG,UAAU,QAAQ,GAAG,KAAK;AACzD,WAAO;AAAA,EACT;AACF;AAWO,SAAS,mBAEd,QACA,MAEqB;AACrB,QAAM,WAAW,oBAAoB,IAAI;AAEzC,MAAI,UAA2C;AAE/C,aAAW,OAAO,UAAU;AAC1B,cAAUA,MAAY,SAAS,MAAM;AACrC,QAAI,CAAC,QAAS,QAAO,CAAA;AAErB,UAAM,WAAW,CACf,SACwC;AACxC,UAAI,OAAO,QAAQ,UAAU;AAC3B,cAAM,QAAS,KAA6B;AAC5C,eAAO,SAAS,OAAO,UAAU,WAC5B,QACD;AAAA,MACN;AACA,YAAM,aAAc,KAAkC;AAGtD,UAAI,cAAc,WAAW,GAAG,EAAG,QAAO,WAAW,GAAG;AACxD,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,SAAS,OAAO;AAC3B,QAAI,SAAS,QAAW;AACtB,aAAO,sBAAsB,SAAS,QAAQ,QAAQ;AAAA,IACxD;AACA,QAAI,CAAC,KAAM,QAAO,CAAA;AAClB,cAAU;AAAA,EACZ;AAEA,QAAM,WAAWA,MAAY,SAAS,MAAM;AAC5C,SAAO,YAAY,CAAA;AACrB;"}
@@ -28,6 +28,22 @@ export declare function resolveRef(property: Record<string, unknown>, root: Reco
28
28
  * each call is an independent resolution.
29
29
  */
30
30
  export declare function deref(node: Record<string, any> | undefined, root: Record<string, any>): Record<string, any> | undefined;
31
+ /**
32
+ * Try to navigate a segment through a schema node's combinator branches
33
+ * (`oneOf` / `anyOf` / `allOf`) when direct navigation has failed.
34
+ *
35
+ * Semantics: for walker purposes (renderer-tester matching), we only need
36
+ * ONE concrete schema that satisfies the next navigation step. First-match
37
+ * by structural shape wins, same convention as `initOneOf` uses for seeding.
38
+ *
39
+ * @param node the schema node to search (already dereffed by caller)
40
+ * @param root the root schema, for dereferencing branch `$ref`s
41
+ * @param tryFn predicate that attempts navigation on a candidate branch
42
+ * and returns the navigated value, or `undefined` if the
43
+ * branch doesn't have what the caller's looking for
44
+ * @param depth recursion depth (capped at `COMBINATOR_DEPTH_LIMIT`)
45
+ */
46
+ export declare function tryCombinatorBranches<T>(node: Record<string, any> | undefined, root: Record<string, any>, tryFn: (candidate: Record<string, any>) => T | undefined, depth?: number): T | undefined;
31
47
  /**
32
48
  * Recursively dereference every `$ref` in a schema subtree, producing a
33
49
  * concrete schema with no remaining refs. Cycles along any single chain are
@@ -1 +1 @@
1
- {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/core/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,GACd,OAAO,CAYT;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAEnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAErC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAExB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAGjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAEvB,IAAI,EAAE,GAAG,EAET,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,IAAI,GAAE,GAAG,CAAC,MAAM,CAAa,GAE5B,GAAG,CAoBL"}
1
+ {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/core/refs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,GACd,OAAO,CAYT;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAEnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAErC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAExB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAGjC;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAErC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAErC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAEzB,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,EACxD,KAAK,SAAI,GACR,CAAC,GAAG,SAAS,CAoBf;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAEvB,IAAI,EAAE,GAAG,EAET,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,IAAI,GAAE,GAAG,CAAC,MAAM,CAAa,GAE5B,GAAG,CAoBL"}
package/dist/core/refs.js CHANGED
@@ -26,6 +26,22 @@ function deref(node, root) {
26
26
  if (!node || typeof node !== "object") return node;
27
27
  return resolveRef(node, root);
28
28
  }
29
+ const COMBINATOR_DEPTH_LIMIT = 8;
30
+ function tryCombinatorBranches(node, root, tryFn, depth = 0) {
31
+ if (depth > COMBINATOR_DEPTH_LIMIT) return void 0;
32
+ if (!node || typeof node !== "object") return void 0;
33
+ const branches = node.oneOf || node.anyOf || node.allOf;
34
+ if (!Array.isArray(branches)) return void 0;
35
+ for (const raw of branches) {
36
+ const branch = deref(raw, root);
37
+ if (!branch || typeof branch !== "object") continue;
38
+ const direct = tryFn(branch);
39
+ if (direct !== void 0) return direct;
40
+ const nested = tryCombinatorBranches(branch, root, tryFn, depth + 1);
41
+ if (nested !== void 0) return nested;
42
+ }
43
+ return void 0;
44
+ }
29
45
  function deepDeref(node, root, seen = /* @__PURE__ */ new Set()) {
30
46
  if (!node || typeof node !== "object") return node;
31
47
  if (Array.isArray(node)) return node.map((n) => deepDeref(n, root, seen));
@@ -48,6 +64,7 @@ export {
48
64
  deepDeref,
49
65
  deref,
50
66
  resolvePointer,
51
- resolveRef
67
+ resolveRef,
68
+ tryCombinatorBranches
52
69
  };
53
70
  //# sourceMappingURL=refs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"refs.js","sources":["../../src/core/refs.ts"],"sourcesContent":["/**\n * JSON Schema $ref resolution helpers.\n *\n * Supports the pointer grammar used across consumer schemas:\n * - \"#/$defs/Name\"\n * - \"#/properties/foo/items\"\n *\n * External refs (URIs, file refs) are intentionally out of scope. A ref that\n * doesn't resolve leaves the node untouched — callers can still inspect the\n * unresolved `$ref` for debugging.\n */\n\n/**\n * Resolve a JSON pointer (`#/a/b/c`) against an object. Returns the node at\n * that path, or `undefined` if any segment is missing or the pointer doesn't\n * start with `#/`.\n */\nexport function 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 * Dereference a schema node along a chain of `$ref`s. Follows `A → B → C`\n * transitively. A cycle (same `$ref` seen twice in one chain) returns the\n * last unresolved node rather than hanging. An unresolvable pointer returns\n * the current node unchanged.\n */\nexport function 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 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 return resolveRef(resolved as Record<string, unknown>, root, visited);\n}\n\n/**\n * Convenience wrapper around `resolveRef` that starts a fresh cycle-detection\n * set. Intended for schema walkers that need to dereference at every step;\n * each call is an independent resolution.\n */\nexport function deref(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n node: Record<string, any> | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n root: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!node || typeof node !== \"object\") return node;\n return resolveRef(node, root) as Record<string, unknown> | undefined;\n}\n\n/**\n * Recursively dereference every `$ref` in a schema subtree, producing a\n * concrete schema with no remaining refs. Cycles along any single chain are\n * handled by leaving the first recursion back into a seen ref as the\n * unresolved node — matching `resolveRef`'s semantics.\n *\n * Intended for use at API boundaries (e.g. `resolveScopeSchema`'s return)\n * so downstream walkers can operate on self-contained schemas without\n * needing the original root.\n */\nexport function deepDeref(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n node: any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n root: Record<string, any>,\n seen: Set<string> = new Set(),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any {\n if (!node || typeof node !== \"object\") return node;\n if (Array.isArray(node)) return node.map((n) => deepDeref(n, root, seen));\n\n const ref = (node as Record<string, unknown>).$ref as string | undefined;\n if (typeof ref === \"string\") {\n if (seen.has(ref)) return node;\n const resolved = resolvePointer(root, ref);\n if (!resolved || typeof resolved !== \"object\") return node;\n const nextSeen = new Set(seen);\n nextSeen.add(ref);\n return deepDeref(resolved, root, nextSeen);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(node)) {\n result[key] = deepDeref(value, root, seen);\n }\n return result;\n}\n"],"names":[],"mappings":"AAiBO,SAAS,eACd,KACA,SACS;AACT,MAAI,CAAC,QAAQ,WAAW,IAAI,EAAG,QAAO;AACtC,QAAM,QAAQ,QAAQ,MAAM,CAAC,EAAE,MAAM,GAAG;AACxC,MAAI,UAAmB;AACvB,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,OAAO,YAAY,YAAY,QAAQ,SAAS;AAC7D,gBAAW,QAAoC,IAAI;AAAA,IACrD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,WACd,UACA,MACA,MACyB;AACzB,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AAEtD,QAAM,MAAM,SAAS;AACrB,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,UAAU,QAAQ,oBAAI,IAAA;AAC5B,MAAI,QAAQ,IAAI,GAAG,EAAG,QAAO;AAC7B,UAAQ,IAAI,GAAG;AAEf,QAAM,WAAW,eAAe,MAAM,GAAG;AACzC,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO,WAAW,UAAqC,MAAM,OAAO;AACtE;AAOO,SAAS,MAEd,MAEA,MAEiC;AACjC,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,SAAO,WAAW,MAAM,IAAI;AAC9B;AAYO,SAAS,UAEd,MAEA,MACA,OAAoB,oBAAI,OAEnB;AACL,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,CAAC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC;AAExE,QAAM,MAAO,KAAiC;AAC9C,MAAI,OAAO,QAAQ,UAAU;AAC3B,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,UAAM,WAAW,eAAe,MAAM,GAAG;AACzC,QAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,UAAM,WAAW,IAAI,IAAI,IAAI;AAC7B,aAAS,IAAI,GAAG;AAChB,WAAO,UAAU,UAAU,MAAM,QAAQ;AAAA,EAC3C;AAGA,QAAM,SAA8B,CAAA;AACpC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,WAAO,GAAG,IAAI,UAAU,OAAO,MAAM,IAAI;AAAA,EAC3C;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"refs.js","sources":["../../src/core/refs.ts"],"sourcesContent":["/**\n * JSON Schema $ref resolution helpers.\n *\n * Supports the pointer grammar used across consumer schemas:\n * - \"#/$defs/Name\"\n * - \"#/properties/foo/items\"\n *\n * External refs (URIs, file refs) are intentionally out of scope. A ref that\n * doesn't resolve leaves the node untouched — callers can still inspect the\n * unresolved `$ref` for debugging.\n */\n\n/**\n * Resolve a JSON pointer (`#/a/b/c`) against an object. Returns the node at\n * that path, or `undefined` if any segment is missing or the pointer doesn't\n * start with `#/`.\n */\nexport function 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 * Dereference a schema node along a chain of `$ref`s. Follows `A → B → C`\n * transitively. A cycle (same `$ref` seen twice in one chain) returns the\n * last unresolved node rather than hanging. An unresolvable pointer returns\n * the current node unchanged.\n */\nexport function 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 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 return resolveRef(resolved as Record<string, unknown>, root, visited);\n}\n\n/**\n * Convenience wrapper around `resolveRef` that starts a fresh cycle-detection\n * set. Intended for schema walkers that need to dereference at every step;\n * each call is an independent resolution.\n */\nexport function deref(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n node: Record<string, any> | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n root: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!node || typeof node !== \"object\") return node;\n return resolveRef(node, root) as Record<string, unknown> | undefined;\n}\n\n/**\n * Depth limit for combinator (oneOf/anyOf/allOf) branch descent. Schemas\n * rarely nest combinators beyond one or two levels; this guard protects\n * against pathological nesting and cycles (e.g. `oneOf: [$ref back to self]`).\n */\nconst COMBINATOR_DEPTH_LIMIT = 8;\n\n/**\n * Try to navigate a segment through a schema node's combinator branches\n * (`oneOf` / `anyOf` / `allOf`) when direct navigation has failed.\n *\n * Semantics: for walker purposes (renderer-tester matching), we only need\n * ONE concrete schema that satisfies the next navigation step. First-match\n * by structural shape wins, same convention as `initOneOf` uses for seeding.\n *\n * @param node the schema node to search (already dereffed by caller)\n * @param root the root schema, for dereferencing branch `$ref`s\n * @param tryFn predicate that attempts navigation on a candidate branch\n * and returns the navigated value, or `undefined` if the\n * branch doesn't have what the caller's looking for\n * @param depth recursion depth (capped at `COMBINATOR_DEPTH_LIMIT`)\n */\nexport function tryCombinatorBranches<T>(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n node: Record<string, any> | undefined,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n root: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n tryFn: (candidate: Record<string, any>) => T | undefined,\n depth = 0,\n): T | undefined {\n if (depth > COMBINATOR_DEPTH_LIMIT) return undefined;\n if (!node || typeof node !== \"object\") return undefined;\n\n const branches = (node.oneOf || node.anyOf || node.allOf) as\n | Record<string, unknown>[]\n | undefined;\n if (!Array.isArray(branches)) return undefined;\n\n for (const raw of branches) {\n const branch = deref(raw as Record<string, unknown>, root);\n if (!branch || typeof branch !== \"object\") continue;\n\n const direct = tryFn(branch);\n if (direct !== undefined) return direct;\n\n const nested = tryCombinatorBranches(branch, root, tryFn, depth + 1);\n if (nested !== undefined) return nested;\n }\n return undefined;\n}\n\n/**\n * Recursively dereference every `$ref` in a schema subtree, producing a\n * concrete schema with no remaining refs. Cycles along any single chain are\n * handled by leaving the first recursion back into a seen ref as the\n * unresolved node — matching `resolveRef`'s semantics.\n *\n * Intended for use at API boundaries (e.g. `resolveScopeSchema`'s return)\n * so downstream walkers can operate on self-contained schemas without\n * needing the original root.\n */\nexport function deepDeref(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n node: any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n root: Record<string, any>,\n seen: Set<string> = new Set(),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any {\n if (!node || typeof node !== \"object\") return node;\n if (Array.isArray(node)) return node.map((n) => deepDeref(n, root, seen));\n\n const ref = (node as Record<string, unknown>).$ref as string | undefined;\n if (typeof ref === \"string\") {\n if (seen.has(ref)) return node;\n const resolved = resolvePointer(root, ref);\n if (!resolved || typeof resolved !== \"object\") return node;\n const nextSeen = new Set(seen);\n nextSeen.add(ref);\n return deepDeref(resolved, root, nextSeen);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result: Record<string, any> = {};\n for (const [key, value] of Object.entries(node)) {\n result[key] = deepDeref(value, root, seen);\n }\n return result;\n}\n"],"names":[],"mappings":"AAiBO,SAAS,eACd,KACA,SACS;AACT,MAAI,CAAC,QAAQ,WAAW,IAAI,EAAG,QAAO;AACtC,QAAM,QAAQ,QAAQ,MAAM,CAAC,EAAE,MAAM,GAAG;AACxC,MAAI,UAAmB;AACvB,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,OAAO,YAAY,YAAY,QAAQ,SAAS;AAC7D,gBAAW,QAAoC,IAAI;AAAA,IACrD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,WACd,UACA,MACA,MACyB;AACzB,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AAEtD,QAAM,MAAM,SAAS;AACrB,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,UAAU,QAAQ,oBAAI,IAAA;AAC5B,MAAI,QAAQ,IAAI,GAAG,EAAG,QAAO;AAC7B,UAAQ,IAAI,GAAG;AAEf,QAAM,WAAW,eAAe,MAAM,GAAG;AACzC,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO,WAAW,UAAqC,MAAM,OAAO;AACtE;AAOO,SAAS,MAEd,MAEA,MAEiC;AACjC,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,SAAO,WAAW,MAAM,IAAI;AAC9B;AAOA,MAAM,yBAAyB;AAiBxB,SAAS,sBAEd,MAEA,MAEA,OACA,QAAQ,GACO;AACf,MAAI,QAAQ,uBAAwB,QAAO;AAC3C,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAE9C,QAAM,WAAY,KAAK,SAAS,KAAK,SAAS,KAAK;AAGnD,MAAI,CAAC,MAAM,QAAQ,QAAQ,EAAG,QAAO;AAErC,aAAW,OAAO,UAAU;AAC1B,UAAM,SAAS,MAAM,KAAgC,IAAI;AACzD,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU;AAE3C,UAAM,SAAS,MAAM,MAAM;AAC3B,QAAI,WAAW,OAAW,QAAO;AAEjC,UAAM,SAAS,sBAAsB,QAAQ,MAAM,OAAO,QAAQ,CAAC;AACnE,QAAI,WAAW,OAAW,QAAO;AAAA,EACnC;AACA,SAAO;AACT;AAYO,SAAS,UAEd,MAEA,MACA,OAAoB,oBAAI,OAEnB;AACL,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,CAAC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC;AAExE,QAAM,MAAO,KAAiC;AAC9C,MAAI,OAAO,QAAQ,UAAU;AAC3B,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,UAAM,WAAW,eAAe,MAAM,GAAG;AACzC,QAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,UAAM,WAAW,IAAI,IAAI,IAAI;AAC7B,aAAS,IAAI,GAAG;AAChB,WAAO,UAAU,UAAU,MAAM,QAAQ;AAAA,EAC3C;AAGA,QAAM,SAA8B,CAAA;AACpC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,WAAO,GAAG,IAAI,UAAU,OAAO,MAAM,IAAI;AAAA,EAC3C;AACA,SAAO;AACT;"}
@@ -7,11 +7,11 @@
7
7
  * - "items" segments navigate into array `.items`
8
8
  * - all other segments index directly into the current object
9
9
  *
10
- * `$ref` nodes are dereferenced transparently at every step, so scopes that
11
- * cross a `$ref` boundary (e.g. `items: { $ref: "#/$defs/X" }`) resolve to
12
- * the target schema rather than returning `{}`. The returned schema is also
13
- * deep-dereferenced so downstream walkers (e.g. `getProjectedSchema`) can
14
- * operate on a self-contained sub-schema without needing the original root.
10
+ * Dereferences `$ref` nodes transparently at every step, and falls through
11
+ * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
12
+ * directly picks the first branch that satisfies the navigation. The
13
+ * returned schema is deep-dereferenced so downstream walkers can operate on
14
+ * a self-contained sub-schema without needing the original root.
15
15
  */
16
16
  export declare function resolveScopeSchema(scope: string, rootSchema: Record<string, any>): Record<string, any> | undefined;
17
17
  //# sourceMappingURL=resolveScope.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveScope.d.ts","sourceRoot":"","sources":["../../src/core/resolveScope.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EAEb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAE9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAyBjC"}
1
+ {"version":3,"file":"resolveScope.d.ts","sourceRoot":"","sources":["../../src/core/resolveScope.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EAEb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAE9B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CA+BjC"}
@@ -1,4 +1,4 @@
1
- import { deepDeref, deref } from "./refs.js";
1
+ import { deepDeref, deref, tryCombinatorBranches } from "./refs.js";
2
2
  function resolveScopeSchema(scope, rootSchema) {
3
3
  if (!scope || !rootSchema) return void 0;
4
4
  const path = scope.replace(/^#\/?/, "");
@@ -8,13 +8,17 @@ function resolveScopeSchema(scope, rootSchema) {
8
8
  for (const segment of segments) {
9
9
  current = deref(current, rootSchema);
10
10
  if (!current || typeof current !== "object") return void 0;
11
- if (segment === "properties") {
12
- current = current.properties;
13
- } else if (segment === "items") {
14
- current = current.items;
15
- } else {
16
- current = current[segment];
11
+ const navigate = (node) => {
12
+ if (segment === "properties") return node.properties;
13
+ if (segment === "items") return node.items;
14
+ return node[segment];
15
+ };
16
+ let next = navigate(current);
17
+ if (next === void 0) {
18
+ next = tryCombinatorBranches(current, rootSchema, navigate);
17
19
  }
20
+ if (next === void 0) return void 0;
21
+ current = next;
18
22
  }
19
23
  return deepDeref(current, rootSchema);
20
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolveScope.js","sources":["../../src/core/resolveScope.ts"],"sourcesContent":["import { deepDeref, deref } from \"./refs\";\n\n/**\n * Resolve a JSON Forms scope path to its schema within a root schema.\n * Handles nested paths like \"#/properties/parent/properties/child\".\n *\n * Follows JSON Schema structure:\n * - \"properties\" segments navigate into object `.properties`\n * - \"items\" segments navigate into array `.items`\n * - all other segments index directly into the current object\n *\n * `$ref` nodes are dereferenced transparently at every step, so scopes that\n * cross a `$ref` boundary (e.g. `items: { $ref: \"#/$defs/X\" }`) resolve to\n * the target schema rather than returning `{}`. The returned schema is also\n * deep-dereferenced so downstream walkers (e.g. `getProjectedSchema`) can\n * operate on a self-contained sub-schema without needing the original root.\n */\nexport function resolveScopeSchema(\n scope: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rootSchema: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!scope || !rootSchema) return undefined;\n\n // Remove the leading \"#/\" and split into segments\n const path = scope.replace(/^#\\/?/, \"\");\n if (!path) return deepDeref(rootSchema, rootSchema);\n\n const segments = path.split(\"/\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = rootSchema;\n\n for (const segment of segments) {\n current = deref(current, rootSchema);\n if (!current || typeof current !== \"object\") return undefined;\n\n if (segment === \"properties\") {\n current = current.properties;\n } else if (segment === \"items\") {\n current = current.items;\n } else {\n current = current[segment];\n }\n }\n\n return deepDeref(current, rootSchema);\n}\n"],"names":[],"mappings":";AAiBO,SAAS,mBACd,OAEA,YAEiC;AACjC,MAAI,CAAC,SAAS,CAAC,WAAY,QAAO;AAGlC,QAAM,OAAO,MAAM,QAAQ,SAAS,EAAE;AACtC,MAAI,CAAC,KAAM,QAAO,UAAU,YAAY,UAAU;AAElD,QAAM,WAAW,KAAK,MAAM,GAAG;AAE/B,MAAI,UAAe;AAEnB,aAAW,WAAW,UAAU;AAC9B,cAAU,MAAM,SAAS,UAAU;AACnC,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AAEpD,QAAI,YAAY,cAAc;AAC5B,gBAAU,QAAQ;AAAA,IACpB,WAAW,YAAY,SAAS;AAC9B,gBAAU,QAAQ;AAAA,IACpB,OAAO;AACL,gBAAU,QAAQ,OAAO;AAAA,IAC3B;AAAA,EACF;AAEA,SAAO,UAAU,SAAS,UAAU;AACtC;"}
1
+ {"version":3,"file":"resolveScope.js","sources":["../../src/core/resolveScope.ts"],"sourcesContent":["import { deepDeref, deref, tryCombinatorBranches } from \"./refs\";\n\n/**\n * Resolve a JSON Forms scope path to its schema within a root schema.\n * Handles nested paths like \"#/properties/parent/properties/child\".\n *\n * Follows JSON Schema structure:\n * - \"properties\" segments navigate into object `.properties`\n * - \"items\" segments navigate into array `.items`\n * - all other segments index directly into the current object\n *\n * Dereferences `$ref` nodes transparently at every step, and falls through\n * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve\n * directly — picks the first branch that satisfies the navigation. The\n * returned schema is deep-dereferenced so downstream walkers can operate on\n * a self-contained sub-schema without needing the original root.\n */\nexport function resolveScopeSchema(\n scope: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n rootSchema: Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): Record<string, any> | undefined {\n if (!scope || !rootSchema) return undefined;\n\n // Remove the leading \"#/\" and split into segments\n const path = scope.replace(/^#\\/?/, \"\");\n if (!path) return deepDeref(rootSchema, rootSchema);\n\n const segments = path.split(\"/\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = rootSchema;\n\n for (const segment of segments) {\n current = deref(current, rootSchema);\n if (!current || typeof current !== \"object\") return undefined;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const navigate = (node: Record<string, any>): unknown => {\n if (segment === \"properties\") return node.properties;\n if (segment === \"items\") return node.items;\n return node[segment];\n };\n\n let next = navigate(current);\n if (next === undefined) {\n next = tryCombinatorBranches(current, rootSchema, navigate);\n }\n if (next === undefined) return undefined;\n current = next;\n }\n\n return deepDeref(current, rootSchema);\n}\n"],"names":[],"mappings":";AAiBO,SAAS,mBACd,OAEA,YAEiC;AACjC,MAAI,CAAC,SAAS,CAAC,WAAY,QAAO;AAGlC,QAAM,OAAO,MAAM,QAAQ,SAAS,EAAE;AACtC,MAAI,CAAC,KAAM,QAAO,UAAU,YAAY,UAAU;AAElD,QAAM,WAAW,KAAK,MAAM,GAAG;AAE/B,MAAI,UAAe;AAEnB,aAAW,WAAW,UAAU;AAC9B,cAAU,MAAM,SAAS,UAAU;AACnC,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AAGpD,UAAM,WAAW,CAAC,SAAuC;AACvD,UAAI,YAAY,aAAc,QAAO,KAAK;AAC1C,UAAI,YAAY,QAAS,QAAO,KAAK;AACrC,aAAO,KAAK,OAAO;AAAA,IACrB;AAEA,QAAI,OAAO,SAAS,OAAO;AAC3B,QAAI,SAAS,QAAW;AACtB,aAAO,sBAAsB,SAAS,YAAY,QAAQ;AAAA,IAC5D;AACA,QAAI,SAAS,OAAW,QAAO;AAC/B,cAAU;AAAA,EACZ;AAEA,SAAO,UAAU,SAAS,UAAU;AACtC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/jsonforms-provider-protocols",
3
- "version": "3.0.0-beta.17",
3
+ "version": "3.0.0-beta.18",
4
4
  "description": "Dynamic data provider capabilities for JSONForms with Vue 3 integration",
5
5
  "type": "module",
6
6
  "author": "Narrative I/O",
@@ -1,4 +1,7 @@
1
- import { deref as derefSchema } from "./refs";
1
+ import {
2
+ deref as derefSchema,
3
+ tryCombinatorBranches,
4
+ } from "./refs";
2
5
 
3
6
  /**
4
7
  * Projection utilities for navigating complex data structures
@@ -100,9 +103,9 @@ function setAtPath(
100
103
  * Numeric segments traverse into `items` (array item schema).
101
104
  * String segments traverse into `properties[segment]`.
102
105
  *
103
- * `$ref` nodes are dereferenced transparently at every step, so projections
104
- * that cross a `$ref` boundary (e.g. `items: { $ref: "#/$defs/X" }`) resolve
105
- * to the target schema rather than collapsing to `{}`.
106
+ * Dereferences `$ref` nodes transparently at every step, and falls through
107
+ * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
108
+ * directly — picks the first branch that satisfies the navigation.
106
109
  */
107
110
  export function getProjectedSchema(
108
111
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -118,25 +121,28 @@ export function getProjectedSchema(
118
121
  current = derefSchema(current, schema);
119
122
  if (!current) return {};
120
123
 
121
- if (typeof seg === "number") {
122
- // Array index → traverse into items schema
123
- const items = current.items;
124
- if (items && typeof items === "object") {
125
- current = items as Record<string, unknown>;
126
- } else {
127
- return {};
124
+ const navigate = (
125
+ node: Record<string, unknown>,
126
+ ): Record<string, unknown> | undefined => {
127
+ if (typeof seg === "number") {
128
+ const items = (node as { items?: unknown }).items;
129
+ return items && typeof items === "object"
130
+ ? (items as Record<string, unknown>)
131
+ : undefined;
128
132
  }
129
- } else {
130
- // Object key → traverse into properties[key]
131
- const properties = current.properties as
133
+ const properties = (node as { properties?: unknown }).properties as
132
134
  | Record<string, Record<string, unknown>>
133
135
  | undefined;
134
- if (properties && properties[seg]) {
135
- current = properties[seg];
136
- } else {
137
- return {};
138
- }
136
+ if (properties && properties[seg]) return properties[seg];
137
+ return undefined;
138
+ };
139
+
140
+ let next = navigate(current);
141
+ if (next === undefined) {
142
+ next = tryCombinatorBranches(current, schema, navigate);
139
143
  }
144
+ if (!next) return {};
145
+ current = next;
140
146
  }
141
147
 
142
148
  const resolved = derefSchema(current, schema);
package/src/core/refs.ts CHANGED
@@ -74,6 +74,58 @@ export function deref(
74
74
  return resolveRef(node, root) as Record<string, unknown> | undefined;
75
75
  }
76
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
+
77
129
  /**
78
130
  * Recursively dereference every `$ref` in a schema subtree, producing a
79
131
  * concrete schema with no remaining refs. Cycles along any single chain are
@@ -1,4 +1,4 @@
1
- import { deepDeref, deref } from "./refs";
1
+ import { deepDeref, deref, tryCombinatorBranches } from "./refs";
2
2
 
3
3
  /**
4
4
  * Resolve a JSON Forms scope path to its schema within a root schema.
@@ -9,11 +9,11 @@ import { deepDeref, deref } from "./refs";
9
9
  * - "items" segments navigate into array `.items`
10
10
  * - all other segments index directly into the current object
11
11
  *
12
- * `$ref` nodes are dereferenced transparently at every step, so scopes that
13
- * cross a `$ref` boundary (e.g. `items: { $ref: "#/$defs/X" }`) resolve to
14
- * the target schema rather than returning `{}`. The returned schema is also
15
- * deep-dereferenced so downstream walkers (e.g. `getProjectedSchema`) can
16
- * operate on a self-contained sub-schema without needing the original root.
12
+ * Dereferences `$ref` nodes transparently at every step, and falls through
13
+ * to `oneOf` / `anyOf` / `allOf` branches when a segment can't resolve
14
+ * directly picks the first branch that satisfies the navigation. The
15
+ * returned schema is deep-dereferenced so downstream walkers can operate on
16
+ * a self-contained sub-schema without needing the original root.
17
17
  */
18
18
  export function resolveScopeSchema(
19
19
  scope: string,
@@ -35,13 +35,19 @@ export function resolveScopeSchema(
35
35
  current = deref(current, rootSchema);
36
36
  if (!current || typeof current !== "object") return undefined;
37
37
 
38
- if (segment === "properties") {
39
- current = current.properties;
40
- } else if (segment === "items") {
41
- current = current.items;
42
- } else {
43
- current = current[segment];
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ const navigate = (node: Record<string, any>): unknown => {
40
+ if (segment === "properties") return node.properties;
41
+ if (segment === "items") return node.items;
42
+ return node[segment];
43
+ };
44
+
45
+ let next = navigate(current);
46
+ if (next === undefined) {
47
+ next = tryCombinatorBranches(current, rootSchema, navigate);
44
48
  }
49
+ if (next === undefined) return undefined;
50
+ current = next;
45
51
  }
46
52
 
47
53
  return deepDeref(current, rootSchema);