@openparser/lineage 1.0.4 → 1.0.5

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @openparser/lineage
2
2
 
3
+ ## 1.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - d46a558: Dotted field paths now round-trip keys containing periods and tildes without collisions, preventing review edits from targeting the wrong field.
8
+
3
9
  ## 1.0.4
4
10
 
5
11
  ## 1.0.3
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Bijective JSON Pointer ↔ dotted UI path encoding.
3
+ *
4
+ * Segments are joined with `.`. Literal `.` and `~` inside a segment name are
5
+ * escaped after RFC 6901 tilde rules so a key containing `~2` (`~02`) never
6
+ * collides with an escaped dot (`~2`). `/` does not need dotted-layer escaping
7
+ * because `.` is the only segment separator.
8
+ *
9
+ * Ordinary keys without `.` or `~` round-trip unchanged (`vendor`, `items.0.amount`).
10
+ */
11
+ /** Escape one dotted-path segment for joining with `.`. */
12
+ export declare function encodeDottedPathSegment(segment: string): string;
13
+ /** Decode one dotted-path segment produced by {@link encodeDottedPathSegment}. */
14
+ export declare function decodeDottedPathSegment(segment: string): string;
15
+ /** Split a dotted UI path into decoded segment names. */
16
+ export declare function parseDottedPathSegments(path: string): string[];
17
+ /** RFC 6901 pointer to bijective dotted UI path. */
18
+ export declare function dottedPathFromPointer(pointer: string): string;
19
+ /** Bijective dotted UI path back to an RFC 6901 pointer. */
20
+ export declare function pointerFromDottedPath(dottedPath: string): string;
21
+ //# sourceMappingURL=dotted-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dotted-path.d.ts","sourceRoot":"","sources":["../src/dotted-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,2DAA2D;AAC3D,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsB/D;AAED,yDAAyD;AACzD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9D;AAaD,oDAAoD;AACpD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGhE"}
package/dist/fields.d.ts CHANGED
@@ -2,15 +2,13 @@ import type { JsonValue } from './common.js';
2
2
  import type { ConfidenceAssertion, ConfidenceKind } from './confidence.js';
3
3
  import type { LineageDocument } from './document.js';
4
4
  import type { Entity } from './entity.js';
5
+ export { decodeDottedPathSegment, dottedPathFromPointer, encodeDottedPathSegment, parseDottedPathSegments, pointerFromDottedPath, } from './dotted-path.js';
5
6
  /** How a field's confidence came about, or that none was asserted at all. */
6
7
  export type ConfidenceOrigin = ConfidenceKind | 'not_reported';
7
8
  export type LineageField = {
8
9
  id: string;
9
10
  pointer: string;
10
- /**
11
- * Display-only dotted rendering of `pointer`. Lossy: a property whose name
12
- * contains a dot is indistinguishable from nesting. Use `pointer` as the key.
13
- */
11
+ /** Dotted UI rendering of `pointer`; bijective with {@link dottedPathFromPointer}. */
14
12
  dottedPath: string;
15
13
  value: JsonValue | undefined;
16
14
  confidence: ConfidenceAssertion | undefined;
@@ -25,20 +23,6 @@ export type LineageFieldTrace = {
25
23
  }>;
26
24
  activities: string[];
27
25
  };
