@totemsdk/proofgraph 1.0.0 → 1.1.0
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/LICENSE +202 -21
- package/NOTICE +60 -0
- package/README.md +1 -1
- package/dist/canonical.js +11 -19
- package/dist/cjs/canonical.d.ts +22 -0
- package/dist/cjs/canonical.js +51 -0
- package/dist/cjs/durable-store.d.ts +50 -0
- package/dist/cjs/durable-store.js +178 -0
- package/dist/cjs/evidence.d.ts +89 -0
- package/dist/cjs/evidence.js +130 -0
- package/dist/cjs/graph.d.ts +103 -0
- package/dist/cjs/graph.js +298 -0
- package/dist/cjs/index.d.ts +26 -0
- package/dist/cjs/index.js +84 -0
- package/dist/cjs/io.d.ts +26 -0
- package/dist/cjs/io.js +173 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/query.d.ts +85 -0
- package/dist/cjs/query.js +189 -0
- package/dist/cjs/replicated-evidence.d.ts +42 -0
- package/dist/cjs/replicated-evidence.js +98 -0
- package/dist/cjs/types.d.ts +62 -0
- package/dist/cjs/types.js +7 -0
- package/dist/cjs/verify.d.ts +23 -0
- package/dist/cjs/verify.js +77 -0
- package/dist/durable-store.d.ts +50 -0
- package/dist/durable-store.js +175 -0
- package/dist/evidence.d.ts +89 -0
- package/dist/evidence.js +125 -0
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +49 -33
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -46
- package/dist/io.js +4 -8
- package/dist/query.d.ts +4 -0
- package/dist/query.js +28 -36
- package/dist/replicated-evidence.d.ts +42 -0
- package/dist/replicated-evidence.js +95 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -2
- package/dist/verify.js +3 -40
- package/package.json +22 -14
- package/rust/pkg/package.json +2 -2
- package/rust/pkg/proofgraph_wasm_bg.wasm +0 -0
- package/rust/pkg-node/package.json +2 -2
- package/rust/pkg-node/proofgraph_wasm_bg.wasm +0 -0
package/dist/evidence.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable evidence-chain owner for @totemsdk/proofgraph (RFC-007 G8 / §4.3).
|
|
3
|
+
*
|
|
4
|
+
* Evidence *bytes* are owned by the minimal `ArtifactStore` over a pluggable
|
|
5
|
+
* backend; graph references alone do not satisfy the flight-evidence gate. This
|
|
6
|
+
* module wires a ProofGraph's `evidence` nodes to content-addressed artifacts,
|
|
7
|
+
* and restores the bytes on demand with commit-time digest verification.
|
|
8
|
+
*
|
|
9
|
+
* Restore semantics (RFC-007 §4.3): `corrupt` (bytes present but digest
|
|
10
|
+
* mismatch — tampered or truncated) is surfaced strictly, never collapsed into
|
|
11
|
+
* `not-found` (bytes absent); `unavailable` (backend unreachable, or the
|
|
12
|
+
* backend deleted a blob the local index still references) is also surfaced.
|
|
13
|
+
*/
|
|
14
|
+
import { StorageError } from '@totemsdk/storage/errors';
|
|
15
|
+
import { sha3_256, toHex } from '@totemsdk/core';
|
|
16
|
+
const DEFAULT_NS = 'totem-proofgraph-evidence';
|
|
17
|
+
const INDEX_PREFIX = 'totem_proofgraph_evidence:v1:';
|
|
18
|
+
export function normalizeEvidenceHash(hash) {
|
|
19
|
+
return hash.replace(/^0x/i, '').toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
/** Throws `StorageError('corrupt')` unless `contentHash === artifact.digest`. */
|
|
22
|
+
export function assertEvidenceBinding(binding) {
|
|
23
|
+
if (normalizeEvidenceHash(binding.contentHash) !== normalizeEvidenceHash(binding.artifact.digest)) {
|
|
24
|
+
throw new StorageError(`evidence binding mismatch for node "${binding.nodeId}": contentHash ${binding.contentHash} !== artifact.digest ${binding.artifact.digest}`, 'corrupt', { key: binding.nodeId });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function evidenceNodeIds(graph) {
|
|
28
|
+
return graph.nodes.filter((node) => node.type === 'evidence').map((node) => node.id);
|
|
29
|
+
}
|
|
30
|
+
/** The claim commitment carried on an evidence node (`data.contentHash`). */
|
|
31
|
+
function nodeContentHash(graph, nodeId) {
|
|
32
|
+
const value = graph.nodes.find((node) => node.id === nodeId)?.data?.contentHash;
|
|
33
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
34
|
+
}
|
|
35
|
+
export function createProofGraphEvidenceStore(artifacts, index, options = {}) {
|
|
36
|
+
if (!artifacts.capabilities.writable) {
|
|
37
|
+
throw new Error('createProofGraphEvidenceStore requires a writable artifact store');
|
|
38
|
+
}
|
|
39
|
+
const namespace = options.namespace ?? DEFAULT_NS;
|
|
40
|
+
const strict = options.strict ?? true;
|
|
41
|
+
const indexKey = (nodeId) => `${INDEX_PREFIX}${nodeId}`;
|
|
42
|
+
return {
|
|
43
|
+
async putEvidence(graph, bytesFor, options = {}) {
|
|
44
|
+
const refs = [];
|
|
45
|
+
for (const nodeId of evidenceNodeIds(graph)) {
|
|
46
|
+
const bytes = bytesFor(nodeId);
|
|
47
|
+
if (!bytes)
|
|
48
|
+
continue;
|
|
49
|
+
const contentHash = options.contentHashFor?.(nodeId) ?? nodeContentHash(graph, nodeId);
|
|
50
|
+
if (!contentHash && options.requireContentHash) {
|
|
51
|
+
throw new StorageError(`evidence commitment missing for node "${nodeId}"`, 'corrupt', { key: indexKey(nodeId) });
|
|
52
|
+
}
|
|
53
|
+
// Byte-identity invariant: the stored bytes must hash to the commitment.
|
|
54
|
+
const digest = toHex(sha3_256(bytes));
|
|
55
|
+
if (contentHash && normalizeEvidenceHash(contentHash) !== digest) {
|
|
56
|
+
throw new StorageError(`evidence commitment mismatch for node "${nodeId}": sha3(bytes) ${digest} !== contentHash ${contentHash}`, 'corrupt', { key: indexKey(nodeId) });
|
|
57
|
+
}
|
|
58
|
+
const receipt = await artifacts.put(namespace, bytes).catch((err) => {
|
|
59
|
+
throw new StorageError(`evidence put failed for node "${nodeId}": ${err.message}`, 'write-failed', { key: indexKey(nodeId), cause: err });
|
|
60
|
+
});
|
|
61
|
+
if (contentHash) {
|
|
62
|
+
// The artifact must address exactly the committed hash.
|
|
63
|
+
assertEvidenceBinding({ nodeId, contentHash, artifact: receipt.ref });
|
|
64
|
+
}
|
|
65
|
+
const entry = {
|
|
66
|
+
ref: receipt.ref,
|
|
67
|
+
...(contentHash ? { contentHash: normalizeEvidenceHash(contentHash) } : {}),
|
|
68
|
+
};
|
|
69
|
+
await index.set(indexKey(nodeId), entry);
|
|
70
|
+
refs.push(receipt.ref);
|
|
71
|
+
}
|
|
72
|
+
return refs;
|
|
73
|
+
},
|
|
74
|
+
async restoreEvidence(graph) {
|
|
75
|
+
const results = [];
|
|
76
|
+
const fail = (err) => {
|
|
77
|
+
if (err instanceof StorageError)
|
|
78
|
+
throw err;
|
|
79
|
+
throw new StorageError(`evidence read failed: ${err.message}`, 'unavailable', { cause: err });
|
|
80
|
+
};
|
|
81
|
+
for (const nodeId of evidenceNodeIds(graph)) {
|
|
82
|
+
const entry = await index.get(indexKey(nodeId));
|
|
83
|
+
if (!entry) {
|
|
84
|
+
results.push({ nodeId, status: 'not-found', message: 'no evidence artifact persisted for node' });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const { ref, contentHash } = entry;
|
|
88
|
+
if (contentHash) {
|
|
89
|
+
// Index-tamper guard: the enforced binding must still hold.
|
|
90
|
+
assertEvidenceBinding({ nodeId, contentHash, artifact: ref });
|
|
91
|
+
}
|
|
92
|
+
const read = await artifacts.get(ref).catch(fail);
|
|
93
|
+
switch (read.status) {
|
|
94
|
+
case 'ok':
|
|
95
|
+
results.push({ nodeId, status: 'ok', ref, ...(contentHash ? { contentHash } : {}), bytes: read.bytes });
|
|
96
|
+
break;
|
|
97
|
+
case 'corrupt':
|
|
98
|
+
if (strict) {
|
|
99
|
+
throw new StorageError(`evidence artifact corrupt for node "${nodeId}": ${read.message ?? 'digest mismatch'}`, 'corrupt', { key: indexKey(nodeId) });
|
|
100
|
+
}
|
|
101
|
+
results.push({ nodeId, status: 'corrupt', ref, message: read.message ?? 'digest mismatch' });
|
|
102
|
+
break;
|
|
103
|
+
case 'unavailable':
|
|
104
|
+
if (strict) {
|
|
105
|
+
throw new StorageError(`evidence artifact unavailable for node "${nodeId}": ${read.message ?? 'backend unavailable'}`, 'unavailable', { key: indexKey(nodeId) });
|
|
106
|
+
}
|
|
107
|
+
results.push({ nodeId, status: 'unavailable', ref, message: read.message ?? 'backend unavailable' });
|
|
108
|
+
break;
|
|
109
|
+
case 'not-found':
|
|
110
|
+
results.push({ nodeId, status: 'not-found', ref, message: read.message ?? 'evidence artifact not found' });
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return results;
|
|
115
|
+
},
|
|
116
|
+
async listCount() {
|
|
117
|
+
let count = 0;
|
|
118
|
+
for (const key of await index.keys()) {
|
|
119
|
+
if (key.startsWith(INDEX_PREFIX))
|
|
120
|
+
count++;
|
|
121
|
+
}
|
|
122
|
+
return count;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
package/dist/graph.d.ts
CHANGED
|
@@ -80,6 +80,17 @@ export declare function addIdentityClaim(graph: ProofGraph, signedClaim: SignedI
|
|
|
80
80
|
* manifests_as manifest → address
|
|
81
81
|
*/
|
|
82
82
|
export declare function addManifest(graph: ProofGraph, signedManifest: SignedManifest): ProofGraph;
|
|
83
|
+
/**
|
|
84
|
+
* Add a signed manifest **revision** without collapsing other revisions.
|
|
85
|
+
*
|
|
86
|
+
* Creates:
|
|
87
|
+
* manifest (logical) refId = manifestId
|
|
88
|
+
* manifest (revision) refId = manifestEnvelopeId, data = { manifestId, manifestEnvelopeId, signedManifest }
|
|
89
|
+
* address resolved author/operator address
|
|
90
|
+
* manifests_as manifestId → address
|
|
91
|
+
* revision_of manifestEnvelopeId → manifestId
|
|
92
|
+
*/
|
|
93
|
+
export declare function addManifestRevision(graph: ProofGraph, signedManifest: SignedManifest): ProofGraph;
|
|
83
94
|
/**
|
|
84
95
|
* Add a receipt-like node (payment, subscription, claim receipt) to the graph.
|
|
85
96
|
* If receipt.proofId is set, adds a 'supports' edge from the receipt to that proof.
|
package/dist/graph.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Proof graph mutation functions — all pure, all returning NEW graphs.
|
|
4
3
|
*
|
|
@@ -6,23 +5,12 @@
|
|
|
6
5
|
* recomputed graphId. Nodes and edges are deduplicated by ID so adding the
|
|
7
6
|
* same proof twice is idempotent.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
exports.buildEdge = buildEdge;
|
|
13
|
-
exports.addNode = addNode;
|
|
14
|
-
exports.addEdge = addEdge;
|
|
15
|
-
exports.addIdentityDocument = addIdentityDocument;
|
|
16
|
-
exports.addProof = addProof;
|
|
17
|
-
exports.addIdentityClaim = addIdentityClaim;
|
|
18
|
-
exports.addManifest = addManifest;
|
|
19
|
-
exports.addReceiptLike = addReceiptLike;
|
|
20
|
-
exports.addAnchor = addAnchor;
|
|
21
|
-
const manifest_1 = require("@totemsdk/manifest");
|
|
22
|
-
const canonical_js_1 = require("./canonical.js");
|
|
8
|
+
import { computeManifestId, computeManifestEnvelopeId } from '@totemsdk/manifest';
|
|
9
|
+
import { computeNodeId, computeEdgeId, computeProofGraphId } from './canonical.js';
|
|
10
|
+
import { normalizeEvidenceHash } from './evidence.js';
|
|
23
11
|
function makeNode(type, refId, data) {
|
|
24
12
|
return {
|
|
25
|
-
id:
|
|
13
|
+
id: computeNodeId(type, refId),
|
|
26
14
|
type,
|
|
27
15
|
refId,
|
|
28
16
|
...(data !== undefined ? { data } : {}),
|
|
@@ -31,7 +19,7 @@ function makeNode(type, refId, data) {
|
|
|
31
19
|
}
|
|
32
20
|
function makeEdge(type, from, to, proofId, data) {
|
|
33
21
|
return {
|
|
34
|
-
id:
|
|
22
|
+
id: computeEdgeId(type, from, to, proofId, data),
|
|
35
23
|
type,
|
|
36
24
|
from,
|
|
37
25
|
to,
|
|
@@ -64,17 +52,17 @@ function rebuild(graph, newNodes, newEdges) {
|
|
|
64
52
|
...graph,
|
|
65
53
|
nodes,
|
|
66
54
|
edges,
|
|
67
|
-
graphId:
|
|
55
|
+
graphId: computeProofGraphId(nodes, edges),
|
|
68
56
|
};
|
|
69
57
|
}
|
|
70
58
|
/**
|
|
71
59
|
* Create an empty ProofGraph.
|
|
72
60
|
*/
|
|
73
|
-
function createProofGraph(metadata) {
|
|
61
|
+
export function createProofGraph(metadata) {
|
|
74
62
|
const nodes = [];
|
|
75
63
|
const edges = [];
|
|
76
64
|
return {
|
|
77
|
-
graphId:
|
|
65
|
+
graphId: computeProofGraphId(nodes, edges),
|
|
78
66
|
nodes,
|
|
79
67
|
edges,
|
|
80
68
|
createdAt: Date.now(),
|
|
@@ -84,39 +72,39 @@ function createProofGraph(metadata) {
|
|
|
84
72
|
/**
|
|
85
73
|
* Attach or replace mutable metadata. Does NOT affect graphId.
|
|
86
74
|
*/
|
|
87
|
-
function setGraphMetadata(graph, metadata) {
|
|
75
|
+
export function setGraphMetadata(graph, metadata) {
|
|
88
76
|
return { ...graph, metadata };
|
|
89
77
|
}
|
|
90
78
|
/**
|
|
91
79
|
* Build a ProofGraphEdge with a deterministic ID from its fields.
|
|
92
80
|
* Convenience helper so callers don't need to import computeEdgeId.
|
|
93
81
|
*/
|
|
94
|
-
function buildEdge(type, from, to, proofId, data) {
|
|
82
|
+
export function buildEdge(type, from, to, proofId, data) {
|
|
95
83
|
return makeEdge(type, from, to, proofId, data);
|
|
96
84
|
}
|
|
97
85
|
/**
|
|
98
86
|
* Add a node directly (type + refId pair). Idempotent — skipped if the node already exists.
|
|
99
87
|
*/
|
|
100
|
-
function addNode(graph, type, refId, data) {
|
|
88
|
+
export function addNode(graph, type, refId, data) {
|
|
101
89
|
return rebuild(graph, [makeNode(type, refId, data)], []);
|
|
102
90
|
}
|
|
103
91
|
/**
|
|
104
92
|
* Append a ProofGraphEdge and recompute graphId. Idempotent by edge ID.
|
|
105
93
|
* Spec API: addEdge(graph, edge). Use buildEdge() to construct the edge object.
|
|
106
94
|
*/
|
|
107
|
-
function addEdge(graph, edge) {
|
|
95
|
+
export function addEdge(graph, edge) {
|
|
108
96
|
const edges = mergeEdges(graph.edges, [edge]);
|
|
109
97
|
return {
|
|
110
98
|
...graph,
|
|
111
99
|
edges,
|
|
112
|
-
graphId:
|
|
100
|
+
graphId: computeProofGraphId(graph.nodes, edges),
|
|
113
101
|
};
|
|
114
102
|
}
|
|
115
103
|
/**
|
|
116
104
|
* Index a TotemIdentityDocument into the graph.
|
|
117
105
|
* Creates an 'identity' node with refId = doc.id.
|
|
118
106
|
*/
|
|
119
|
-
function addIdentityDocument(graph, doc) {
|
|
107
|
+
export function addIdentityDocument(graph, doc) {
|
|
120
108
|
return rebuild(graph, [makeNode('identity', doc.id, { ...doc })], []);
|
|
121
109
|
}
|
|
122
110
|
/**
|
|
@@ -136,7 +124,7 @@ function addIdentityDocument(graph, doc) {
|
|
|
136
124
|
* references proof → evidence (one per evidence ref, in array order)
|
|
137
125
|
* anchored_to proof → anchor (if present)
|
|
138
126
|
*/
|
|
139
|
-
function addProof(graph, signedProof) {
|
|
127
|
+
export function addProof(graph, signedProof) {
|
|
140
128
|
const proofNode = makeNode('proof', signedProof.proofId, {
|
|
141
129
|
...signedProof,
|
|
142
130
|
});
|
|
@@ -150,7 +138,9 @@ function addProof(graph, signedProof) {
|
|
|
150
138
|
];
|
|
151
139
|
if (signedProof.evidence) {
|
|
152
140
|
for (const ev of signedProof.evidence) {
|
|
153
|
-
|
|
141
|
+
// RFC-007 Amendment A (A3): carry the claim commitment on the evidence
|
|
142
|
+
// node so the evidence store can enforce claim→contentHash→artifact.
|
|
143
|
+
newNodes.push(makeNode('evidence', ev.id, ev.hash ? { contentHash: normalizeEvidenceHash(ev.hash) } : undefined));
|
|
154
144
|
newEdges.push(makeEdge('references', signedProof.proofId, ev.id, signedProof.proofId));
|
|
155
145
|
}
|
|
156
146
|
}
|
|
@@ -174,7 +164,7 @@ function addProof(graph, signedProof) {
|
|
|
174
164
|
* revokes → revokes edge (claim-node → subject identity)
|
|
175
165
|
* rotates_to → controls edge (claim-node → new address)
|
|
176
166
|
*/
|
|
177
|
-
function addIdentityClaim(graph, signedClaim) {
|
|
167
|
+
export function addIdentityClaim(graph, signedClaim) {
|
|
178
168
|
const { claim } = signedClaim;
|
|
179
169
|
const claimNode = makeNode('identity-claim', claim.id, {
|
|
180
170
|
...signedClaim,
|
|
@@ -228,8 +218,8 @@ function resolveManifestAddress(signedManifest) {
|
|
|
228
218
|
* Edge created:
|
|
229
219
|
* manifests_as manifest → address
|
|
230
220
|
*/
|
|
231
|
-
function addManifest(graph, signedManifest) {
|
|
232
|
-
const manifestId =
|
|
221
|
+
export function addManifest(graph, signedManifest) {
|
|
222
|
+
const manifestId = computeManifestId(signedManifest.manifest);
|
|
233
223
|
const address = resolveManifestAddress(signedManifest);
|
|
234
224
|
const manifestNode = makeNode('manifest', manifestId, {
|
|
235
225
|
...signedManifest,
|
|
@@ -239,11 +229,37 @@ function addManifest(graph, signedManifest) {
|
|
|
239
229
|
makeEdge('manifests_as', manifestId, address),
|
|
240
230
|
]);
|
|
241
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Add a signed manifest **revision** without collapsing other revisions.
|
|
234
|
+
*
|
|
235
|
+
* Creates:
|
|
236
|
+
* manifest (logical) refId = manifestId
|
|
237
|
+
* manifest (revision) refId = manifestEnvelopeId, data = { manifestId, manifestEnvelopeId, signedManifest }
|
|
238
|
+
* address resolved author/operator address
|
|
239
|
+
* manifests_as manifestId → address
|
|
240
|
+
* revision_of manifestEnvelopeId → manifestId
|
|
241
|
+
*/
|
|
242
|
+
export function addManifestRevision(graph, signedManifest) {
|
|
243
|
+
const manifestId = computeManifestId(signedManifest.manifest);
|
|
244
|
+
const manifestEnvelopeId = computeManifestEnvelopeId(signedManifest);
|
|
245
|
+
const address = resolveManifestAddress(signedManifest);
|
|
246
|
+
const logicalNode = makeNode('manifest', manifestId, { manifestId });
|
|
247
|
+
const revisionNode = makeNode('manifest', manifestEnvelopeId, {
|
|
248
|
+
manifestId,
|
|
249
|
+
manifestEnvelopeId,
|
|
250
|
+
signedManifest,
|
|
251
|
+
});
|
|
252
|
+
const addressNode = makeNode('address', address);
|
|
253
|
+
return rebuild(graph, [logicalNode, revisionNode, addressNode], [
|
|
254
|
+
makeEdge('manifests_as', manifestId, address),
|
|
255
|
+
makeEdge('revision_of', manifestEnvelopeId, manifestId),
|
|
256
|
+
]);
|
|
257
|
+
}
|
|
242
258
|
/**
|
|
243
259
|
* Add a receipt-like node (payment, subscription, claim receipt) to the graph.
|
|
244
260
|
* If receipt.proofId is set, adds a 'supports' edge from the receipt to that proof.
|
|
245
261
|
*/
|
|
246
|
-
function addReceiptLike(graph, receipt) {
|
|
262
|
+
export function addReceiptLike(graph, receipt) {
|
|
247
263
|
const { id, data, proofId } = receipt;
|
|
248
264
|
const receiptNode = makeNode('receipt', id, data);
|
|
249
265
|
const newEdges = [];
|
|
@@ -256,7 +272,7 @@ function addReceiptLike(graph, receipt) {
|
|
|
256
272
|
* Add a standalone anchor node. If anchor.proofId is set, adds an 'anchored_to'
|
|
257
273
|
* edge from the proof to this anchor.
|
|
258
274
|
*/
|
|
259
|
-
function addAnchor(graph, anchor) {
|
|
275
|
+
export function addAnchor(graph, anchor) {
|
|
260
276
|
const { hash, proofId, ...rest } = anchor;
|
|
261
277
|
const nodeData = { ...rest };
|
|
262
278
|
const anchorNode = makeNode('anchor', hash, Object.keys(nodeData).length > 0 ? nodeData : undefined);
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,13 @@
|
|
|
14
14
|
* const result = verifyProofGraph(g1);
|
|
15
15
|
*/
|
|
16
16
|
export type { ProofGraphNodeType, ProofGraphEdgeType, ProofGraphNode, ProofGraphEdge, ProofGraph, ProofGraphVerifyResult, ProofGraphStoragePort, ReceiptLikeInput, AnchorInput, } from './types.js';
|
|
17
|
+
export { createDurableProofGraphStore, } from './durable-store.js';
|
|
18
|
+
export type { DurableProofGraphStore, DurableProofGraphStoreOptions, ProofGraphRecoveryReport, } from './durable-store.js';
|
|
19
|
+
export { createProofGraphEvidenceStore, assertEvidenceBinding, normalizeEvidenceHash, } from './evidence.js';
|
|
20
|
+
export type { ProofGraphEvidenceStore, ProofGraphEvidenceStoreOptions, ProofGraphEvidenceResult, EvidenceArtifactBinding, PutEvidenceOptions, } from './evidence.js';
|
|
17
21
|
export { toHex, canonicalJson, computeNodeId, computeEdgeId, computeProofGraphId, recomputeGraphId, } from './canonical.js';
|
|
18
|
-
export { createProofGraph, setGraphMetadata, buildEdge, addNode, addEdge, addIdentityDocument, addProof, addIdentityClaim, addManifest, addReceiptLike, addAnchor, } from './graph.js';
|
|
19
|
-
export { findNode, getEdgesFrom, getEdgesTo, getEdgesByType, getProofNodes, findProofsBySubject, findProofsByIssuer, findAnchorsForProof, findRevocations, findSupersessions, findConflicts, getEvidenceTrail, getProofLineage, resolveCurrentProofSet, getManifestsForAddress, getEdgesBetween, reachableFrom, } from './query.js';
|
|
22
|
+
export { createProofGraph, setGraphMetadata, buildEdge, addNode, addEdge, addIdentityDocument, addProof, addIdentityClaim, addManifest, addManifestRevision, addReceiptLike, addAnchor, } from './graph.js';
|
|
23
|
+
export { findNode, getEdgesFrom, getEdgesTo, getEdgesByType, getProofNodes, findProofsBySubject, findProofsByIssuer, findAnchorsForProof, findRevocations, findSupersessions, findConflicts, getEvidenceTrail, getProofLineage, resolveCurrentProofSet, getManifestRevisions, getManifestsForAddress, getEdgesBetween, reachableFrom, } from './query.js';
|
|
20
24
|
export { verifyProofGraph, verifyGraphProofs, } from './verify.js';
|
|
21
25
|
export { exportProofGraph, importProofGraph, } from './io.js';
|
|
26
|
+
export * from './replicated-evidence.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* @module @totemsdk/proofgraph
|
|
4
3
|
*
|
|
@@ -14,48 +13,11 @@
|
|
|
14
13
|
* const g1 = addProof(g0, signedProof);
|
|
15
14
|
* const result = verifyProofGraph(g1);
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Object.defineProperty(exports, "recomputeGraphId", { enumerable: true, get: function () { return canonical_js_1.recomputeGraphId; } });
|
|
26
|
-
var graph_js_1 = require("./graph.js");
|
|
27
|
-
Object.defineProperty(exports, "createProofGraph", { enumerable: true, get: function () { return graph_js_1.createProofGraph; } });
|
|
28
|
-
Object.defineProperty(exports, "setGraphMetadata", { enumerable: true, get: function () { return graph_js_1.setGraphMetadata; } });
|
|
29
|
-
Object.defineProperty(exports, "buildEdge", { enumerable: true, get: function () { return graph_js_1.buildEdge; } });
|
|
30
|
-
Object.defineProperty(exports, "addNode", { enumerable: true, get: function () { return graph_js_1.addNode; } });
|
|
31
|
-
Object.defineProperty(exports, "addEdge", { enumerable: true, get: function () { return graph_js_1.addEdge; } });
|
|
32
|
-
Object.defineProperty(exports, "addIdentityDocument", { enumerable: true, get: function () { return graph_js_1.addIdentityDocument; } });
|
|
33
|
-
Object.defineProperty(exports, "addProof", { enumerable: true, get: function () { return graph_js_1.addProof; } });
|
|
34
|
-
Object.defineProperty(exports, "addIdentityClaim", { enumerable: true, get: function () { return graph_js_1.addIdentityClaim; } });
|
|
35
|
-
Object.defineProperty(exports, "addManifest", { enumerable: true, get: function () { return graph_js_1.addManifest; } });
|
|
36
|
-
Object.defineProperty(exports, "addReceiptLike", { enumerable: true, get: function () { return graph_js_1.addReceiptLike; } });
|
|
37
|
-
Object.defineProperty(exports, "addAnchor", { enumerable: true, get: function () { return graph_js_1.addAnchor; } });
|
|
38
|
-
var query_js_1 = require("./query.js");
|
|
39
|
-
Object.defineProperty(exports, "findNode", { enumerable: true, get: function () { return query_js_1.findNode; } });
|
|
40
|
-
Object.defineProperty(exports, "getEdgesFrom", { enumerable: true, get: function () { return query_js_1.getEdgesFrom; } });
|
|
41
|
-
Object.defineProperty(exports, "getEdgesTo", { enumerable: true, get: function () { return query_js_1.getEdgesTo; } });
|
|
42
|
-
Object.defineProperty(exports, "getEdgesByType", { enumerable: true, get: function () { return query_js_1.getEdgesByType; } });
|
|
43
|
-
Object.defineProperty(exports, "getProofNodes", { enumerable: true, get: function () { return query_js_1.getProofNodes; } });
|
|
44
|
-
Object.defineProperty(exports, "findProofsBySubject", { enumerable: true, get: function () { return query_js_1.findProofsBySubject; } });
|
|
45
|
-
Object.defineProperty(exports, "findProofsByIssuer", { enumerable: true, get: function () { return query_js_1.findProofsByIssuer; } });
|
|
46
|
-
Object.defineProperty(exports, "findAnchorsForProof", { enumerable: true, get: function () { return query_js_1.findAnchorsForProof; } });
|
|
47
|
-
Object.defineProperty(exports, "findRevocations", { enumerable: true, get: function () { return query_js_1.findRevocations; } });
|
|
48
|
-
Object.defineProperty(exports, "findSupersessions", { enumerable: true, get: function () { return query_js_1.findSupersessions; } });
|
|
49
|
-
Object.defineProperty(exports, "findConflicts", { enumerable: true, get: function () { return query_js_1.findConflicts; } });
|
|
50
|
-
Object.defineProperty(exports, "getEvidenceTrail", { enumerable: true, get: function () { return query_js_1.getEvidenceTrail; } });
|
|
51
|
-
Object.defineProperty(exports, "getProofLineage", { enumerable: true, get: function () { return query_js_1.getProofLineage; } });
|
|
52
|
-
Object.defineProperty(exports, "resolveCurrentProofSet", { enumerable: true, get: function () { return query_js_1.resolveCurrentProofSet; } });
|
|
53
|
-
Object.defineProperty(exports, "getManifestsForAddress", { enumerable: true, get: function () { return query_js_1.getManifestsForAddress; } });
|
|
54
|
-
Object.defineProperty(exports, "getEdgesBetween", { enumerable: true, get: function () { return query_js_1.getEdgesBetween; } });
|
|
55
|
-
Object.defineProperty(exports, "reachableFrom", { enumerable: true, get: function () { return query_js_1.reachableFrom; } });
|
|
56
|
-
var verify_js_1 = require("./verify.js");
|
|
57
|
-
Object.defineProperty(exports, "verifyProofGraph", { enumerable: true, get: function () { return verify_js_1.verifyProofGraph; } });
|
|
58
|
-
Object.defineProperty(exports, "verifyGraphProofs", { enumerable: true, get: function () { return verify_js_1.verifyGraphProofs; } });
|
|
59
|
-
var io_js_1 = require("./io.js");
|
|
60
|
-
Object.defineProperty(exports, "exportProofGraph", { enumerable: true, get: function () { return io_js_1.exportProofGraph; } });
|
|
61
|
-
Object.defineProperty(exports, "importProofGraph", { enumerable: true, get: function () { return io_js_1.importProofGraph; } });
|
|
16
|
+
export { createDurableProofGraphStore, } from './durable-store.js';
|
|
17
|
+
export { createProofGraphEvidenceStore, assertEvidenceBinding, normalizeEvidenceHash, } from './evidence.js';
|
|
18
|
+
export { toHex, canonicalJson, computeNodeId, computeEdgeId, computeProofGraphId, recomputeGraphId, } from './canonical.js'; // canonicalJson / toHex re-exported from @totemsdk/proof
|
|
19
|
+
export { createProofGraph, setGraphMetadata, buildEdge, addNode, addEdge, addIdentityDocument, addProof, addIdentityClaim, addManifest, addManifestRevision, addReceiptLike, addAnchor, } from './graph.js';
|
|
20
|
+
export { findNode, getEdgesFrom, getEdgesTo, getEdgesByType, getProofNodes, findProofsBySubject, findProofsByIssuer, findAnchorsForProof, findRevocations, findSupersessions, findConflicts, getEvidenceTrail, getProofLineage, resolveCurrentProofSet, getManifestRevisions, getManifestsForAddress, getEdgesBetween, reachableFrom, } from './query.js';
|
|
21
|
+
export { verifyProofGraph, verifyGraphProofs, } from './verify.js';
|
|
22
|
+
export { exportProofGraph, importProofGraph, } from './io.js';
|
|
23
|
+
export * from './replicated-evidence.js';
|
package/dist/io.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Serialization helpers for ProofGraph.
|
|
4
3
|
*
|
|
@@ -9,10 +8,7 @@
|
|
|
9
8
|
* The graphId guards against silent corruption in transit. Node/edge
|
|
10
9
|
* validation prevents hostile or malformed input from entering the system.
|
|
11
10
|
*/
|
|
12
|
-
|
|
13
|
-
exports.exportProofGraph = exportProofGraph;
|
|
14
|
-
exports.importProofGraph = importProofGraph;
|
|
15
|
-
const canonical_js_1 = require("./canonical.js");
|
|
11
|
+
import { recomputeGraphId } from './canonical.js';
|
|
16
12
|
const VALID_NODE_TYPES = new Set([
|
|
17
13
|
'proof', 'identity', 'identity-claim', 'manifest', 'address',
|
|
18
14
|
'subject', 'evidence', 'anchor', 'receipt', 'payment', 'device',
|
|
@@ -121,7 +117,7 @@ function checkDepth(obj, depth, maxDepth, path) {
|
|
|
121
117
|
* Serialize a ProofGraph to a JSON string.
|
|
122
118
|
* The stored graphId was computed at mutation time and is included as-is.
|
|
123
119
|
*/
|
|
124
|
-
function exportProofGraph(graph) {
|
|
120
|
+
export function exportProofGraph(graph) {
|
|
125
121
|
return JSON.stringify(graph);
|
|
126
122
|
}
|
|
127
123
|
/**
|
|
@@ -133,7 +129,7 @@ function exportProofGraph(graph) {
|
|
|
133
129
|
*
|
|
134
130
|
* @throws Error if parsing, validation, or graphId verification fails.
|
|
135
131
|
*/
|
|
136
|
-
function importProofGraph(json) {
|
|
132
|
+
export function importProofGraph(json) {
|
|
137
133
|
let parsed;
|
|
138
134
|
try {
|
|
139
135
|
parsed = JSON.parse(json);
|
|
@@ -163,7 +159,7 @@ function importProofGraph(json) {
|
|
|
163
159
|
}
|
|
164
160
|
// Recompute and verify graphId
|
|
165
161
|
const originalId = typeof graph.graphId === 'string' ? graph.graphId : '';
|
|
166
|
-
const recomputed =
|
|
162
|
+
const recomputed = recomputeGraphId(graph);
|
|
167
163
|
// If a stored graphId exists, verify it matches
|
|
168
164
|
if (originalId !== '' && recomputed !== originalId) {
|
|
169
165
|
throw new Error(`importProofGraph: graphId mismatch — stored="${originalId}" recomputed="${recomputed}"`);
|
package/dist/query.d.ts
CHANGED
|
@@ -70,6 +70,10 @@ export declare function resolveCurrentProofSet(graph: ProofGraph): ProofGraphNod
|
|
|
70
70
|
* Return all manifest nodes linked to a given address via 'manifests_as' edges.
|
|
71
71
|
*/
|
|
72
72
|
export declare function getManifestsForAddress(graph: ProofGraph, address: string): ProofGraphNode[];
|
|
73
|
+
/**
|
|
74
|
+
* Return all content-addressed revisions of a logical manifest, ordered by envelope ID.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getManifestRevisions(graph: ProofGraph, manifestId: string): ProofGraphNode[];
|
|
73
77
|
/**
|
|
74
78
|
* Return all edges between two nodes (in either direction).
|
|
75
79
|
*/
|
package/dist/query.js
CHANGED
|
@@ -1,38 +1,19 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Query helpers for ProofGraph — pure read-only utilities.
|
|
4
3
|
*
|
|
5
4
|
* All functions receive a ProofGraph and return data without mutating the graph.
|
|
6
5
|
* Function names match the @totemsdk/proofgraph public API spec exactly.
|
|
7
6
|
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.findNode = findNode;
|
|
10
|
-
exports.getEdgesFrom = getEdgesFrom;
|
|
11
|
-
exports.getEdgesTo = getEdgesTo;
|
|
12
|
-
exports.getEdgesByType = getEdgesByType;
|
|
13
|
-
exports.getProofNodes = getProofNodes;
|
|
14
|
-
exports.findProofsBySubject = findProofsBySubject;
|
|
15
|
-
exports.findProofsByIssuer = findProofsByIssuer;
|
|
16
|
-
exports.findAnchorsForProof = findAnchorsForProof;
|
|
17
|
-
exports.findRevocations = findRevocations;
|
|
18
|
-
exports.findSupersessions = findSupersessions;
|
|
19
|
-
exports.findConflicts = findConflicts;
|
|
20
|
-
exports.getEvidenceTrail = getEvidenceTrail;
|
|
21
|
-
exports.getProofLineage = getProofLineage;
|
|
22
|
-
exports.resolveCurrentProofSet = resolveCurrentProofSet;
|
|
23
|
-
exports.getManifestsForAddress = getManifestsForAddress;
|
|
24
|
-
exports.getEdgesBetween = getEdgesBetween;
|
|
25
|
-
exports.reachableFrom = reachableFrom;
|
|
26
7
|
/**
|
|
27
8
|
* Find a node by its composed node ID (type:refId) or by refId alone.
|
|
28
9
|
*/
|
|
29
|
-
function findNode(graph, id) {
|
|
10
|
+
export function findNode(graph, id) {
|
|
30
11
|
return graph.nodes.find((n) => n.id === id || n.refId === id);
|
|
31
12
|
}
|
|
32
13
|
/**
|
|
33
14
|
* Return all outgoing edges from a node (identified by node ID or refId).
|
|
34
15
|
*/
|
|
35
|
-
function getEdgesFrom(graph, nodeIdOrRefId) {
|
|
16
|
+
export function getEdgesFrom(graph, nodeIdOrRefId) {
|
|
36
17
|
const node = findNode(graph, nodeIdOrRefId);
|
|
37
18
|
if (!node)
|
|
38
19
|
return [];
|
|
@@ -41,7 +22,7 @@ function getEdgesFrom(graph, nodeIdOrRefId) {
|
|
|
41
22
|
/**
|
|
42
23
|
* Return all incoming edges to a node (identified by node ID or refId).
|
|
43
24
|
*/
|
|
44
|
-
function getEdgesTo(graph, nodeIdOrRefId) {
|
|
25
|
+
export function getEdgesTo(graph, nodeIdOrRefId) {
|
|
45
26
|
const node = findNode(graph, nodeIdOrRefId);
|
|
46
27
|
if (!node)
|
|
47
28
|
return [];
|
|
@@ -50,19 +31,19 @@ function getEdgesTo(graph, nodeIdOrRefId) {
|
|
|
50
31
|
/**
|
|
51
32
|
* Return all edges of a given type.
|
|
52
33
|
*/
|
|
53
|
-
function getEdgesByType(graph, type) {
|
|
34
|
+
export function getEdgesByType(graph, type) {
|
|
54
35
|
return graph.edges.filter((e) => e.type === type);
|
|
55
36
|
}
|
|
56
37
|
/**
|
|
57
38
|
* Return all proof nodes in the graph.
|
|
58
39
|
*/
|
|
59
|
-
function getProofNodes(graph) {
|
|
40
|
+
export function getProofNodes(graph) {
|
|
60
41
|
return graph.nodes.filter((n) => n.type === 'proof');
|
|
61
42
|
}
|
|
62
43
|
/**
|
|
63
44
|
* Find all 'proof' nodes connected to a subject via 'about' OR 'proves' edges.
|
|
64
45
|
*/
|
|
65
|
-
function findProofsBySubject(graph, subjectId) {
|
|
46
|
+
export function findProofsBySubject(graph, subjectId) {
|
|
66
47
|
const relevantEdges = graph.edges.filter((e) => (e.type === 'about' || e.type === 'proves') && e.to === subjectId);
|
|
67
48
|
const proofRefIds = new Set(relevantEdges.map((e) => e.from));
|
|
68
49
|
return graph.nodes.filter((n) => proofRefIds.has(n.refId) && n.type === 'proof');
|
|
@@ -70,7 +51,7 @@ function findProofsBySubject(graph, subjectId) {
|
|
|
70
51
|
/**
|
|
71
52
|
* Find all 'proof' nodes issued by a given address (via 'issued_by' edges).
|
|
72
53
|
*/
|
|
73
|
-
function findProofsByIssuer(graph, issuerId) {
|
|
54
|
+
export function findProofsByIssuer(graph, issuerId) {
|
|
74
55
|
const issuedEdges = graph.edges.filter((e) => e.type === 'issued_by' && e.to === issuerId);
|
|
75
56
|
const proofRefIds = new Set(issuedEdges.map((e) => e.from));
|
|
76
57
|
return graph.nodes.filter((n) => proofRefIds.has(n.refId) && n.type === 'proof');
|
|
@@ -78,7 +59,7 @@ function findProofsByIssuer(graph, issuerId) {
|
|
|
78
59
|
/**
|
|
79
60
|
* Find all 'anchor' nodes linked to a proof via 'anchored_to' edges.
|
|
80
61
|
*/
|
|
81
|
-
function findAnchorsForProof(graph, proofId) {
|
|
62
|
+
export function findAnchorsForProof(graph, proofId) {
|
|
82
63
|
const anchoredEdges = graph.edges.filter((e) => e.type === 'anchored_to' && e.from === proofId);
|
|
83
64
|
const anchorRefIds = new Set(anchoredEdges.map((e) => e.to));
|
|
84
65
|
return graph.nodes.filter((n) => anchorRefIds.has(n.refId) && n.type === 'anchor');
|
|
@@ -86,19 +67,19 @@ function findAnchorsForProof(graph, proofId) {
|
|
|
86
67
|
/**
|
|
87
68
|
* Find all 'revokes' edges whose target is the given proof node.
|
|
88
69
|
*/
|
|
89
|
-
function findRevocations(graph, proofId) {
|
|
70
|
+
export function findRevocations(graph, proofId) {
|
|
90
71
|
return graph.edges.filter((e) => e.type === 'revokes' && e.to === proofId);
|
|
91
72
|
}
|
|
92
73
|
/**
|
|
93
74
|
* Find all 'supersedes' edges whose target is the given proof node.
|
|
94
75
|
*/
|
|
95
|
-
function findSupersessions(graph, proofId) {
|
|
76
|
+
export function findSupersessions(graph, proofId) {
|
|
96
77
|
return graph.edges.filter((e) => e.type === 'supersedes' && e.to === proofId);
|
|
97
78
|
}
|
|
98
79
|
/**
|
|
99
80
|
* Find all 'conflicts_with' edges involving the given proof node (in either direction).
|
|
100
81
|
*/
|
|
101
|
-
function findConflicts(graph, proofId) {
|
|
82
|
+
export function findConflicts(graph, proofId) {
|
|
102
83
|
return graph.edges.filter((e) => e.type === 'conflicts_with' &&
|
|
103
84
|
(e.from === proofId || e.to === proofId));
|
|
104
85
|
}
|
|
@@ -107,7 +88,7 @@ function findConflicts(graph, proofId) {
|
|
|
107
88
|
* Returns nodes in insertion order (the order edges were added, which matches
|
|
108
89
|
* the original evidence array order from addProof).
|
|
109
90
|
*/
|
|
110
|
-
function getEvidenceTrail(graph, proofId) {
|
|
91
|
+
export function getEvidenceTrail(graph, proofId) {
|
|
111
92
|
const refEdges = graph.edges.filter((e) => e.type === 'references' && e.from === proofId);
|
|
112
93
|
return refEdges
|
|
113
94
|
.map((e) => graph.nodes.find((n) => n.refId === e.to && n.type === 'evidence'))
|
|
@@ -118,7 +99,7 @@ function getEvidenceTrail(graph, proofId) {
|
|
|
118
99
|
* Returns the ordered chain of ancestor proof nodes (closest ancestor first).
|
|
119
100
|
* Terminates on cycle detection.
|
|
120
101
|
*/
|
|
121
|
-
function getProofLineage(graph, proofId, _visited = new Set()) {
|
|
102
|
+
export function getProofLineage(graph, proofId, _visited = new Set()) {
|
|
122
103
|
if (_visited.has(proofId))
|
|
123
104
|
return [];
|
|
124
105
|
_visited.add(proofId);
|
|
@@ -137,7 +118,7 @@ function getProofLineage(graph, proofId, _visited = new Set()) {
|
|
|
137
118
|
* Return all proof nodes that are NOT the target of any 'revokes' or 'supersedes' edge.
|
|
138
119
|
* These are the current / active proofs in the graph.
|
|
139
120
|
*/
|
|
140
|
-
function resolveCurrentProofSet(graph) {
|
|
121
|
+
export function resolveCurrentProofSet(graph) {
|
|
141
122
|
const supersededOrRevoked = new Set();
|
|
142
123
|
for (const edge of graph.edges) {
|
|
143
124
|
if (edge.type === 'revokes' || edge.type === 'supersedes') {
|
|
@@ -149,15 +130,26 @@ function resolveCurrentProofSet(graph) {
|
|
|
149
130
|
/**
|
|
150
131
|
* Return all manifest nodes linked to a given address via 'manifests_as' edges.
|
|
151
132
|
*/
|
|
152
|
-
function getManifestsForAddress(graph, address) {
|
|
133
|
+
export function getManifestsForAddress(graph, address) {
|
|
153
134
|
const manifestEdges = graph.edges.filter((e) => e.type === 'manifests_as' && e.to === address);
|
|
154
135
|
const manifestRefIds = new Set(manifestEdges.map((e) => e.from));
|
|
155
136
|
return graph.nodes.filter((n) => manifestRefIds.has(n.refId) && n.type === 'manifest');
|
|
156
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Return all content-addressed revisions of a logical manifest, ordered by envelope ID.
|
|
140
|
+
*/
|
|
141
|
+
export function getManifestRevisions(graph, manifestId) {
|
|
142
|
+
const revisionRefs = new Set(graph.edges
|
|
143
|
+
.filter((edge) => edge.type === 'revision_of' && edge.to === manifestId)
|
|
144
|
+
.map((edge) => edge.from));
|
|
145
|
+
return graph.nodes
|
|
146
|
+
.filter((node) => node.type === 'manifest' && revisionRefs.has(node.refId))
|
|
147
|
+
.sort((a, b) => a.refId.localeCompare(b.refId));
|
|
148
|
+
}
|
|
157
149
|
/**
|
|
158
150
|
* Return all edges between two nodes (in either direction).
|
|
159
151
|
*/
|
|
160
|
-
function getEdgesBetween(graph, refIdA, refIdB) {
|
|
152
|
+
export function getEdgesBetween(graph, refIdA, refIdB) {
|
|
161
153
|
return graph.edges.filter((e) => (e.from === refIdA && e.to === refIdB) ||
|
|
162
154
|
(e.from === refIdB && e.to === refIdA));
|
|
163
155
|
}
|
|
@@ -165,7 +157,7 @@ function getEdgesBetween(graph, refIdA, refIdB) {
|
|
|
165
157
|
* Return the set of all node refIds reachable from a given node via directed edges.
|
|
166
158
|
* Includes the start node itself.
|
|
167
159
|
*/
|
|
168
|
-
function reachableFrom(graph, startRefId, _visited = new Set()) {
|
|
160
|
+
export function reachableFrom(graph, startRefId, _visited = new Set()) {
|
|
169
161
|
if (_visited.has(startRefId))
|
|
170
162
|
return _visited;
|
|
171
163
|
_visited.add(startRefId);
|