@bcts/xid 1.0.0-alpha.10
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 +15 -0
- package/dist/index.cjs +1943 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1038 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1038 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +1942 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +1923 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +81 -0
- package/src/delegate.ts +132 -0
- package/src/error.ts +305 -0
- package/src/index.ts +65 -0
- package/src/key.ts +431 -0
- package/src/name.ts +42 -0
- package/src/permissions.ts +229 -0
- package/src/privilege.ts +160 -0
- package/src/provenance.ts +374 -0
- package/src/service.ts +343 -0
- package/src/shared.ts +76 -0
- package/src/xid-document.ts +862 -0
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
|
+
}
|