28
- /**
29
- * RFC 6901 pointer to dotted display path, unescaping `~1` and `~0`.
30
- *
31
- * Display only. A property whose name contains a dot is indistinguishable from
32
- * nesting, so never use the result as a key.
33
- */
34
- export declare function dottedPathFromPointer(pointer: string): string;
35
- /**
36
- * Dotted display path back to an RFC 6901 pointer, escaping `~` and `/`.
37
- *
38
- * Only sound for paths `dottedPathFromPointer` could have produced: a property
39
- * name containing a dot round-trips as nesting. Prefer carrying the pointer.
40
- */
41
- export declare function pointerFromDottedPath(dottedPath: string): string;
42
26
  /**
43
27
  * The entity holding a field's current value at `pointer`.
44
28
  *
@@ -1 +1 @@
1
- {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,6EAA6E;AAC7E,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,cAAc,CAAC;AAE/D,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC5C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAcF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,MAAM,GACd;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAe5C;AAgBD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,eAAe,GAAG,YAAY,EAAE,CAS3D;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAmB/F"}
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAEvB,6EAA6E;AAC7E,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,cAAc,CAAC;AAE/D,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC5C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,YAAY,CAAC;IACpB,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAcF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,MAAM,GACd;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAe5C;AAgBD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,eAAe,GAAG,YAAY,EAAE,CAS3D;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAmB/F"}
package/dist/index.d.ts CHANGED
@@ -14,7 +14,8 @@ export { LineageDocumentSchema, LineageDocumentShapeSchema, parseLineageDocument
14
14
  export { AttributionSchema, DigestSchema, ENTITY_KINDS, EntityKindSchema, EntitySchema, LocatorSchema, SelectorSchema, type Attribution, type Digest, type Entity, type EntityKind, type Locator, type Selector, } from './entity.js';
15
15
  export { LINEAGE_PROV_NAMESPACE, lineageIdToProvQualifiedName, toProvJson, type ProvJsonDocument, } from './prov.js';
16
16
  export { RELATION_TYPES, RelationSchema, RelationTypeSchema, type Relation, type RelationType, } from './relation.js';
17
- export { dottedPathFromPointer, fieldEntityByPointer, fieldTrace, fields, pointerFromDottedPath, type ConfidenceOrigin, type LineageField, type LineageFieldTrace, } from './fields.js';
17
+ export { decodeDottedPathSegment, dottedPathFromPointer, encodeDottedPathSegment, parseDottedPathSegments, pointerFromDottedPath, } from './dotted-path.js';
18
+ export { fieldEntityByPointer, fieldTrace, fields, type ConfidenceOrigin, type LineageField, type LineageFieldTrace, } from './fields.js';
18
19
  export { activityLabel } from './labels.js';
19
20
  export { entityAncestors, entityDescendants, topologicalEntityOrder } from './traverse.js';
20
21
  export { LineageValidationError, validateLineageGraph } from './validate.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,uBAAuB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE7E,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,eAAe,EACf,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,oBAAoB,EACpB,wBAAwB,EACxB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,QAAQ,GACd,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,UAAU,EACV,MAAM,EACN,qBAAqB,EACrB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAExF,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,uBAAuB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE7E,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,eAAe,EACf,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,GACf,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,oBAAoB,EACpB,wBAAwB,EACxB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,QAAQ,GACd,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,sBAAsB,EACtB,4BAA4B,EAC5B,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,MAAM,EACN,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAExF,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -663,6 +663,58 @@ function flattenAttrs(attrs) {
663
663
  return out;
664
664
  }
665
665
 
666
+ // src/dotted-path.ts
667
+ var DOTTED_SEGMENT_DOT = "~2";
668
+ var JSON_POINTER_ESCAPE = /~(?:0|1)/g;
669
+ var INVALID_JSON_POINTER_ESCAPE = /~(?![01])/;
670
+ var INVALID_DOTTED_PATH_ESCAPE = /~(?![02])/;
671
+ function encodeDottedPathSegment(segment) {
672
+ return segment.replaceAll("~", "~0").replaceAll(".", DOTTED_SEGMENT_DOT);
673
+ }
674
+ function decodeDottedPathSegment(segment) {
675
+ if (INVALID_DOTTED_PATH_ESCAPE.test(segment)) {
676
+ throw new Error(`Invalid dotted path escape in segment "${segment}"`);
677
+ }
678
+ let decoded = "";
679
+ for (let index = 0; index < segment.length; index += 1) {
680
+ if (segment[index] === "~" && index + 1 < segment.length) {
681
+ const code = segment[index + 1];
682
+ if (code === "0") {
683
+ decoded += "~";
684
+ index += 1;
685
+ continue;
686
+ }
687
+ if (code === "2") {
688
+ decoded += ".";
689
+ index += 1;
690
+ continue;
691
+ }
692
+ }
693
+ decoded += segment[index];
694
+ }
695
+ return decoded;
696
+ }
697
+ function parseDottedPathSegments(path) {
698
+ return path.split(".").filter(Boolean).map(decodeDottedPathSegment);
699
+ }
700
+ function encodeJsonPointerSegment(segment) {
701
+ return segment.replaceAll("~", "~0").replaceAll("/", "~1");
702
+ }
703
+ function decodeJsonPointerSegment(segment) {
704
+ if (INVALID_JSON_POINTER_ESCAPE.test(segment)) {
705
+ throw new Error(`Invalid JSON Pointer escape in segment "${segment}"`);
706
+ }
707
+ return segment.replace(JSON_POINTER_ESCAPE, (escape) => escape === "~1" ? "/" : "~");
708
+ }
709
+ function dottedPathFromPointer(pointer) {
710
+ if (pointer === "" || pointer === "/") return "";
711
+ return pointer.slice(1).split("/").map(decodeJsonPointerSegment).map(encodeDottedPathSegment).join(".");
712
+ }
713
+ function pointerFromDottedPath(dottedPath) {
714
+ if (dottedPath === "") return "";
715
+ return `/${parseDottedPathSegments(dottedPath).map(encodeJsonPointerSegment).join("/")}`;
716
+ }
717
+
666
718
  // src/traverse.ts
667
719
  function buildForwardEdges(doc) {
668
720
  const edges = /* @__PURE__ */ new Map();
