@bcts/xid 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/src/service.ts ADDED
@@ -0,0 +1,343 @@
1
+ /**
2
+ * XID Service
3
+ *
4
+ * Represents a service endpoint in an XID document, containing URI, key references,
5
+ * delegate references, permissions, capability, and name.
6
+ *
7
+ * Ported from bc-xid-rust/src/service.rs
8
+ */
9
+
10
+ import { Envelope, type EnvelopeEncodable, type EnvelopeEncodableValue } from "@bcts/envelope";
11
+ import { KEY, DELEGATE, NAME, CAPABILITY, ALLOW, type KnownValue } from "@bcts/known-values";
12
+
13
+ // Helper to convert KnownValue to EnvelopeEncodableValue
14
+ const kv = (v: KnownValue): EnvelopeEncodableValue => v as unknown as EnvelopeEncodableValue;
15
+ import type { Reference } from "@bcts/components";
16
+ import { Permissions, type HasPermissions } from "./permissions";
17
+ import { privilegeFromEnvelope } from "./privilege";
18
+ import { XIDError } from "./error";
19
+
20
+ // Raw values for predicate matching
21
+ const KEY_RAW = KEY.value();
22
+ const DELEGATE_RAW = DELEGATE.value();
23
+ const NAME_RAW = NAME.value();
24
+ const CAPABILITY_RAW = CAPABILITY.value();
25
+ const ALLOW_RAW = ALLOW.value();
26
+
27
+ /**
28
+ * Represents a service endpoint in an XID document.
29
+ */
30
+ export class Service implements HasPermissions, EnvelopeEncodable {
31
+ private readonly _uri: string;
32
+ private _keyReferences: Set<string>; // Store as hex strings for easier comparison
33
+ private _delegateReferences: Set<string>;
34
+ private _permissions: Permissions;
35
+ private _capability: string;
36
+ private _name: string;
37
+
38
+ constructor(uri: string) {
39
+ this._uri = uri;
40
+ this._keyReferences = new Set();
41
+ this._delegateReferences = new Set();
42
+ this._permissions = Permissions.new();
43
+ this._capability = "";
44
+ this._name = "";
45
+ }
46
+
47
+ /**
48
+ * Create a new Service with the given URI.
49
+ */
50
+ static new(uri: string): Service {
51
+ return new Service(uri);
52
+ }
53
+
54
+ /**
55
+ * Get the service URI.
56
+ */
57
+ uri(): string {
58
+ return this._uri;
59
+ }
60
+
61
+ /**
62
+ * Get the capability string.
63
+ */
64
+ capability(): string {
65
+ return this._capability;
66
+ }
67
+
68
+ /**
69
+ * Set the capability string.
70
+ */
71
+ setCapability(capability: string): void {
72
+ this._capability = capability;
73
+ }
74
+
75
+ /**
76
+ * Add a capability, throwing if one already exists or is empty.
77
+ */
78
+ addCapability(capability: string): void {
79
+ if (this._capability !== "") {
80
+ throw XIDError.duplicate("capability");
81
+ }
82
+ if (capability === "") {
83
+ throw XIDError.emptyValue("capability");
84
+ }
85
+ this.setCapability(capability);
86
+ }
87
+
88
+ /**
89
+ * Get the key references set.
90
+ */
91
+ keyReferences(): Set<string> {
92
+ return this._keyReferences;
93
+ }
94
+
95
+ /**
96
+ * Get the key references set for mutation.
97
+ */
98
+ keyReferencesMut(): Set<string> {
99
+ return this._keyReferences;
100
+ }
101
+
102
+ /**
103
+ * Add a key reference by hex string.
104
+ */
105
+ addKeyReferenceHex(keyReferenceHex: string): void {
106
+ if (this._keyReferences.has(keyReferenceHex)) {
107
+ throw XIDError.duplicate("key reference");
108
+ }
109
+ this._keyReferences.add(keyReferenceHex);
110
+ }
111
+
112
+ /**
113
+ * Add a key reference.
114
+ */
115
+ addKeyReference(keyReference: Reference): void {
116
+ this.addKeyReferenceHex(keyReference.toHex());
117
+ }
118
+
119
+ /**
120
+ * Get the delegate references set.
121
+ */
122
+ delegateReferences(): Set<string> {
123
+ return this._delegateReferences;
124
+ }
125
+
126
+ /**
127
+ * Get the delegate references set for mutation.
128
+ */
129
+ delegateReferencesMut(): Set<string> {
130
+ return this._delegateReferences;
131
+ }
132
+
133
+ /**
134
+ * Add a delegate reference by hex string.
135
+ */
136
+ addDelegateReferenceHex(delegateReferenceHex: string): void {
137
+ if (this._delegateReferences.has(delegateReferenceHex)) {
138
+ throw XIDError.duplicate("delegate reference");
139
+ }
140
+ this._delegateReferences.add(delegateReferenceHex);
141
+ }
142
+
143
+ /**
144
+ * Add a delegate reference.
145
+ */
146
+ addDelegateReference(delegateReference: Reference): void {
147
+ this.addDelegateReferenceHex(delegateReference.toHex());
148
+ }
149
+
150
+ /**
151
+ * Get the name.
152
+ */
153
+ name(): string {
154
+ return this._name;
155
+ }
156
+
157
+ /**
158
+ * Set the name, throwing if one already exists or is empty.
159
+ */
160
+ setName(name: string): void {
161
+ if (this._name !== "") {
162
+ throw XIDError.duplicate("name");
163
+ }
164
+ if (name === "") {
165
+ throw XIDError.emptyValue("name");
166
+ }
167
+ this._name = name;
168
+ }
169
+
170
+ // HasPermissions implementation
171
+ permissions(): Permissions {
172
+ return this._permissions;
173
+ }
174
+
175
+ permissionsMut(): Permissions {
176
+ return this._permissions;
177
+ }
178
+
179
+ /**
180
+ * Convert to envelope.
181
+ */
182
+ intoEnvelope(): Envelope {
183
+ let envelope = Envelope.new(this._uri);
184
+
185
+ // Add key references
186
+ for (const keyRef of this._keyReferences) {
187
+ const refBytes = hexToBytes(keyRef);
188
+ envelope = envelope.addAssertion(kv(KEY), refBytes);
189
+ }
190
+
191
+ // Add delegate references
192
+ for (const delegateRef of this._delegateReferences) {
193
+ const refBytes = hexToBytes(delegateRef);
194
+ envelope = envelope.addAssertion(kv(DELEGATE), refBytes);
195
+ }
196
+
197
+ // Add capability if not empty
198
+ if (this._capability !== "") {
199
+ envelope = envelope.addAssertion(kv(CAPABILITY), this._capability);
200
+ }
201
+
202
+ // Add name if not empty
203
+ if (this._name !== "") {
204
+ envelope = envelope.addAssertion(kv(NAME), this._name);
205
+ }
206
+
207
+ // Add permissions
208
+ envelope = this._permissions.addToEnvelope(envelope);
209
+
210
+ return envelope;
211
+ }
212
+
213
+ /**
214
+ * Try to extract a Service from an envelope.
215
+ */
216
+ static tryFromEnvelope(envelope: Envelope): Service {
217
+ // Extract URI from subject
218
+ // The envelope may be a node (with assertions) or a leaf
219
+ const envExt = envelope as unknown as {
220
+ subject(): Envelope;
221
+ asText(): string | undefined;
222
+ case(): { type: string };
223
+ };
224
+ const envCase = envExt.case();
225
+ const subject = envCase.type === "node" ? envExt.subject() : envelope;
226
+ const uri = (subject as unknown as { asText(): string | undefined }).asText();
227
+ if (uri === undefined) {
228
+ throw XIDError.component(new Error("Could not extract URI from envelope"));
229
+ }
230
+
231
+ const service = new Service(uri);
232
+
233
+ // Process assertions
234
+ const assertions = (envelope as unknown as { assertions(): Envelope[] }).assertions();
235
+ for (const assertion of assertions) {
236
+ const assertionCase = assertion.case();
237
+ if (assertionCase.type !== "assertion") {
238
+ continue;
239
+ }
240
+
241
+ const predicateEnv = assertionCase.assertion.predicate();
242
+ const predicateCase = predicateEnv.case();
243
+ if (predicateCase.type !== "knownValue") {
244
+ continue;
245
+ }
246
+
247
+ const predicate = predicateCase.value.value();
248
+ const object = assertionCase.assertion.object();
249
+
250
+ // Check for nested assertions
251
+ const objectAssertions = (object as unknown as { assertions(): Envelope[] }).assertions();
252
+ if (objectAssertions.length > 0) {
253
+ throw XIDError.unexpectedNestedAssertions();
254
+ }
255
+
256
+ switch (predicate) {
257
+ case KEY_RAW: {
258
+ const keyData = (
259
+ object as unknown as { asByteString(): Uint8Array | undefined }
260
+ ).asByteString();
261
+ if (keyData !== undefined) {
262
+ service.addKeyReferenceHex(bytesToHex(keyData));
263
+ }
264
+ break;
265
+ }
266
+ case DELEGATE_RAW: {
267
+ const delegateData = (
268
+ object as unknown as { asByteString(): Uint8Array | undefined }
269
+ ).asByteString();
270
+ if (delegateData !== undefined) {
271
+ service.addDelegateReferenceHex(bytesToHex(delegateData));
272
+ }
273
+ break;
274
+ }
275
+ case CAPABILITY_RAW: {
276
+ const capability = (object as unknown as { asText(): string | undefined }).asText();
277
+ if (capability !== undefined) {
278
+ service.addCapability(capability);
279
+ }
280
+ break;
281
+ }
282
+ case NAME_RAW: {
283
+ const name = (object as unknown as { asText(): string | undefined }).asText();
284
+ if (name !== undefined) {
285
+ service.setName(name);
286
+ }
287
+ break;
288
+ }
289
+ case ALLOW_RAW: {
290
+ const privilege = privilegeFromEnvelope(object);
291
+ service._permissions.addAllow(privilege);
292
+ break;
293
+ }
294
+ default:
295
+ throw XIDError.unexpectedPredicate(String(predicate));
296
+ }
297
+ }
298
+
299
+ return service;
300
+ }
301
+
302
+ /**
303
+ * Check equality with another Service (based on URI).
304
+ */
305
+ equals(other: Service): boolean {
306
+ return this._uri === other._uri;
307
+ }
308
+
309
+ /**
310
+ * Get a hash key for use in Sets/Maps.
311
+ */
312
+ hashKey(): string {
313
+ return this._uri;
314
+ }
315
+
316
+ /**
317
+ * Clone this Service.
318
+ */
319
+ clone(): Service {
320
+ const clone = new Service(this._uri);
321
+ clone._keyReferences = new Set(this._keyReferences);
322
+ clone._delegateReferences = new Set(this._delegateReferences);
323
+ clone._permissions = this._permissions.clone();
324
+ clone._capability = this._capability;
325
+ clone._name = this._name;
326
+ return clone;
327
+ }
328
+ }
329
+
330
+ // Helper functions
331
+ function hexToBytes(hex: string): Uint8Array {
332
+ const bytes = new Uint8Array(hex.length / 2);
333
+ for (let i = 0; i < hex.length; i += 2) {
334
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
335
+ }
336
+ return bytes;
337
+ }
338
+
339
+ function bytesToHex(bytes: Uint8Array): string {
340
+ return Array.from(bytes)
341
+ .map((b) => b.toString(16).padStart(2, "0"))
342
+ .join("");
343
+ }
package/src/shared.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared Reference Wrapper
3
+ *
4
+ * Provides a wrapper for shared references to objects.
5
+ * In TypeScript, we don't have Arc/RwLock like Rust, but we can provide
6
+ * a simple wrapper that allows shared access to a value.
7
+ *
8
+ * Ported from bc-xid-rust/src/shared.rs
9
+ */
10
+
11
+ /**
12
+ * A wrapper for shared references to objects.
13
+ *
14
+ * Unlike Rust's Arc<RwLock<T>>, JavaScript uses reference semantics for objects,
15
+ * so this is primarily a type-safe wrapper that makes the sharing explicit.
16
+ */
17
+ export class Shared<T> {
18
+ private readonly value: T;
19
+
20
+ constructor(value: T) {
21
+ this.value = value;
22
+ }
23
+
24
+ /**
25
+ * Create a new Shared instance.
26
+ */
27
+ static new<T>(value: T): Shared<T> {
28
+ return new Shared(value);
29
+ }
30
+
31
+ /**
32
+ * Get a read-only reference to the value.
33
+ */
34
+ read(): T {
35
+ return this.value;
36
+ }
37
+
38
+ /**
39
+ * Get a mutable reference to the value.
40
+ */
41
+ write(): T {
42
+ return this.value;
43
+ }
44
+
45
+ /**
46
+ * Check equality with another Shared instance.
47
+ */
48
+ equals(other: Shared<T>): boolean {
49
+ // Use JSON.stringify for deep equality check
50
+ // This is a simple approach; for complex objects, a proper deep equals would be better
51
+ if (this.value === other.value) return true;
52
+ try {
53
+ return JSON.stringify(this.value) === JSON.stringify(other.value);
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Clone this Shared instance.
61
+ * Note: This creates a shallow copy in JS; for deep copy, implement on T.
62
+ */
63
+ clone(): Shared<T> {
64
+ // If the value has a clone method, use it
65
+ if (
66
+ typeof this.value === "object" &&
67
+ this.value !== null &&
68
+ "clone" in this.value &&
69
+ typeof (this.value as { clone(): T }).clone === "function"
70
+ ) {
71
+ return new Shared((this.value as { clone(): T }).clone());
72
+ }
73
+ // Otherwise, return the same reference (shallow copy)
74
+ return new Shared(this.value);
75
+ }
76
+ }