@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,222 @@
|
|
|
1
|
+
import { Envelope } from "../base/envelope";
|
|
2
|
+
import { type EnvelopeEncodableValue } from "../base/envelope-encodable";
|
|
3
|
+
import { EnvelopeError } from "../base/error";
|
|
4
|
+
|
|
5
|
+
/// Type system for Gordian Envelopes.
|
|
6
|
+
///
|
|
7
|
+
/// This module provides functionality for adding, querying, and verifying types
|
|
8
|
+
/// within envelopes. In Gordian Envelope, types are implemented using the
|
|
9
|
+
/// special `'isA'` predicate (the string "isA"), which is semantically
|
|
10
|
+
/// equivalent to the RDF `rdf:type` concept.
|
|
11
|
+
///
|
|
12
|
+
/// Type information enables:
|
|
13
|
+
/// - Semantic classification of envelopes
|
|
14
|
+
/// - Type verification before processing content
|
|
15
|
+
/// - Conversion between domain objects and envelopes
|
|
16
|
+
/// - Schema validation
|
|
17
|
+
///
|
|
18
|
+
/// ## Type Representation
|
|
19
|
+
///
|
|
20
|
+
/// Types are represented as assertions with the `'isA'` predicate and an object
|
|
21
|
+
/// that specifies the type. The type object is typically a string or an envelope.
|
|
22
|
+
///
|
|
23
|
+
/// ## Usage Patterns
|
|
24
|
+
///
|
|
25
|
+
/// The type system is commonly used in two ways:
|
|
26
|
+
///
|
|
27
|
+
/// 1. **Type Tagging**: Adding type information to envelopes to indicate their
|
|
28
|
+
/// semantic meaning
|
|
29
|
+
///
|
|
30
|
+
/// ```typescript
|
|
31
|
+
/// // Create an envelope representing a person
|
|
32
|
+
/// const person = Envelope.new("Alice")
|
|
33
|
+
/// .addType("Person")
|
|
34
|
+
/// .addAssertion("age", 30);
|
|
35
|
+
/// ```
|
|
36
|
+
///
|
|
37
|
+
/// 2. **Type Checking**: Verifying that an envelope has the expected type
|
|
38
|
+
/// before processing
|
|
39
|
+
///
|
|
40
|
+
/// ```typescript
|
|
41
|
+
/// function processPerson(envelope: Envelope): void {
|
|
42
|
+
/// // Verify this is a person before processing
|
|
43
|
+
/// envelope.checkType("Person");
|
|
44
|
+
///
|
|
45
|
+
/// // Now we can safely extract person-specific information
|
|
46
|
+
/// const name = envelope.subject().extractString();
|
|
47
|
+
/// const age = envelope.objectForPredicate("age").extractNumber();
|
|
48
|
+
///
|
|
49
|
+
/// console.log(`${name} is ${age} years old`);
|
|
50
|
+
/// }
|
|
51
|
+
/// ```
|
|
52
|
+
|
|
53
|
+
/// The standard predicate for type assertions
|
|
54
|
+
export const IS_A = "isA";
|
|
55
|
+
|
|
56
|
+
declare module "../base/envelope" {
|
|
57
|
+
interface Envelope {
|
|
58
|
+
/// Adds a type assertion to the envelope using the `'isA'` predicate.
|
|
59
|
+
///
|
|
60
|
+
/// This method provides a convenient way to declare the type of an envelope
|
|
61
|
+
/// using the standard `'isA'` predicate. The type can be any value that can
|
|
62
|
+
/// be converted to an envelope, typically a string.
|
|
63
|
+
///
|
|
64
|
+
/// @param object - The type to assign to this envelope
|
|
65
|
+
/// @returns A new envelope with the type assertion added
|
|
66
|
+
///
|
|
67
|
+
/// @example
|
|
68
|
+
/// ```typescript
|
|
69
|
+
/// // Create a document and declare its type
|
|
70
|
+
/// const document = Envelope.new("Important Content").addType("Document");
|
|
71
|
+
///
|
|
72
|
+
/// // Verify the type was added
|
|
73
|
+
/// assert(document.hasType("Document"));
|
|
74
|
+
/// ```
|
|
75
|
+
addType(object: EnvelopeEncodableValue): Envelope;
|
|
76
|
+
|
|
77
|
+
/// Returns all type objects from the envelope's `'isA'` assertions.
|
|
78
|
+
///
|
|
79
|
+
/// This method retrieves all objects of assertions that use the `'isA'`
|
|
80
|
+
/// predicate. Each returned envelope represents a type that has been
|
|
81
|
+
/// assigned to this envelope.
|
|
82
|
+
///
|
|
83
|
+
/// @returns An array of envelopes, each representing a type assigned to
|
|
84
|
+
/// this envelope
|
|
85
|
+
///
|
|
86
|
+
/// @example
|
|
87
|
+
/// ```typescript
|
|
88
|
+
/// // Create an envelope with multiple types
|
|
89
|
+
/// const multiTyped = Envelope.new("Versatile Entity")
|
|
90
|
+
/// .addType("Person")
|
|
91
|
+
/// .addType("Employee")
|
|
92
|
+
/// .addType("Manager");
|
|
93
|
+
///
|
|
94
|
+
/// // Get all the type objects
|
|
95
|
+
/// const types = multiTyped.types();
|
|
96
|
+
///
|
|
97
|
+
/// // There should be 3 types
|
|
98
|
+
/// console.log(types.length); // 3
|
|
99
|
+
/// ```
|
|
100
|
+
types(): Envelope[];
|
|
101
|
+
|
|
102
|
+
/// Gets a single type object from the envelope's `'isA'` assertions.
|
|
103
|
+
///
|
|
104
|
+
/// This method is useful when an envelope is expected to have exactly one
|
|
105
|
+
/// type. It throws an error if the envelope has zero or multiple types.
|
|
106
|
+
///
|
|
107
|
+
/// @returns The single type object if exactly one exists
|
|
108
|
+
/// @throws {EnvelopeError} If multiple types exist or no types exist
|
|
109
|
+
///
|
|
110
|
+
/// @example
|
|
111
|
+
/// ```typescript
|
|
112
|
+
/// // Create an envelope with a single type
|
|
113
|
+
/// const person = Envelope.new("Alice").addType("Person");
|
|
114
|
+
///
|
|
115
|
+
/// // Get the type
|
|
116
|
+
/// const typeObj = person.getType();
|
|
117
|
+
/// const typeString = typeObj.extractString();
|
|
118
|
+
/// console.log(typeString); // "Person"
|
|
119
|
+
/// ```
|
|
120
|
+
getType(): Envelope;
|
|
121
|
+
|
|
122
|
+
/// Checks if the envelope has a specific type, using an envelope as the
|
|
123
|
+
/// type identifier.
|
|
124
|
+
///
|
|
125
|
+
/// This method compares the digest of each type object with the digest of
|
|
126
|
+
/// the provided value to determine if the envelope has the specified type.
|
|
127
|
+
///
|
|
128
|
+
/// @param t - The type to check for, which will be converted to an envelope
|
|
129
|
+
/// @returns `true` if the envelope has the specified type, `false`
|
|
130
|
+
/// otherwise
|
|
131
|
+
///
|
|
132
|
+
/// @example
|
|
133
|
+
/// ```typescript
|
|
134
|
+
/// // Create a typed envelope
|
|
135
|
+
/// const document = Envelope.new("Contract")
|
|
136
|
+
/// .addType("LegalDocument")
|
|
137
|
+
/// .addAssertion("status", "Draft");
|
|
138
|
+
///
|
|
139
|
+
/// // Check for various types
|
|
140
|
+
/// console.log(document.hasType("LegalDocument")); // true
|
|
141
|
+
/// console.log(document.hasType("Spreadsheet")); // false
|
|
142
|
+
/// ```
|
|
143
|
+
hasType(t: EnvelopeEncodableValue): boolean;
|
|
144
|
+
|
|
145
|
+
/// Verifies that the envelope has a specific type.
|
|
146
|
+
///
|
|
147
|
+
/// This method is similar to `hasType` but throws an error instead of
|
|
148
|
+
/// returning false, making it suitable for use in validation chains.
|
|
149
|
+
///
|
|
150
|
+
/// @param t - The type to check for, which will be converted to an envelope
|
|
151
|
+
/// @throws {EnvelopeError} If the envelope does not have the specified type
|
|
152
|
+
///
|
|
153
|
+
/// @example
|
|
154
|
+
/// ```typescript
|
|
155
|
+
/// // Function that processes a person
|
|
156
|
+
/// function processPerson(envelope: Envelope): string {
|
|
157
|
+
/// // Verify this is a person
|
|
158
|
+
/// envelope.checkType("Person");
|
|
159
|
+
///
|
|
160
|
+
/// // Extract the name
|
|
161
|
+
/// const name = envelope.subject().extractString();
|
|
162
|
+
/// return name;
|
|
163
|
+
/// }
|
|
164
|
+
///
|
|
165
|
+
/// // Create a person envelope
|
|
166
|
+
/// const person = Envelope.new("Alice").addType("Person");
|
|
167
|
+
///
|
|
168
|
+
/// // Process the person
|
|
169
|
+
/// const result = processPerson(person);
|
|
170
|
+
/// console.log(result); // "Alice"
|
|
171
|
+
///
|
|
172
|
+
/// // Create a non-person envelope
|
|
173
|
+
/// const document = Envelope.new("Contract").addType("Document");
|
|
174
|
+
///
|
|
175
|
+
/// // Processing will throw an error
|
|
176
|
+
/// try {
|
|
177
|
+
/// processPerson(document);
|
|
178
|
+
/// } catch (e) {
|
|
179
|
+
/// console.log("Not a person!");
|
|
180
|
+
/// }
|
|
181
|
+
/// ```
|
|
182
|
+
checkType(t: EnvelopeEncodableValue): void;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Implementation of addType()
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
188
|
+
if (Envelope?.prototype) {
|
|
189
|
+
Envelope.prototype.addType = function (this: Envelope, object: EnvelopeEncodableValue): Envelope {
|
|
190
|
+
return this.addAssertion(IS_A, object);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/// Implementation of types()
|
|
194
|
+
Envelope.prototype.types = function (this: Envelope): Envelope[] {
|
|
195
|
+
return this.objectsForPredicate(IS_A);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/// Implementation of getType()
|
|
199
|
+
Envelope.prototype.getType = function (this: Envelope): Envelope {
|
|
200
|
+
const t = this.types();
|
|
201
|
+
if (t.length === 0) {
|
|
202
|
+
throw EnvelopeError.invalidType();
|
|
203
|
+
}
|
|
204
|
+
if (t.length === 1) {
|
|
205
|
+
return t[0];
|
|
206
|
+
}
|
|
207
|
+
throw EnvelopeError.ambiguousType();
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/// Implementation of hasType()
|
|
211
|
+
Envelope.prototype.hasType = function (this: Envelope, t: EnvelopeEncodableValue): boolean {
|
|
212
|
+
const e = Envelope.new(t);
|
|
213
|
+
return this.types().some((x) => x.digest().equals(e.digest()));
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/// Implementation of checkType()
|
|
217
|
+
Envelope.prototype.checkType = function (this: Envelope, t: EnvelopeEncodableValue): void {
|
|
218
|
+
if (!this.hasType(t)) {
|
|
219
|
+
throw EnvelopeError.invalidType();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Envelope } from "../base/envelope";
|
|
2
|
+
|
|
3
|
+
// Type for CBOR values that can appear in diagnostic notation
|
|
4
|
+
type CborValue =
|
|
5
|
+
| string
|
|
6
|
+
| number
|
|
7
|
+
| boolean
|
|
8
|
+
| null
|
|
9
|
+
| Uint8Array
|
|
10
|
+
| CborValue[]
|
|
11
|
+
| Map<CborValue, CborValue>
|
|
12
|
+
| { tag: number; value: CborValue }
|
|
13
|
+
| { type: number; value: unknown };
|
|
14
|
+
|
|
15
|
+
/// Diagnostic notation formatting for Gordian Envelopes.
|
|
16
|
+
///
|
|
17
|
+
/// This module provides methods for converting envelopes to CBOR diagnostic
|
|
18
|
+
/// notation, a human-readable text format defined in RFC 8949 §8.
|
|
19
|
+
///
|
|
20
|
+
/// See [RFC-8949 §8](https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation)
|
|
21
|
+
/// for information on CBOR diagnostic notation.
|
|
22
|
+
|
|
23
|
+
// Note: Method declarations are in the base Envelope class.
|
|
24
|
+
// This module provides the prototype implementations.
|
|
25
|
+
|
|
26
|
+
/// Converts a CBOR value to diagnostic notation
|
|
27
|
+
function cborToDiagnostic(cbor: CborValue, indent = 0): string {
|
|
28
|
+
// Handle tagged values (CBOR tags)
|
|
29
|
+
if (typeof cbor === "object" && cbor !== null && "tag" in cbor && "value" in cbor) {
|
|
30
|
+
const tagged = cbor as { tag: number; value: CborValue };
|
|
31
|
+
return `${tagged.tag}(${cborToDiagnostic(tagged.value, indent)})`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle arrays
|
|
35
|
+
if (Array.isArray(cbor)) {
|
|
36
|
+
if (cbor.length === 0) {
|
|
37
|
+
return "[]";
|
|
38
|
+
}
|
|
39
|
+
const items = cbor.map((item) => cborToDiagnostic(item, indent + 2));
|
|
40
|
+
return `[${items.join(", ")}]`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle Maps
|
|
44
|
+
if (cbor instanceof Map) {
|
|
45
|
+
if (cbor.size === 0) {
|
|
46
|
+
return "{}";
|
|
47
|
+
}
|
|
48
|
+
const entries: string[] = [];
|
|
49
|
+
for (const [key, value] of cbor) {
|
|
50
|
+
const keyStr = cborToDiagnostic(key, indent + 2);
|
|
51
|
+
const valueStr = cborToDiagnostic(value, indent + 2);
|
|
52
|
+
entries.push(`${keyStr}: ${valueStr}`);
|
|
53
|
+
}
|
|
54
|
+
return `{${entries.join(", ")}}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle Uint8Array (byte strings)
|
|
58
|
+
if (cbor instanceof Uint8Array) {
|
|
59
|
+
const hex = Array.from(cbor)
|
|
60
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
61
|
+
.join("");
|
|
62
|
+
return `h'${hex}'`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle strings
|
|
66
|
+
if (typeof cbor === "string") {
|
|
67
|
+
return JSON.stringify(cbor);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle CBOR objects with type information
|
|
71
|
+
if (typeof cbor === "object" && cbor !== null && "type" in cbor) {
|
|
72
|
+
const typed = cbor as { type: number; value: unknown };
|
|
73
|
+
switch (typed.type) {
|
|
74
|
+
case 0: // Unsigned
|
|
75
|
+
return String(typed.value);
|
|
76
|
+
case 1: // Negative
|
|
77
|
+
return String(-1 - Number(typed.value));
|
|
78
|
+
case 7: {
|
|
79
|
+
// Simple
|
|
80
|
+
const simpleValue = typed.value;
|
|
81
|
+
if (simpleValue !== null && typeof simpleValue === "object" && "type" in simpleValue) {
|
|
82
|
+
const floatValue = simpleValue as { type: string; value: unknown };
|
|
83
|
+
if (floatValue.type === "Float") {
|
|
84
|
+
return String(floatValue.value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (simpleValue === 20) return "false";
|
|
88
|
+
if (simpleValue === 21) return "true";
|
|
89
|
+
if (simpleValue === 22) return "null";
|
|
90
|
+
if (simpleValue === 23) return "undefined";
|
|
91
|
+
return `simple(${String(simpleValue)})`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Fallback for primitives
|
|
97
|
+
if (typeof cbor === "boolean") return String(cbor);
|
|
98
|
+
if (typeof cbor === "number") return String(cbor);
|
|
99
|
+
if (typeof cbor === "bigint") return String(cbor);
|
|
100
|
+
if (cbor === null) return "null";
|
|
101
|
+
if (cbor === undefined) return "undefined";
|
|
102
|
+
|
|
103
|
+
// Unknown type - try JSON stringify
|
|
104
|
+
try {
|
|
105
|
+
return JSON.stringify(cbor);
|
|
106
|
+
} catch {
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
108
|
+
return String(cbor);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// Implementation of diagnostic()
|
|
113
|
+
Envelope.prototype.diagnostic = function (this: Envelope): string {
|
|
114
|
+
const cbor = this.taggedCbor();
|
|
115
|
+
return cborToDiagnostic(cbor);
|
|
116
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Envelope } from "../base/envelope";
|
|
2
|
+
import { cborData } from "@bcts/dcbor";
|
|
3
|
+
|
|
4
|
+
/// Hex formatting for Gordian Envelopes.
|
|
5
|
+
///
|
|
6
|
+
/// This module provides methods for converting envelopes to hexadecimal
|
|
7
|
+
/// representations of their CBOR encoding, useful for debugging and
|
|
8
|
+
/// low-level inspection.
|
|
9
|
+
|
|
10
|
+
// Note: Method declarations are in the base Envelope class.
|
|
11
|
+
// This module provides the prototype implementations.
|
|
12
|
+
|
|
13
|
+
/// Implementation of hex()
|
|
14
|
+
Envelope.prototype.hex = function (this: Envelope): string {
|
|
15
|
+
const bytes = this.cborBytes();
|
|
16
|
+
return Array.from(bytes)
|
|
17
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
18
|
+
.join("");
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/// Implementation of cborBytes()
|
|
22
|
+
Envelope.prototype.cborBytes = function (this: Envelope): Uint8Array {
|
|
23
|
+
const cbor = this.taggedCbor();
|
|
24
|
+
return cborData(cbor);
|
|
25
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// Format module exports for Gordian Envelope.
|
|
2
|
+
///
|
|
3
|
+
/// This module provides various formatting options for displaying and
|
|
4
|
+
/// serializing envelopes, including hex, diagnostic, notation, tree,
|
|
5
|
+
/// and mermaid diagram formats.
|
|
6
|
+
|
|
7
|
+
// Export types
|
|
8
|
+
export type { TreeFormatOptions } from "./tree";
|
|
9
|
+
|
|
10
|
+
// Import side-effect modules to register prototype extensions
|
|
11
|
+
import "./hex";
|
|
12
|
+
import "./diagnostic";
|
|
13
|
+
import "./tree";
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Envelope } from "../base/envelope";
|
|
2
|
+
import { type EdgeType, edgeLabel } from "../base/walk";
|
|
3
|
+
|
|
4
|
+
/// Tree formatting for Gordian Envelopes.
|
|
5
|
+
///
|
|
6
|
+
/// This module provides functionality for creating textual tree
|
|
7
|
+
/// representations of envelopes, which is useful for debugging and visualizing
|
|
8
|
+
/// the hierarchical structure of complex envelopes.
|
|
9
|
+
///
|
|
10
|
+
/// The tree format displays each component of an envelope (subject and
|
|
11
|
+
/// assertions) as nodes in a tree, making it easy to understand the
|
|
12
|
+
/// hierarchical structure of nested envelopes. Each node includes:
|
|
13
|
+
///
|
|
14
|
+
/// - The first 8 characters of the element's digest (for easy reference)
|
|
15
|
+
/// - The type of the element (NODE, ASSERTION, ELIDED, etc.)
|
|
16
|
+
/// - The content of the element (for leaf nodes)
|
|
17
|
+
|
|
18
|
+
/// Options for tree formatting
|
|
19
|
+
export interface TreeFormatOptions {
|
|
20
|
+
/// If true, hides NODE identifiers and only shows semantic content
|
|
21
|
+
hideNodes?: boolean;
|
|
22
|
+
/// Set of digest strings to highlight in the tree
|
|
23
|
+
highlightDigests?: Set<string>;
|
|
24
|
+
/// Format for displaying digests
|
|
25
|
+
digestDisplay?: "short" | "full";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Represents an element in the tree representation
|
|
29
|
+
interface TreeElement {
|
|
30
|
+
/// Indentation level
|
|
31
|
+
level: number;
|
|
32
|
+
/// The envelope element
|
|
33
|
+
envelope: Envelope;
|
|
34
|
+
/// Type of incoming edge
|
|
35
|
+
incomingEdge: EdgeType;
|
|
36
|
+
/// Whether to show the digest ID
|
|
37
|
+
showId: boolean;
|
|
38
|
+
/// Whether this element is highlighted
|
|
39
|
+
isHighlighted: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Note: Method declarations are in the base Envelope class.
|
|
43
|
+
// This module provides the prototype implementations.
|
|
44
|
+
|
|
45
|
+
/// Implementation of shortId()
|
|
46
|
+
Envelope.prototype.shortId = function (this: Envelope, format: "short" | "full" = "short"): string {
|
|
47
|
+
const digest = this.digest();
|
|
48
|
+
if (format === "full") {
|
|
49
|
+
return digest.hex();
|
|
50
|
+
}
|
|
51
|
+
return digest.short();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/// Implementation of summary()
|
|
55
|
+
Envelope.prototype.summary = function (this: Envelope, maxLength = 40): string {
|
|
56
|
+
const c = this.case();
|
|
57
|
+
|
|
58
|
+
switch (c.type) {
|
|
59
|
+
case "node":
|
|
60
|
+
return "NODE";
|
|
61
|
+
case "leaf": {
|
|
62
|
+
// Try to extract a readable value
|
|
63
|
+
try {
|
|
64
|
+
const text = this.asText();
|
|
65
|
+
if (text !== undefined) {
|
|
66
|
+
const truncated = text.length > maxLength ? `${text.substring(0, maxLength)}...` : text;
|
|
67
|
+
return JSON.stringify(truncated);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Fall through
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const num = this.extractNumber();
|
|
75
|
+
return String(num);
|
|
76
|
+
} catch {
|
|
77
|
+
// Fall through
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const bool = this.extractBoolean();
|
|
82
|
+
return String(bool);
|
|
83
|
+
} catch {
|
|
84
|
+
// Fall through
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (this.isNull()) {
|
|
88
|
+
return "null";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fallback: show byte string
|
|
92
|
+
const bytes = this.asByteString();
|
|
93
|
+
if (bytes !== undefined && bytes.length <= 16) {
|
|
94
|
+
const hex = Array.from(bytes)
|
|
95
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
96
|
+
.join("");
|
|
97
|
+
return `h'${hex}'`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return "LEAF";
|
|
101
|
+
}
|
|
102
|
+
case "wrapped":
|
|
103
|
+
return "WRAPPED";
|
|
104
|
+
case "assertion":
|
|
105
|
+
return "ASSERTION";
|
|
106
|
+
case "elided":
|
|
107
|
+
return "ELIDED";
|
|
108
|
+
case "encrypted":
|
|
109
|
+
return "ENCRYPTED";
|
|
110
|
+
case "compressed":
|
|
111
|
+
return "COMPRESSED";
|
|
112
|
+
case "knownValue":
|
|
113
|
+
return "KNOWN_VALUE";
|
|
114
|
+
default:
|
|
115
|
+
return "UNKNOWN";
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/// Implementation of treeFormat()
|
|
120
|
+
Envelope.prototype.treeFormat = function (this: Envelope, options: TreeFormatOptions = {}): string {
|
|
121
|
+
const hideNodes = options.hideNodes ?? false;
|
|
122
|
+
const highlightDigests = options.highlightDigests ?? new Set<string>();
|
|
123
|
+
const digestDisplay = options.digestDisplay ?? "short";
|
|
124
|
+
|
|
125
|
+
const elements: TreeElement[] = [];
|
|
126
|
+
|
|
127
|
+
// Walk the envelope and collect elements
|
|
128
|
+
this.walk(hideNodes, undefined, (envelope, level, incomingEdge, _state) => {
|
|
129
|
+
const digestStr = envelope.digest().short();
|
|
130
|
+
const isHighlighted = highlightDigests.has(digestStr);
|
|
131
|
+
|
|
132
|
+
elements.push({
|
|
133
|
+
level,
|
|
134
|
+
envelope,
|
|
135
|
+
incomingEdge,
|
|
136
|
+
showId: !hideNodes,
|
|
137
|
+
isHighlighted,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return [undefined, false];
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Format each element as a line
|
|
144
|
+
const lines = elements.map((elem) => {
|
|
145
|
+
const parts: string[] = [];
|
|
146
|
+
|
|
147
|
+
if (elem.isHighlighted) {
|
|
148
|
+
parts.push("*");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (elem.showId) {
|
|
152
|
+
parts.push(elem.envelope.shortId(digestDisplay));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const label = edgeLabel(elem.incomingEdge);
|
|
156
|
+
if (label !== undefined && label !== "") {
|
|
157
|
+
parts.push(label);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
parts.push(elem.envelope.summary(40));
|
|
161
|
+
|
|
162
|
+
const line = parts.join(" ");
|
|
163
|
+
const indent = " ".repeat(elem.level * 4);
|
|
164
|
+
return indent + line;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// Gordian Envelope TypeScript Library
|
|
2
|
+
///
|
|
3
|
+
/// A TypeScript implementation of Blockchain Commons' Gordian Envelope
|
|
4
|
+
/// specification for structured, privacy-focused data containers.
|
|
5
|
+
///
|
|
6
|
+
/// This is a 1:1 port of the Rust bc-envelope library, maintaining the same
|
|
7
|
+
/// API structure and functionality.
|
|
8
|
+
///
|
|
9
|
+
/// @module bc-envelope
|
|
10
|
+
|
|
11
|
+
// Re-export everything from the base module
|
|
12
|
+
export * from "./base";
|
|
13
|
+
|
|
14
|
+
// Re-export everything from the extension module
|
|
15
|
+
export * from "./extension";
|
|
16
|
+
|
|
17
|
+
// Import registration functions and call them to ensure proper initialization order
|
|
18
|
+
import { registerEncryptExtension } from "./extension/encrypt";
|
|
19
|
+
import { registerCompressExtension } from "./extension/compress";
|
|
20
|
+
registerEncryptExtension();
|
|
21
|
+
registerCompressExtension();
|
|
22
|
+
|
|
23
|
+
// Re-export everything from the format module
|
|
24
|
+
// Import for side effects (registers prototype extensions like treeFormat)
|
|
25
|
+
import "./format";
|
|
26
|
+
export type * from "./format";
|
|
27
|
+
|
|
28
|
+
// Re-export everything from the utils module
|
|
29
|
+
export * from "./utils";
|
|
30
|
+
|
|
31
|
+
// Version information
|
|
32
|
+
export const VERSION = "0.37.0";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String utility functions used throughout the envelope library.
|
|
3
|
+
*
|
|
4
|
+
* Provides helper methods for string formatting and manipulation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Flanks a string with specified left and right delimiters.
|
|
9
|
+
*
|
|
10
|
+
* @param str - The string to flank
|
|
11
|
+
* @param left - The left delimiter
|
|
12
|
+
* @param right - The right delimiter
|
|
13
|
+
* @returns The flanked string
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* flanked('hello', '"', '"') // Returns: "hello"
|
|
18
|
+
* flanked('name', "'", "'") // Returns: 'name'
|
|
19
|
+
* flanked('item', '[', ']') // Returns: [item]
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function flanked(str: string, left: string, right: string): string {
|
|
23
|
+
return `${left}${str}${right}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extension methods for String objects to support fluent API style.
|
|
28
|
+
*/
|
|
29
|
+
declare global {
|
|
30
|
+
interface String {
|
|
31
|
+
/**
|
|
32
|
+
* Flanks this string with specified left and right delimiters.
|
|
33
|
+
*
|
|
34
|
+
* @param left - The left delimiter
|
|
35
|
+
* @param right - The right delimiter
|
|
36
|
+
* @returns The flanked string
|
|
37
|
+
*/
|
|
38
|
+
flankedBy(left: string, right: string): string;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Extend String prototype with flankedBy method
|
|
43
|
+
String.prototype.flankedBy = function (this: string, left: string, right: string): string {
|
|
44
|
+
return flanked(this, left, right);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Export the extension for side-effects
|
|
48
|
+
export {};
|