@@ -746,14 +798,6 @@ function fieldConfidence(entity) {
746
798
  if (!assertions || assertions.length === 0) return void 0;
747
799
  return assertions.find((assertion) => assertion.scope === "extraction") ?? assertions[0];
748
800
  }
749
- function dottedPathFromPointer(pointer) {
750
- if (pointer === "" || pointer === "/") return "";
751
- return pointer.replace(/^\//, "").split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")).join(".");
752
- }
753
- function pointerFromDottedPath(dottedPath) {
754
- if (dottedPath === "") return "";
755
- return `/${dottedPath.split(".").map((segment) => segment.replaceAll("~", "~0").replaceAll("/", "~1")).join("/")}`;
756
- }
757
801
  function fieldEntityByPointer(doc, pointer) {
758
802
  const matching = Object.entries(doc.entities).filter(([, entity]) => entity.path === pointer);
759
803
  if (matching.length === 0) return void 0;
@@ -852,8 +896,10 @@ export {
852
896
  TimestampSchema,
853
897
  TransformationSchema,
854
898
  activityLabel,
899
+ decodeDottedPathSegment,
855
900
  derivationId,
856
901
  dottedPathFromPointer,
902
+ encodeDottedPathSegment,
857
903
  entityAncestors,
858
904
  entityDescendants,
859
905
  fieldEntityByPointer,
@@ -861,6 +907,7 @@ export {
861
907
  fields,
862
908
  jsonValuesEqual,
863
909
  lineageIdToProvQualifiedName,
910
+ parseDottedPathSegments,
864
911
  parseLineageDocument,
865
912
  pointerFromDottedPath,
866
913
  safeParseLineageDocument,
@@ -10,6 +10,24 @@ function activityLabel(activity) {
10
10
  return activity.name ?? ACTIVITY_LABELS[activity.type] ?? activity.type;
11
11
  }
12
12
 
13
+ // src/dotted-path.ts
14
+ var DOTTED_SEGMENT_DOT = "~2";
15
+ var JSON_POINTER_ESCAPE = /~(?:0|1)/g;
16
+ var INVALID_JSON_POINTER_ESCAPE = /~(?![01])/;
17
+ function encodeDottedPathSegment(segment) {
18
+ return segment.replaceAll("~", "~0").replaceAll(".", DOTTED_SEGMENT_DOT);
19
+ }
20
+ function decodeJsonPointerSegment(segment) {
21
+ if (INVALID_JSON_POINTER_ESCAPE.test(segment)) {
22
+ throw new Error(`Invalid JSON Pointer escape in segment "${segment}"`);
23
+ }
24
+ return segment.replace(JSON_POINTER_ESCAPE, (escape) => escape === "~1" ? "/" : "~");
25
+ }
26
+ function dottedPathFromPointer(pointer) {
27
+ if (pointer === "" || pointer === "/") return "";
28
+ return pointer.slice(1).split("/").map(decodeJsonPointerSegment).map(encodeDottedPathSegment).join(".");
29
+ }
30
+
13
31
  // src/traverse.ts
14
32
  function buildReverseEdges(doc) {
15
33
  const edges = /* @__PURE__ */ new Map();
@@ -50,10 +68,6 @@ function fieldConfidence(entity) {
50
68
  if (!assertions || assertions.length === 0) return void 0;
51
69
  return assertions.find((assertion) => assertion.scope === "extraction") ?? assertions[0];
52
70
  }
53
- function dottedPathFromPointer(pointer) {
54
- if (pointer === "" || pointer === "/") return "";
55
- return pointer.replace(/^\//, "").split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")).join(".");
56
- }
57
71
  function toField(id, entity) {
58
72
  const pointer = fieldPointer(entity);
59
73
  const confidence = fieldConfidence(entity);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openparser/lineage",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Zod schemas, validation, and helpers for lineage@1 complete data-derivation DAGs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Bijective JSON Pointer ↔ dotted UI path encoding.
3
+ *
4
+ * Segments are joined with `.`. Literal `.` and `~` inside a segment name are
5
+ * escaped after RFC 6901 tilde rules so a key containing `~2` (`~02`) never
6
+ * collides with an escaped dot (`~2`). `/` does not need dotted-layer escaping
7
+ * because `.` is the only segment separator.
8
+ *
9
+ * Ordinary keys without `.` or `~` round-trip unchanged (`vendor`, `items.0.amount`).
10
+ */
11
+
12
+ const DOTTED_SEGMENT_DOT = '~2';
13
+ const JSON_POINTER_ESCAPE = /~(?:0|1)/g;
14
+ const INVALID_JSON_POINTER_ESCAPE = /~(?![01])/;
15
+ const INVALID_DOTTED_PATH_ESCAPE = /~(?![02])/;
16
+
17
+ /** Escape one dotted-path segment for joining with `.`. */
18
+ export function encodeDottedPathSegment(segment: string): string {
19
+ return segment.replaceAll('~', '~0').replaceAll('.', DOTTED_SEGMENT_DOT);
20
+ }
21
+
22
+ /** Decode one dotted-path segment produced by {@link encodeDottedPathSegment}. */
23
+ export function decodeDottedPathSegment(segment: string): string {
24
+ if (INVALID_DOTTED_PATH_ESCAPE.test(segment)) {
25
+ throw new Error(`Invalid dotted path escape in segment "${segment}"`);
26
+ }
27
+ let decoded = '';
28
+ for (let index = 0; index < segment.length; index += 1) {
29
+ if (segment[index] === '~' && index + 1 < segment.length) {
30
+ const code = segment[index + 1];
31
+ if (code === '0') {
32
+ decoded += '~';
33
+ index += 1;
34
+ continue;
35
+ }
36
+ if (code === '2') {
37
+ decoded += '.';
38
+ index += 1;
39
+ continue;
40
+ }
41
+ }
42
+ decoded += segment[index];
43
+ }
44
+ return decoded;
45
+ }
46
+
47
+ /** Split a dotted UI path into decoded segment names. */
48
+ export function parseDottedPathSegments(path: string): string[] {
49
+ return path.split('.').filter(Boolean).map(decodeDottedPathSegment);
50
+ }
51
+
52
+ function encodeJsonPointerSegment(segment: string): string {
53
+ return segment.replaceAll('~', '~0').replaceAll('/', '~1');
54
+ }
55
+
56
+ function decodeJsonPointerSegment(segment: string): string {
57
+ if (INVALID_JSON_POINTER_ESCAPE.test(segment)) {
58
+ throw new Error(`Invalid JSON Pointer escape in segment "${segment}"`);
59
+ }
60
+ return segment.replace(JSON_POINTER_ESCAPE, (escape) => (escape === '~1' ? '/' : '~'));
61
+ }
62
+
63
+ /** RFC 6901 pointer to bijective dotted UI path. */
64
+ export function dottedPathFromPointer(pointer: string): string {
65
+ if (pointer === '' || pointer === '/') return '';
66
+ return pointer
67
+ .slice(1)
68
+ .split('/')
69
+ .map(decodeJsonPointerSegment)
70
+ .map(encodeDottedPathSegment)
71
+ .join('.');
72
+ }
73
+
74
+ /** Bijective dotted UI path back to an RFC 6901 pointer. */
75
+ export function pointerFromDottedPath(dottedPath: string): string {
76
+ if (dottedPath === '') return '';
77
+ return `/${parseDottedPathSegments(dottedPath).map(encodeJsonPointerSegment).join('/')}`;
78
+ }
package/src/fields.ts CHANGED
@@ -1,19 +1,25 @@
1
1
  import type { JsonValue } from './common';
2
2
  import type { ConfidenceAssertion, ConfidenceKind } from './confidence';
3
3
  import type { LineageDocument } from './document';
4
+ import { dottedPathFromPointer } from './dotted-path';
4
5
  import type { Entity } from './entity';
5
6
  import { entityAncestors } from './traverse';
6
7
 
8
+ export {
9
+ decodeDottedPathSegment,
10
+ dottedPathFromPointer,
11
+ encodeDottedPathSegment,
12
+ parseDottedPathSegments,
13
+ pointerFromDottedPath,
14
+ } from './dotted-path';
15
+
7
16
  /** How a field's confidence came about, or that none was asserted at all. */
8
17
  export type ConfidenceOrigin = ConfidenceKind | 'not_reported';
9
18
 
10
19
  export type LineageField = {
11
20
  id: string;
12
21
  pointer: string;
13
- /**
14
- * Display-only dotted rendering of `pointer`. Lossy: a property whose name
15
- * contains a dot is indistinguishable from nesting. Use `pointer` as the key.
16
- */
22
+ /** Dotted UI rendering of `pointer`; bijective with {@link dottedPathFromPointer}. */
17
23
  dottedPath: string;
18
24
  value: JsonValue | undefined;
19
25
  confidence: ConfidenceAssertion | undefined;
@@ -39,35 +45,6 @@ function fieldConfidence(entity: Entity): ConfidenceAssertion | undefined {
39
45
  return assertions.find((assertion) => assertion.scope === 'extraction') ?? assertions[0];
40
46
  }
41
47
 
42
- /**
43
- * RFC 6901 pointer to dotted display path, unescaping `~1` and `~0`.
44
- *
45
- * Display only. A property whose name contains a dot is indistinguishable from
46
- * nesting, so never use the result as a key.
47
- */
48
- export function dottedPathFromPointer(pointer: string): string {
49
- if (pointer === '' || pointer === '/') return '';
50
- return pointer
51
- .replace(/^\//, '')
52
- .split('/')
53
- .map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~'))
54
- .join('.');
55
- }
56
-
57
- /**
58
- * Dotted display path back to an RFC 6901 pointer, escaping `~` and `/`.
59
- *
60
- * Only sound for paths `dottedPathFromPointer` could have produced: a property
61
- * name containing a dot round-trips as nesting. Prefer carrying the pointer.
62
- */
63
- export function pointerFromDottedPath(dottedPath: string): string {
64
- if (dottedPath === '') return '';
65
- return `/${dottedPath
66
- .split('.')
67
- .map((segment) => segment.replaceAll('~', '~0').replaceAll('/', '~1'))
68
- .join('/')}`;
69
- }
70
-
71
48
  /**
72
49
  * The entity holding a field's current value at `pointer`.
73
50
  *
package/src/index.ts CHANGED
@@ -106,11 +106,17 @@ export {
106
106
  } from './relation';
107
107
 
108
108
  export {
109
+ decodeDottedPathSegment,
109
110
  dottedPathFromPointer,
111
+ encodeDottedPathSegment,
112
+ parseDottedPathSegments,
113
+ pointerFromDottedPath,
114
+ } from './dotted-path';
115
+
116
+ export {
110
117
  fieldEntityByPointer,
111
118
  fieldTrace,
112
119
  fields,
113
- pointerFromDottedPath,
114
120
  type ConfidenceOrigin,
115
121
  type LineageField,
116
122
  type LineageFieldTrace,