@feniix/bridgekit 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/src/core/execute-tool.js +77 -64
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to `@feniix/bridgekit` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.9.2] - 2026-05-26
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `PortableValidationError.field` now disambiguates slash-named properties
|
|
12
|
+
(`"a/b": Type.String()`) from nested-object paths (`a: Type.Object({ b: ... })`)
|
|
13
|
+
that produce identical `instancePath` values. The field-derivation walker
|
|
14
|
+
reads `error.schemaPath` instead, which carries explicit `/properties/`
|
|
15
|
+
markers per nesting level. Behavior change limited to the overlapping-prefix
|
|
16
|
+
case from PR #42's adversarial review; the common slash-named-property case
|
|
17
|
+
from 0.8.3 and the `Type.Intersect` `allOf` descent from 0.9.0 are unchanged.
|
|
18
|
+
The walker treats `anyOf` and `oneOf` as branch-descent commands (parallel
|
|
19
|
+
to the 0.9.0 `allOf` handler), so slash-named properties holding
|
|
20
|
+
`Type.Union(...)` values preserve their prefix through per-branch errors.
|
|
21
|
+
Pre-fix, the walker would match the slash-name at the `properties` level,
|
|
22
|
+
then bail on `anyOf` with no handler, and the fallback would strip the
|
|
23
|
+
prefix to the trailing segment. The walker now handles all three
|
|
24
|
+
JSON-Schema combinators (`allOf`, `anyOf`, `oneOf`) symmetrically.
|
|
25
|
+
Resolves [#43](https://github.com/feniix/bridgekit/issues/43).
|
|
26
|
+
|
|
7
27
|
## [0.9.1] - 2026-05-26
|
|
8
28
|
|
|
9
29
|
### Fixed
|
|
@@ -4,93 +4,106 @@ function fieldFromPath(instancePath) {
|
|
|
4
4
|
return instancePath.split("/").filter(Boolean).at(-1) ?? ROOT_FIELD;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Resolve the leaf
|
|
7
|
+
* Resolve the leaf property name from a TypeBox error by walking
|
|
8
|
+
* `error.schemaPath` rather than `error.instancePath`.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* actual leaf key. Array nodes consume a single numeric segment via `items`.
|
|
10
|
+
* `instancePath` does not escape `/` inside property names (TypeBox does not
|
|
11
|
+
* follow JSON Pointer RFC 6901's `~1` encoding), so a schema with BOTH a
|
|
12
|
+
* slash-named property `"a/b"` AND a nested path `a.b` produces the same
|
|
13
|
+
* `instancePath: "/a/b"` for either failure — the two cases are
|
|
14
|
+
* indistinguishable from the data path alone. `schemaPath` carries explicit
|
|
15
|
+
* `/properties/` markers per nesting level, so the two cases become
|
|
16
|
+
* structurally distinct:
|
|
17
17
|
*
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
* is what lets `Type.Intersect([Object({"a/b": String()}), Object({c: ...})])`
|
|
21
|
-
* resolve a `/a/b` instancePath to the `"a/b"` key in the first branch.
|
|
18
|
+
* slash-named `"a/b": Type.String()` wrong type → `#/properties/a/b`
|
|
19
|
+
* nested `a: Object({ b: Number() })` wrong type → `#/properties/a/properties/b`
|
|
22
20
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* TypeBox's schemaPath terminates at the violated property's value-schema —
|
|
22
|
+
* it does NOT append the keyword as a trailing segment (the keyword lives in
|
|
23
|
+
* `error.keyword` instead). The walker therefore only needs to consume
|
|
24
|
+
* structural commands (`properties`, `items`, `allOf`) and the property /
|
|
25
|
+
* branch tokens that follow them.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* `allOf` descent (the 0.9.0 Intersect support) is preserved: an
|
|
28
|
+
* `#/allOf/<i>/properties/<key>` path descends into the i-th branch before
|
|
29
|
+
* resolving the property. `anyOf` / `oneOf` descent is symmetric: TypeBox
|
|
30
|
+
* emits per-branch errors with schemaPath `.../anyOf/<i>/...` (or `.../oneOf/<i>/...`)
|
|
31
|
+
* when a Union (or hand-authored oneOf) sits under a property; the walker
|
|
32
|
+
* descends into the indexed branch carrying `lastField` through so a
|
|
33
|
+
* slash-named property holding a `Type.Union(...)` value preserves its
|
|
34
|
+
* prefix.
|
|
35
|
+
*
|
|
36
|
+
* Returns `undefined` if the walk cannot model the schemaPath (e.g.
|
|
37
|
+
* `additionalProperties` content whose schemaPath stops at `#`). The caller
|
|
38
|
+
* then falls back to the string-split `fieldFromPath` on `instancePath`,
|
|
39
|
+
* preserving prior behavior. The `required` and `additionalProperties`
|
|
40
|
+
* callers already read structured `params` and are unaffected; this helper
|
|
41
|
+
* covers the `type` / `const` / `enum` / numeric-keyword branches.
|
|
30
42
|
*/
|
|
31
|
-
function fieldFromSchemaWalk(schema,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const segments = instancePath.split("/").filter(Boolean);
|
|
43
|
+
function fieldFromSchemaWalk(schema, schemaPath) {
|
|
44
|
+
const raw = schemaPath.startsWith("#") ? schemaPath.slice(1) : schemaPath;
|
|
45
|
+
const segments = raw.split("/").filter(Boolean);
|
|
35
46
|
if (segments.length === 0)
|
|
36
47
|
return undefined;
|
|
37
|
-
return walkSegments(schema, segments, 0);
|
|
48
|
+
return walkSegments(schema, segments, 0, undefined);
|
|
38
49
|
}
|
|
39
|
-
function walkSegments(node, segments, i) {
|
|
40
|
-
if (i >= segments.length)
|
|
41
|
-
return undefined;
|
|
50
|
+
function walkSegments(node, segments, i, lastField) {
|
|
42
51
|
if (!node || typeof node !== "object")
|
|
43
52
|
return undefined;
|
|
53
|
+
if (i >= segments.length)
|
|
54
|
+
return lastField;
|
|
44
55
|
const obj = node;
|
|
45
|
-
|
|
46
|
-
// `
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const head = segments[i];
|
|
57
|
+
// `allOf` / `anyOf` / `oneOf` commands: next segment is the branch index.
|
|
58
|
+
// Descend into the chosen branch with the rest of the path. `allOf` covers
|
|
59
|
+
// `Type.Intersect` lowering (0.9.0); `anyOf` covers `Type.Union` (and the
|
|
60
|
+
// hand-authored `oneOf` shape). `lastField` carries through unchanged — a
|
|
61
|
+
// slash-named property whose value is a Union must keep its prefix when
|
|
62
|
+
// the walker descends into a per-branch error.
|
|
63
|
+
if (head === "allOf" && Array.isArray(obj.allOf) && i + 1 < segments.length) {
|
|
64
|
+
const idx = Number(segments[i + 1]);
|
|
65
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= obj.allOf.length)
|
|
66
|
+
return undefined;
|
|
67
|
+
return walkSegments(obj.allOf[idx], segments, i + 2, lastField);
|
|
68
|
+
}
|
|
69
|
+
if (head === "anyOf" && Array.isArray(obj.anyOf) && i + 1 < segments.length) {
|
|
70
|
+
const idx = Number(segments[i + 1]);
|
|
71
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= obj.anyOf.length)
|
|
72
|
+
return undefined;
|
|
73
|
+
return walkSegments(obj.anyOf[idx], segments, i + 2, lastField);
|
|
74
|
+
}
|
|
75
|
+
if (head === "oneOf" && Array.isArray(obj.oneOf) && i + 1 < segments.length) {
|
|
76
|
+
const idx = Number(segments[i + 1]);
|
|
77
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= obj.oneOf.length)
|
|
78
|
+
return undefined;
|
|
79
|
+
return walkSegments(obj.oneOf[idx], segments, i + 2, lastField);
|
|
61
80
|
}
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
81
|
+
// `items` command: array element schema. TypeBox emits a single `items`
|
|
82
|
+
// marker regardless of element index (the data-side index is in
|
|
83
|
+
// instancePath, not schemaPath). Tuple-form `items` (array) is not
|
|
84
|
+
// expected here from TypeBox lowering — schema-form is uniform.
|
|
85
|
+
if (head === "items" && obj.items !== undefined) {
|
|
86
|
+
return walkSegments(obj.items, segments, i + 1, lastField);
|
|
87
|
+
}
|
|
88
|
+
// `properties` command: subsequent segments form the property name (joined
|
|
89
|
+
// by `/` to recover slash-named keys). Consume by greedy longest-prefix
|
|
90
|
+
// match against the parent's actual `properties` keys, then descend.
|
|
91
|
+
if (head === "properties" && obj.properties && typeof obj.properties === "object") {
|
|
66
92
|
const props = obj.properties;
|
|
67
|
-
for (let j = segments.length; j > i; j--) {
|
|
68
|
-
const candidate = segments.slice(i, j).join("/");
|
|
93
|
+
for (let j = segments.length; j > i + 1; j--) {
|
|
94
|
+
const candidate = segments.slice(i + 1, j).join("/");
|
|
69
95
|
if (Object.hasOwn(props, candidate)) {
|
|
70
|
-
|
|
71
|
-
return candidate;
|
|
72
|
-
const child = walkSegments(props[candidate], segments, j);
|
|
96
|
+
const child = walkSegments(props[candidate], segments, j, candidate);
|
|
73
97
|
if (child !== undefined)
|
|
74
98
|
return child;
|
|
75
99
|
}
|
|
76
100
|
}
|
|
77
101
|
return undefined;
|
|
78
102
|
}
|
|
79
|
-
// allOf descent: TypeBox's Intersect lowering. Each branch sees the full
|
|
80
|
-
// remaining path; return the first branch that resolves. This is the
|
|
81
|
-
// schema-side mirror of how Intersect merges property contributions.
|
|
82
|
-
if (Array.isArray(obj.allOf)) {
|
|
83
|
-
for (const branch of obj.allOf) {
|
|
84
|
-
const matched = walkSegments(branch, segments, i);
|
|
85
|
-
if (matched !== undefined)
|
|
86
|
-
return matched;
|
|
87
|
-
}
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
103
|
return undefined;
|
|
91
104
|
}
|
|
92
105
|
function fieldFromError(schema, error) {
|
|
93
|
-
return fieldFromSchemaWalk(schema, error.
|
|
106
|
+
return fieldFromSchemaWalk(schema, error.schemaPath) ?? fieldFromPath(error.instancePath);
|
|
94
107
|
}
|
|
95
108
|
function readDiscriminatorValues(propSchema) {
|
|
96
109
|
// Extract the allowed values from a discriminator-eligible prop schema.
|