@bcts/envelope 1.0.0-alpha.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/LICENSE +48 -0
- package/README.md +23 -0
- package/dist/index.cjs +2646 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +978 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +978 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +2644 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +2552 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +85 -0
- package/src/base/assertion.ts +179 -0
- package/src/base/assertions.ts +304 -0
- package/src/base/cbor.ts +122 -0
- package/src/base/digest.ts +204 -0
- package/src/base/elide.ts +526 -0
- package/src/base/envelope-decodable.ts +229 -0
- package/src/base/envelope-encodable.ts +71 -0
- package/src/base/envelope.ts +790 -0
- package/src/base/error.ts +421 -0
- package/src/base/index.ts +56 -0
- package/src/base/leaf.ts +226 -0
- package/src/base/queries.ts +374 -0
- package/src/base/walk.ts +241 -0
- package/src/base/wrap.ts +72 -0
- package/src/extension/attachment.ts +369 -0
- package/src/extension/compress.ts +293 -0
- package/src/extension/encrypt.ts +379 -0
- package/src/extension/expression.ts +404 -0
- package/src/extension/index.ts +72 -0
- package/src/extension/proof.ts +276 -0
- package/src/extension/recipient.ts +557 -0
- package/src/extension/salt.ts +223 -0
- package/src/extension/signature.ts +463 -0
- package/src/extension/types.ts +222 -0
- package/src/format/diagnostic.ts +116 -0
- package/src/format/hex.ts +25 -0
- package/src/format/index.ts +13 -0
- package/src/format/tree.ts +168 -0
- package/src/index.ts +32 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/string.ts +48 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// Cbor type available if needed later
|
|
2
|
+
import { Envelope } from "./envelope";
|
|
3
|
+
import type { EnvelopeEncodableValue } from "./envelope-encodable";
|
|
4
|
+
import { EnvelopeError } from "./error";
|
|
5
|
+
|
|
6
|
+
/// Provides methods for querying envelope structure and extracting data.
|
|
7
|
+
///
|
|
8
|
+
/// The `queries` module contains methods for:
|
|
9
|
+
///
|
|
10
|
+
/// 1. **Structural queries**: Methods for examining the envelope's structure
|
|
11
|
+
/// (`subject()`, `assertions()`)
|
|
12
|
+
/// 2. **Type queries**: Methods for determining the envelope's type
|
|
13
|
+
/// (`isLeaf()`, `isNode()`, etc.)
|
|
14
|
+
/// 3. **Content extraction**: Methods for extracting typed content from
|
|
15
|
+
/// envelopes (`extractSubject()`, `extractObjectForPredicate()`)
|
|
16
|
+
/// 4. **Assertion queries**: Methods for finding assertions with specific
|
|
17
|
+
/// predicates (`assertionWithPredicate()`)
|
|
18
|
+
///
|
|
19
|
+
/// These methods enable traversal and inspection of envelope hierarchies,
|
|
20
|
+
/// allowing for flexible manipulation and access to envelope data structures.
|
|
21
|
+
|
|
22
|
+
declare module "./envelope" {
|
|
23
|
+
interface Envelope {
|
|
24
|
+
/// Returns true if the envelope has at least one assertion.
|
|
25
|
+
///
|
|
26
|
+
/// @returns `true` if there are assertions, `false` otherwise
|
|
27
|
+
hasAssertions(): boolean;
|
|
28
|
+
|
|
29
|
+
/// Returns the envelope as an assertion if it is one.
|
|
30
|
+
///
|
|
31
|
+
/// @returns The assertion envelope or undefined
|
|
32
|
+
asAssertion(): Envelope | undefined;
|
|
33
|
+
|
|
34
|
+
/// Returns the envelope as an assertion or throws an error.
|
|
35
|
+
///
|
|
36
|
+
/// @returns The assertion envelope
|
|
37
|
+
/// @throws {EnvelopeError} If the envelope is not an assertion
|
|
38
|
+
tryAssertion(): Envelope;
|
|
39
|
+
|
|
40
|
+
/// Returns the predicate of this assertion envelope.
|
|
41
|
+
///
|
|
42
|
+
/// @returns The predicate envelope or undefined
|
|
43
|
+
asPredicate(): Envelope | undefined;
|
|
44
|
+
|
|
45
|
+
/// Returns the predicate of this assertion envelope or throws an error.
|
|
46
|
+
///
|
|
47
|
+
/// @returns The predicate envelope
|
|
48
|
+
/// @throws {EnvelopeError} If the envelope is not an assertion
|
|
49
|
+
tryPredicate(): Envelope;
|
|
50
|
+
|
|
51
|
+
/// Returns the object of this assertion envelope.
|
|
52
|
+
///
|
|
53
|
+
/// @returns The object envelope or undefined
|
|
54
|
+
asObject(): Envelope | undefined;
|
|
55
|
+
|
|
56
|
+
/// Returns the object of this assertion envelope or throws an error.
|
|
57
|
+
///
|
|
58
|
+
/// @returns The object envelope
|
|
59
|
+
/// @throws {EnvelopeError} If the envelope is not an assertion
|
|
60
|
+
tryObject(): Envelope;
|
|
61
|
+
|
|
62
|
+
/// Checks if this envelope is case Assertion.
|
|
63
|
+
///
|
|
64
|
+
/// @returns `true` if this is an assertion envelope
|
|
65
|
+
isAssertion(): boolean;
|
|
66
|
+
|
|
67
|
+
/// Checks if this envelope is case Elided.
|
|
68
|
+
///
|
|
69
|
+
/// @returns `true` if this is an elided envelope
|
|
70
|
+
isElided(): boolean;
|
|
71
|
+
|
|
72
|
+
/// Checks if this envelope is case Leaf.
|
|
73
|
+
///
|
|
74
|
+
/// @returns `true` if this is a leaf envelope
|
|
75
|
+
isLeaf(): boolean;
|
|
76
|
+
|
|
77
|
+
/// Checks if this envelope is case Node.
|
|
78
|
+
///
|
|
79
|
+
/// @returns `true` if this is a node envelope
|
|
80
|
+
isNode(): boolean;
|
|
81
|
+
|
|
82
|
+
/// Checks if this envelope is case Wrapped.
|
|
83
|
+
///
|
|
84
|
+
/// @returns `true` if this is a wrapped envelope
|
|
85
|
+
isWrapped(): boolean;
|
|
86
|
+
|
|
87
|
+
/// Checks if this envelope is internal (has child elements).
|
|
88
|
+
///
|
|
89
|
+
/// Internal elements include node, wrapped, and assertion.
|
|
90
|
+
///
|
|
91
|
+
/// @returns `true` if this envelope has children
|
|
92
|
+
isInternal(): boolean;
|
|
93
|
+
|
|
94
|
+
/// Checks if this envelope is obscured (elided, encrypted, or compressed).
|
|
95
|
+
///
|
|
96
|
+
/// @returns `true` if this envelope is obscured
|
|
97
|
+
isObscured(): boolean;
|
|
98
|
+
|
|
99
|
+
/// Returns all assertions with the given predicate.
|
|
100
|
+
///
|
|
101
|
+
/// Match is performed by comparing digests.
|
|
102
|
+
///
|
|
103
|
+
/// @param predicate - The predicate to search for
|
|
104
|
+
/// @returns An array of matching assertion envelopes
|
|
105
|
+
assertionsWithPredicate(predicate: EnvelopeEncodableValue): Envelope[];
|
|
106
|
+
|
|
107
|
+
/// Returns the assertion with the given predicate.
|
|
108
|
+
///
|
|
109
|
+
/// @param predicate - The predicate to search for
|
|
110
|
+
/// @returns The matching assertion envelope
|
|
111
|
+
/// @throws {EnvelopeError} If no assertion or multiple assertions match
|
|
112
|
+
assertionWithPredicate(predicate: EnvelopeEncodableValue): Envelope;
|
|
113
|
+
|
|
114
|
+
/// Returns the assertion with the given predicate, or undefined if not
|
|
115
|
+
/// found.
|
|
116
|
+
///
|
|
117
|
+
/// @param predicate - The predicate to search for
|
|
118
|
+
/// @returns The matching assertion envelope or undefined
|
|
119
|
+
/// @throws {EnvelopeError} If multiple assertions match
|
|
120
|
+
optionalAssertionWithPredicate(predicate: EnvelopeEncodableValue): Envelope | undefined;
|
|
121
|
+
|
|
122
|
+
/// Returns the object of the assertion with the given predicate.
|
|
123
|
+
///
|
|
124
|
+
/// @param predicate - The predicate to search for
|
|
125
|
+
/// @returns The object envelope
|
|
126
|
+
/// @throws {EnvelopeError} If no assertion or multiple assertions match
|
|
127
|
+
objectForPredicate(predicate: EnvelopeEncodableValue): Envelope;
|
|
128
|
+
|
|
129
|
+
/// Returns the object of the assertion with the given predicate, or
|
|
130
|
+
/// undefined if not found.
|
|
131
|
+
///
|
|
132
|
+
/// @param predicate - The predicate to search for
|
|
133
|
+
/// @returns The object envelope or undefined
|
|
134
|
+
/// @throws {EnvelopeError} If multiple assertions match
|
|
135
|
+
optionalObjectForPredicate(predicate: EnvelopeEncodableValue): Envelope | undefined;
|
|
136
|
+
|
|
137
|
+
/// Returns the objects of all assertions with the matching predicate.
|
|
138
|
+
///
|
|
139
|
+
/// @param predicate - The predicate to search for
|
|
140
|
+
/// @returns An array of object envelopes
|
|
141
|
+
objectsForPredicate(predicate: EnvelopeEncodableValue): Envelope[];
|
|
142
|
+
|
|
143
|
+
/// Returns the number of structural elements in the envelope.
|
|
144
|
+
///
|
|
145
|
+
/// This includes the envelope itself and all nested elements.
|
|
146
|
+
///
|
|
147
|
+
/// @returns The total element count
|
|
148
|
+
elementsCount(): number;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Implementation of hasAssertions()
|
|
153
|
+
Envelope.prototype.hasAssertions = function (this: Envelope): boolean {
|
|
154
|
+
const c = this.case();
|
|
155
|
+
return c.type === "node" && c.assertions.length > 0;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/// Implementation of asAssertion()
|
|
159
|
+
Envelope.prototype.asAssertion = function (this: Envelope): Envelope | undefined {
|
|
160
|
+
const c = this.case();
|
|
161
|
+
return c.type === "assertion" ? this : undefined;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/// Implementation of tryAssertion()
|
|
165
|
+
Envelope.prototype.tryAssertion = function (this: Envelope): Envelope {
|
|
166
|
+
const result = this.asAssertion();
|
|
167
|
+
if (result === undefined) {
|
|
168
|
+
throw EnvelopeError.notAssertion();
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/// Implementation of asPredicate()
|
|
174
|
+
Envelope.prototype.asPredicate = function (this: Envelope): Envelope | undefined {
|
|
175
|
+
// Refer to subject in case the assertion is a node and therefore has
|
|
176
|
+
// its own assertions
|
|
177
|
+
const subj = this.subject();
|
|
178
|
+
const c = subj.case();
|
|
179
|
+
if (c.type === "assertion") {
|
|
180
|
+
return c.assertion.predicate();
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/// Implementation of tryPredicate()
|
|
186
|
+
Envelope.prototype.tryPredicate = function (this: Envelope): Envelope {
|
|
187
|
+
const result = this.asPredicate();
|
|
188
|
+
if (result === undefined) {
|
|
189
|
+
throw EnvelopeError.notAssertion();
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/// Implementation of asObject()
|
|
195
|
+
Envelope.prototype.asObject = function (this: Envelope): Envelope | undefined {
|
|
196
|
+
// Refer to subject in case the assertion is a node and therefore has
|
|
197
|
+
// its own assertions
|
|
198
|
+
const subj = this.subject();
|
|
199
|
+
const c = subj.case();
|
|
200
|
+
if (c.type === "assertion") {
|
|
201
|
+
return c.assertion.object();
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/// Implementation of tryObject()
|
|
207
|
+
Envelope.prototype.tryObject = function (this: Envelope): Envelope {
|
|
208
|
+
const result = this.asObject();
|
|
209
|
+
if (result === undefined) {
|
|
210
|
+
throw EnvelopeError.notAssertion();
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/// Implementation of isAssertion()
|
|
216
|
+
Envelope.prototype.isAssertion = function (this: Envelope): boolean {
|
|
217
|
+
return this.case().type === "assertion";
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/// Implementation of isElided()
|
|
221
|
+
Envelope.prototype.isElided = function (this: Envelope): boolean {
|
|
222
|
+
return this.case().type === "elided";
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/// Implementation of isLeaf()
|
|
226
|
+
Envelope.prototype.isLeaf = function (this: Envelope): boolean {
|
|
227
|
+
return this.case().type === "leaf";
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/// Implementation of isNode()
|
|
231
|
+
Envelope.prototype.isNode = function (this: Envelope): boolean {
|
|
232
|
+
return this.case().type === "node";
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/// Implementation of isWrapped()
|
|
236
|
+
Envelope.prototype.isWrapped = function (this: Envelope): boolean {
|
|
237
|
+
return this.case().type === "wrapped";
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/// Implementation of isInternal()
|
|
241
|
+
Envelope.prototype.isInternal = function (this: Envelope): boolean {
|
|
242
|
+
const type = this.case().type;
|
|
243
|
+
return type === "node" || type === "wrapped" || type === "assertion";
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/// Implementation of isObscured()
|
|
247
|
+
Envelope.prototype.isObscured = function (this: Envelope): boolean {
|
|
248
|
+
const type = this.case().type;
|
|
249
|
+
return type === "elided" || type === "encrypted" || type === "compressed";
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/// Implementation of assertionsWithPredicate()
|
|
253
|
+
Envelope.prototype.assertionsWithPredicate = function (
|
|
254
|
+
this: Envelope,
|
|
255
|
+
predicate: EnvelopeEncodableValue,
|
|
256
|
+
): Envelope[] {
|
|
257
|
+
const predicateEnv = Envelope.new(predicate);
|
|
258
|
+
const predicateDigest = predicateEnv.digest();
|
|
259
|
+
|
|
260
|
+
return this.assertions().filter((assertion) => {
|
|
261
|
+
const pred = assertion.subject().asPredicate();
|
|
262
|
+
return pred?.digest().equals(predicateDigest) === true;
|
|
263
|
+
});
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/// Implementation of assertionWithPredicate()
|
|
267
|
+
Envelope.prototype.assertionWithPredicate = function (
|
|
268
|
+
this: Envelope,
|
|
269
|
+
predicate: EnvelopeEncodableValue,
|
|
270
|
+
): Envelope {
|
|
271
|
+
const matches = this.assertionsWithPredicate(predicate);
|
|
272
|
+
|
|
273
|
+
if (matches.length === 0) {
|
|
274
|
+
throw EnvelopeError.nonexistentPredicate();
|
|
275
|
+
}
|
|
276
|
+
if (matches.length > 1) {
|
|
277
|
+
throw EnvelopeError.ambiguousPredicate();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return matches[0];
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/// Implementation of optionalAssertionWithPredicate()
|
|
284
|
+
Envelope.prototype.optionalAssertionWithPredicate = function (
|
|
285
|
+
this: Envelope,
|
|
286
|
+
predicate: EnvelopeEncodableValue,
|
|
287
|
+
): Envelope | undefined {
|
|
288
|
+
const matches = this.assertionsWithPredicate(predicate);
|
|
289
|
+
|
|
290
|
+
if (matches.length === 0) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
if (matches.length > 1) {
|
|
294
|
+
throw EnvelopeError.ambiguousPredicate();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return matches[0];
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/// Implementation of objectForPredicate()
|
|
301
|
+
Envelope.prototype.objectForPredicate = function (
|
|
302
|
+
this: Envelope,
|
|
303
|
+
predicate: EnvelopeEncodableValue,
|
|
304
|
+
): Envelope {
|
|
305
|
+
const assertion = this.assertionWithPredicate(predicate);
|
|
306
|
+
const obj = assertion.asObject();
|
|
307
|
+
if (obj === undefined) {
|
|
308
|
+
throw EnvelopeError.notAssertion();
|
|
309
|
+
}
|
|
310
|
+
return obj;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/// Implementation of optionalObjectForPredicate()
|
|
314
|
+
Envelope.prototype.optionalObjectForPredicate = function (
|
|
315
|
+
this: Envelope,
|
|
316
|
+
predicate: EnvelopeEncodableValue,
|
|
317
|
+
): Envelope | undefined {
|
|
318
|
+
const matches = this.assertionsWithPredicate(predicate);
|
|
319
|
+
|
|
320
|
+
if (matches.length === 0) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
if (matches.length > 1) {
|
|
324
|
+
throw EnvelopeError.ambiguousPredicate();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const obj = matches[0].subject().asObject();
|
|
328
|
+
return obj;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/// Implementation of objectsForPredicate()
|
|
332
|
+
Envelope.prototype.objectsForPredicate = function (
|
|
333
|
+
this: Envelope,
|
|
334
|
+
predicate: EnvelopeEncodableValue,
|
|
335
|
+
): Envelope[] {
|
|
336
|
+
return this.assertionsWithPredicate(predicate).map((assertion) => {
|
|
337
|
+
const obj = assertion.asObject();
|
|
338
|
+
if (obj === undefined) {
|
|
339
|
+
throw EnvelopeError.notAssertion();
|
|
340
|
+
}
|
|
341
|
+
return obj;
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/// Implementation of elementsCount()
|
|
346
|
+
Envelope.prototype.elementsCount = function (this: Envelope): number {
|
|
347
|
+
let count = 1; // Count this envelope
|
|
348
|
+
|
|
349
|
+
const c = this.case();
|
|
350
|
+
switch (c.type) {
|
|
351
|
+
case "node":
|
|
352
|
+
count += c.subject.elementsCount();
|
|
353
|
+
for (const assertion of c.assertions) {
|
|
354
|
+
count += assertion.elementsCount();
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
case "assertion":
|
|
358
|
+
count += c.assertion.predicate().elementsCount();
|
|
359
|
+
count += c.assertion.object().elementsCount();
|
|
360
|
+
break;
|
|
361
|
+
case "wrapped":
|
|
362
|
+
count += c.envelope.elementsCount();
|
|
363
|
+
break;
|
|
364
|
+
case "leaf":
|
|
365
|
+
case "elided":
|
|
366
|
+
case "knownValue":
|
|
367
|
+
case "encrypted":
|
|
368
|
+
case "compressed":
|
|
369
|
+
// These cases don't contribute additional elements
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return count;
|
|
374
|
+
};
|
package/src/base/walk.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { Envelope } from "./envelope";
|
|
2
|
+
|
|
3
|
+
/// Functions for traversing and manipulating the envelope hierarchy.
|
|
4
|
+
///
|
|
5
|
+
/// This module provides functionality for traversing the hierarchical structure
|
|
6
|
+
/// of envelopes, allowing for operations such as inspection, transformation,
|
|
7
|
+
/// and extraction of specific elements. It implements a visitor pattern that
|
|
8
|
+
/// enables executing arbitrary code on each element of an envelope in a
|
|
9
|
+
/// structured way.
|
|
10
|
+
///
|
|
11
|
+
/// The traversal can be performed in two modes:
|
|
12
|
+
/// - Structure-based traversal: Visits every element in the envelope hierarchy
|
|
13
|
+
/// - Tree-based traversal: Skips node elements and focuses on the semantic
|
|
14
|
+
/// content
|
|
15
|
+
|
|
16
|
+
/// The type of incoming edge provided to the visitor.
|
|
17
|
+
///
|
|
18
|
+
/// This enum identifies how an envelope element is connected to its parent in
|
|
19
|
+
/// the hierarchy during traversal. It helps the visitor function understand the
|
|
20
|
+
/// semantic relationship between elements.
|
|
21
|
+
export enum EdgeType {
|
|
22
|
+
/// No incoming edge (root)
|
|
23
|
+
None = "none",
|
|
24
|
+
/// Element is the subject of a node
|
|
25
|
+
Subject = "subject",
|
|
26
|
+
/// Element is an assertion on a node
|
|
27
|
+
Assertion = "assertion",
|
|
28
|
+
/// Element is the predicate of an assertion
|
|
29
|
+
Predicate = "predicate",
|
|
30
|
+
/// Element is the object of an assertion
|
|
31
|
+
Object = "object",
|
|
32
|
+
/// Element is the content wrapped by another envelope
|
|
33
|
+
Content = "content",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Returns a short text label for the edge type, or undefined if no label is
|
|
37
|
+
/// needed.
|
|
38
|
+
///
|
|
39
|
+
/// This is primarily used for tree formatting to identify relationships
|
|
40
|
+
/// between elements.
|
|
41
|
+
///
|
|
42
|
+
/// @param edgeType - The edge type
|
|
43
|
+
/// @returns A short label or undefined
|
|
44
|
+
export function edgeLabel(edgeType: EdgeType): string | undefined {
|
|
45
|
+
switch (edgeType) {
|
|
46
|
+
case EdgeType.Subject:
|
|
47
|
+
return "subj";
|
|
48
|
+
case EdgeType.Content:
|
|
49
|
+
return "cont";
|
|
50
|
+
case EdgeType.Predicate:
|
|
51
|
+
return "pred";
|
|
52
|
+
case EdgeType.Object:
|
|
53
|
+
return "obj";
|
|
54
|
+
case EdgeType.None:
|
|
55
|
+
case EdgeType.Assertion:
|
|
56
|
+
return undefined;
|
|
57
|
+
default:
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// A visitor function that is called for each element in the envelope.
|
|
63
|
+
///
|
|
64
|
+
/// The visitor function takes the following parameters:
|
|
65
|
+
/// - `envelope`: The current envelope element being visited
|
|
66
|
+
/// - `level`: The depth level in the hierarchy (0 for root)
|
|
67
|
+
/// - `incomingEdge`: The type of edge connecting this element to its parent
|
|
68
|
+
/// - `state`: Optional context passed down from the parent's visitor call
|
|
69
|
+
///
|
|
70
|
+
/// The visitor returns a tuple of:
|
|
71
|
+
/// - The state that will be passed to child elements
|
|
72
|
+
/// - A boolean indicating whether to stop traversal (true = stop)
|
|
73
|
+
///
|
|
74
|
+
/// This enables accumulating state or passing context during traversal.
|
|
75
|
+
export type Visitor<State> = (
|
|
76
|
+
envelope: Envelope,
|
|
77
|
+
level: number,
|
|
78
|
+
incomingEdge: EdgeType,
|
|
79
|
+
state: State,
|
|
80
|
+
) => [State, boolean];
|
|
81
|
+
|
|
82
|
+
declare module "./envelope" {
|
|
83
|
+
interface Envelope {
|
|
84
|
+
/// Walks the envelope structure, calling the visitor function for each
|
|
85
|
+
/// element.
|
|
86
|
+
///
|
|
87
|
+
/// This function traverses the entire envelope hierarchy and calls the
|
|
88
|
+
/// visitor function on each element. The traversal can be performed in
|
|
89
|
+
/// two modes:
|
|
90
|
+
///
|
|
91
|
+
/// - Structure-based traversal (`hideNodes = false`): Visits every element
|
|
92
|
+
/// including node containers
|
|
93
|
+
/// - Tree-based traversal (`hideNodes = true`): Skips node elements and
|
|
94
|
+
/// focuses on semantic content
|
|
95
|
+
///
|
|
96
|
+
/// The visitor function can optionally return a context value that is
|
|
97
|
+
/// passed to child elements, enabling state to be accumulated or passed
|
|
98
|
+
/// down during traversal.
|
|
99
|
+
///
|
|
100
|
+
/// @param hideNodes - If true, the visitor will not be called for node
|
|
101
|
+
/// containers
|
|
102
|
+
/// @param state - Initial state passed to the visitor
|
|
103
|
+
/// @param visit - The visitor function called for each element
|
|
104
|
+
walk<State>(hideNodes: boolean, state: State, visit: Visitor<State>): void;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Implementation of walk()
|
|
109
|
+
Envelope.prototype.walk = function <State>(
|
|
110
|
+
this: Envelope,
|
|
111
|
+
hideNodes: boolean,
|
|
112
|
+
state: State,
|
|
113
|
+
visit: Visitor<State>,
|
|
114
|
+
): void {
|
|
115
|
+
if (hideNodes) {
|
|
116
|
+
walkTree(this, 0, EdgeType.None, state, visit);
|
|
117
|
+
} else {
|
|
118
|
+
walkStructure(this, 0, EdgeType.None, state, visit);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/// Recursive implementation of structure-based traversal.
|
|
123
|
+
///
|
|
124
|
+
/// This internal function performs the actual recursive traversal of the
|
|
125
|
+
/// envelope structure, visiting every element and maintaining the
|
|
126
|
+
/// correct level and edge relationships.
|
|
127
|
+
function walkStructure<State>(
|
|
128
|
+
envelope: Envelope,
|
|
129
|
+
level: number,
|
|
130
|
+
incomingEdge: EdgeType,
|
|
131
|
+
state: State,
|
|
132
|
+
visit: Visitor<State>,
|
|
133
|
+
): void {
|
|
134
|
+
// Visit this envelope
|
|
135
|
+
const [newState, stop] = visit(envelope, level, incomingEdge, state);
|
|
136
|
+
if (stop) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const nextLevel = level + 1;
|
|
141
|
+
const c = envelope.case();
|
|
142
|
+
|
|
143
|
+
switch (c.type) {
|
|
144
|
+
case "node":
|
|
145
|
+
// Visit subject
|
|
146
|
+
walkStructure(c.subject, nextLevel, EdgeType.Subject, newState, visit);
|
|
147
|
+
// Visit all assertions
|
|
148
|
+
for (const assertion of c.assertions) {
|
|
149
|
+
walkStructure(assertion, nextLevel, EdgeType.Assertion, newState, visit);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
case "wrapped":
|
|
154
|
+
// Visit wrapped envelope
|
|
155
|
+
walkStructure(c.envelope, nextLevel, EdgeType.Content, newState, visit);
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case "assertion":
|
|
159
|
+
// Visit predicate and object
|
|
160
|
+
walkStructure(c.assertion.predicate(), nextLevel, EdgeType.Predicate, newState, visit);
|
|
161
|
+
walkStructure(c.assertion.object(), nextLevel, EdgeType.Object, newState, visit);
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
case "leaf":
|
|
165
|
+
case "elided":
|
|
166
|
+
case "knownValue":
|
|
167
|
+
case "encrypted":
|
|
168
|
+
case "compressed":
|
|
169
|
+
// Leaf nodes and other types have no children
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Recursive implementation of tree-based traversal.
|
|
175
|
+
///
|
|
176
|
+
/// This internal function performs the actual recursive traversal of the
|
|
177
|
+
/// envelope's semantic tree, skipping node containers and focusing on
|
|
178
|
+
/// the semantic content elements. It maintains the correct level and
|
|
179
|
+
/// edge relationships while skipping structural elements.
|
|
180
|
+
function walkTree<State>(
|
|
181
|
+
envelope: Envelope,
|
|
182
|
+
level: number,
|
|
183
|
+
incomingEdge: EdgeType,
|
|
184
|
+
state: State,
|
|
185
|
+
visit: Visitor<State>,
|
|
186
|
+
): State {
|
|
187
|
+
let currentState = state;
|
|
188
|
+
let subjectLevel = level;
|
|
189
|
+
|
|
190
|
+
// Skip visiting if this is a node
|
|
191
|
+
if (!envelope.isNode()) {
|
|
192
|
+
const [newState, stop] = visit(envelope, level, incomingEdge, currentState);
|
|
193
|
+
if (stop) {
|
|
194
|
+
return newState;
|
|
195
|
+
}
|
|
196
|
+
currentState = newState;
|
|
197
|
+
subjectLevel = level + 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const c = envelope.case();
|
|
201
|
+
|
|
202
|
+
switch (c.type) {
|
|
203
|
+
case "node": {
|
|
204
|
+
// Visit subject
|
|
205
|
+
const assertionState = walkTree(
|
|
206
|
+
c.subject,
|
|
207
|
+
subjectLevel,
|
|
208
|
+
EdgeType.Subject,
|
|
209
|
+
currentState,
|
|
210
|
+
visit,
|
|
211
|
+
);
|
|
212
|
+
// Visit all assertions
|
|
213
|
+
const assertionLevel = subjectLevel + 1;
|
|
214
|
+
for (const assertion of c.assertions) {
|
|
215
|
+
walkTree(assertion, assertionLevel, EdgeType.Assertion, assertionState, visit);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
case "wrapped":
|
|
221
|
+
// Visit wrapped envelope
|
|
222
|
+
walkTree(c.envelope, subjectLevel, EdgeType.Content, currentState, visit);
|
|
223
|
+
break;
|
|
224
|
+
|
|
225
|
+
case "assertion":
|
|
226
|
+
// Visit predicate and object
|
|
227
|
+
walkTree(c.assertion.predicate(), subjectLevel, EdgeType.Predicate, currentState, visit);
|
|
228
|
+
walkTree(c.assertion.object(), subjectLevel, EdgeType.Object, currentState, visit);
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case "leaf":
|
|
232
|
+
case "elided":
|
|
233
|
+
case "knownValue":
|
|
234
|
+
case "encrypted":
|
|
235
|
+
case "compressed":
|
|
236
|
+
// Leaf nodes and other types have no children
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return currentState;
|
|
241
|
+
}
|
package/src/base/wrap.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Envelope } from "./envelope";
|
|
2
|
+
import { EnvelopeError } from "./error";
|
|
3
|
+
|
|
4
|
+
/// Support for wrapping and unwrapping envelopes.
|
|
5
|
+
///
|
|
6
|
+
/// Wrapping allows treating an envelope (including its assertions) as a single
|
|
7
|
+
/// unit, making it possible to add assertions about the envelope as a whole.
|
|
8
|
+
|
|
9
|
+
declare module "./envelope" {
|
|
10
|
+
interface Envelope {
|
|
11
|
+
/// Returns a new envelope which wraps the current envelope.
|
|
12
|
+
///
|
|
13
|
+
/// Wrapping an envelope allows you to treat an envelope (including its
|
|
14
|
+
/// assertions) as a single unit, making it possible to add assertions
|
|
15
|
+
/// about the envelope as a whole.
|
|
16
|
+
///
|
|
17
|
+
/// @returns A new wrapped envelope
|
|
18
|
+
///
|
|
19
|
+
/// @example
|
|
20
|
+
/// ```typescript
|
|
21
|
+
/// // Create an envelope with an assertion
|
|
22
|
+
/// const envelope = Envelope.new("Hello.").addAssertion("language", "English");
|
|
23
|
+
///
|
|
24
|
+
/// // Wrap it to add an assertion about the envelope as a whole
|
|
25
|
+
/// const wrapped = envelope.wrap().addAssertion("authenticated", true);
|
|
26
|
+
/// ```
|
|
27
|
+
wrap(): Envelope;
|
|
28
|
+
|
|
29
|
+
/// Unwraps and returns the inner envelope.
|
|
30
|
+
///
|
|
31
|
+
/// This extracts the envelope contained within a wrapped envelope.
|
|
32
|
+
///
|
|
33
|
+
/// @returns The unwrapped envelope
|
|
34
|
+
/// @throws {EnvelopeError} If this is not a wrapped envelope
|
|
35
|
+
///
|
|
36
|
+
/// @example
|
|
37
|
+
/// ```typescript
|
|
38
|
+
/// // Create an envelope and wrap it
|
|
39
|
+
/// const envelope = Envelope.new("Hello.");
|
|
40
|
+
/// const wrapped = envelope.wrap();
|
|
41
|
+
///
|
|
42
|
+
/// // Unwrap to get the original envelope
|
|
43
|
+
/// const unwrapped = wrapped.tryUnwrap();
|
|
44
|
+
/// ```
|
|
45
|
+
tryUnwrap(): Envelope;
|
|
46
|
+
|
|
47
|
+
/// Alias for tryUnwrap() - unwraps and returns the inner envelope.
|
|
48
|
+
///
|
|
49
|
+
/// @returns The unwrapped envelope
|
|
50
|
+
/// @throws {EnvelopeError} If this is not a wrapped envelope
|
|
51
|
+
unwrap(): Envelope;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Implementation of wrap()
|
|
56
|
+
Envelope.prototype.wrap = function (this: Envelope): Envelope {
|
|
57
|
+
return Envelope.newWrapped(this);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/// Implementation of tryUnwrap()
|
|
61
|
+
Envelope.prototype.tryUnwrap = function (this: Envelope): Envelope {
|
|
62
|
+
const c = this.subject().case();
|
|
63
|
+
if (c.type === "wrapped") {
|
|
64
|
+
return c.envelope;
|
|
65
|
+
}
|
|
66
|
+
throw EnvelopeError.notWrapped();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/// Implementation of unwrap() - alias for tryUnwrap()
|
|
70
|
+
Envelope.prototype.unwrap = function (this: Envelope): Envelope {
|
|
71
|
+
return this.tryUnwrap();
|
|
72
|
+
};
|