@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.
Files changed (44) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +23 -0
  3. package/dist/index.cjs +2646 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +978 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +978 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.iife.js +2644 -0
  10. package/dist/index.iife.js.map +1 -0
  11. package/dist/index.mjs +2552 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +85 -0
  14. package/src/base/assertion.ts +179 -0
  15. package/src/base/assertions.ts +304 -0
  16. package/src/base/cbor.ts +122 -0
  17. package/src/base/digest.ts +204 -0
  18. package/src/base/elide.ts +526 -0
  19. package/src/base/envelope-decodable.ts +229 -0
  20. package/src/base/envelope-encodable.ts +71 -0
  21. package/src/base/envelope.ts +790 -0
  22. package/src/base/error.ts +421 -0
  23. package/src/base/index.ts +56 -0
  24. package/src/base/leaf.ts +226 -0
  25. package/src/base/queries.ts +374 -0
  26. package/src/base/walk.ts +241 -0
  27. package/src/base/wrap.ts +72 -0
  28. package/src/extension/attachment.ts +369 -0
  29. package/src/extension/compress.ts +293 -0
  30. package/src/extension/encrypt.ts +379 -0
  31. package/src/extension/expression.ts +404 -0
  32. package/src/extension/index.ts +72 -0
  33. package/src/extension/proof.ts +276 -0
  34. package/src/extension/recipient.ts +557 -0
  35. package/src/extension/salt.ts +223 -0
  36. package/src/extension/signature.ts +463 -0
  37. package/src/extension/types.ts +222 -0
  38. package/src/format/diagnostic.ts +116 -0
  39. package/src/format/hex.ts +25 -0
  40. package/src/format/index.ts +13 -0
  41. package/src/format/tree.ts +168 -0
  42. package/src/index.ts +32 -0
  43. package/src/utils/index.ts +8 -0
  44. 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,8 @@
1
+ /**
2
+ * Utility functions for the envelope library.
3
+ */
4
+
5
+ export { flanked } from "./string";
6
+
7
+ // Side-effect import to extend String prototype
8
+ import "./string";
@@ -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 {};