Skip to content

Commit ff9dca7

Browse files
authored
When following a pointer, adjust targetDoc to point to the root of the current doc (commontoolsinc#1805)
* When following a pointer, adjust targetDoc to point to the root of the current doc, since our pointers may point outside of the portion where we are. * Added unit test * Removed unused test class
1 parent fdad7fd commit ff9dca7

File tree

2 files changed

+134
-29
lines changed

2 files changed

+134
-29
lines changed

packages/runner/src/traverse.ts

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -468,10 +468,6 @@ function followPointer<S extends BaseMemoryAddress>(
468468
const target: BaseMemoryAddress = (link.id !== undefined)
469469
? { id: link.id, type: "application/json" }
470470
: doc.address;
471-
const targetDoc = {
472-
address: doc.address,
473-
value: doc.value,
474-
};
475471
if (selector !== undefined) {
476472
// We'll need to re-root the selector for the target doc
477473
// Remove the portions of doc.path from selector.path, limiting schema if
@@ -492,43 +488,47 @@ function followPointer<S extends BaseMemoryAddress>(
492488
// Cycle detected - treat this as notFound to avoid traversal
493489
return [notFound(doc.address), selector];
494490
}
491+
// We may access portions of the doc outside what we have in our doc
492+
// attestation, so reload the top level doc from the manager.
493+
const valueEntry = manager.load(target);
494+
if (valueEntry === null) {
495+
return [notFound(doc.address), selector];
496+
}
495497
if (link.id !== undefined) {
496-
// We have a reference to a different cell, so track the dependency
498+
// We have a reference to a different doc, so track the dependency
497499
// and update our targetDoc
498-
const valueEntry = manager.load(target);
499-
if (valueEntry === null) {
500-
return [notFound(doc.address), selector];
501-
}
502500
if (schemaTracker !== undefined && selector !== undefined) {
503501
schemaTracker.add(manager.toKey(target), selector);
504502
}
505-
// If the object we're pointing to is a retracted fact, just return undefined.
506-
// We can't do a better match, but we do want to include the result so we watch this doc
507-
if (valueEntry.value === undefined) {
508-
return [notFound(target), selector];
503+
// Load the sources/recipes recursively unless we're a retracted fact.
504+
if (valueEntry.value !== undefined) {
505+
loadSource(
506+
manager,
507+
valueEntry,
508+
new Set<string>(),
509+
schemaTracker,
510+
);
509511
}
510-
// Otherwise, we can continue with the target.
511-
// an assertion fact.is will be an object with a value property, and
512-
// that's what our schema is relative to.
513-
targetDoc.address = { ...target, path: ["value"] };
514-
targetDoc.value = (valueEntry.value as Immutable<JSONObject>)["value"];
515-
// Load any sources (recursively) if they exist and any linked recipes
516-
loadSource(
517-
manager,
518-
valueEntry,
519-
new Set<string>(),
520-
schemaTracker,
521-
);
522512
}
513+
// If the object we're pointing to is a retracted fact, just return undefined.
514+
// We can't do a better match, but we do want to include the result so we watch this doc
515+
if (valueEntry.value === undefined) {
516+
return [notFound(target), selector];
517+
}
518+
// We can continue with the target, but provide the top level target doc
519+
// to getAtPath.
520+
// An assertion fact.is will be an object with a value property, and
521+
// that's what our schema is relative to, so we'll grab the value part.
522+
const targetDoc = {
523+
address: { ...target, path: ["value"] },
524+
value: (valueEntry.value as Immutable<JSONObject>)["value"],
525+
};
523526

524527
// We've loaded the linked doc, so walk the path to get to the right part of that doc (or whatever doc that path leads to),
525528
// then the provided path from the arguments.
526529
return getAtPath(
527530
manager,
528-
{
529-
address: targetDoc.address,
530-
value: targetDoc.value,
531-
},
531+
targetDoc,
532532
[...link.path, ...path] as string[],
533533
tracker,
534534
schemaTracker,
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { describe, it } from "@std/testing/bdd";
2+
import { expect } from "@std/expect";
3+
import { refer } from "merkle-reference/json";
4+
import type {
5+
Entity,
6+
Revision,
7+
State,
8+
URI,
9+
} from "@commontools/memory/interface";
10+
import { SchemaObjectTraverser } from "../src/traverse.ts";
11+
import { StoreObjectManager } from "../src/storage/query.ts";
12+
13+
describe("SchemaObjectTraverser.traverseDAG", () => {
14+
it("follows legacy cell links when traversing", () => {
15+
const store = new Map<string, Revision<State>>();
16+
const type = "application/json" as const;
17+
const doc1Uri = "of:doc-1" as URI;
18+
const doc2Uri = "of:doc-2" as URI;
19+
const doc1Entity = doc1Uri as Entity;
20+
const doc2Entity = doc2Uri as Entity;
21+
22+
const doc1Value = { employees: [{ name: "Bob" }] };
23+
const doc1EntityId = { "/": doc1Uri };
24+
25+
const doc1Revision: Revision<State> = {
26+
the: type,
27+
of: doc1Entity,
28+
is: { value: doc1Value },
29+
cause: refer({ the: type, of: doc1Entity }),
30+
since: 1,
31+
};
32+
store.set(
33+
`${doc1Revision.of}/${doc1Revision.the}`,
34+
doc1Revision,
35+
);
36+
37+
const doc2Value = {
38+
employeeName: {
39+
cell: doc1EntityId,
40+
path: ["employees", "0", "name"],
41+
},
42+
argument: {
43+
tools: {
44+
search_web: {
45+
pattern: {
46+
result: {
47+
$alias: {
48+
path: ["internal", "__#0"],
49+
},
50+
},
51+
},
52+
},
53+
},
54+
},
55+
internal: {
56+
"__#0": {
57+
name: "Foo",
58+
},
59+
},
60+
};
61+
62+
const doc2Revision: Revision<State> = {
63+
the: type,
64+
of: doc2Entity,
65+
is: { value: doc2Value },
66+
cause: refer({ the: type, of: doc2Entity }),
67+
since: 2,
68+
};
69+
store.set(
70+
`${doc2Revision.of}/${doc2Revision.the}`,
71+
doc2Revision,
72+
);
73+
74+
const manager = new StoreObjectManager(store);
75+
const traverser = new SchemaObjectTraverser(manager, {
76+
path: [],
77+
schemaContext: { schema: true, rootSchema: true },
78+
});
79+
80+
const result = traverser.traverse({
81+
address: { id: doc2Uri, type, path: ["value"] },
82+
value: doc2Value,
83+
});
84+
85+
expect(result).toEqual({
86+
argument: {
87+
tools: {
88+
search_web: {
89+
pattern: {
90+
result: {
91+
name: "Foo",
92+
},
93+
},
94+
},
95+
},
96+
},
97+
employeeName: "Bob",
98+
internal: {
99+
"__#0": {
100+
name: "Foo",
101+
},
102+
},
103+
});
104+
});
105+
});

0 commit comments

Comments
 (0)