@enbox/api 0.6.20 → 0.6.22

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 (63) hide show
  1. package/dist/browser.mjs +11 -11
  2. package/dist/browser.mjs.map +3 -3
  3. package/dist/esm/dwn-api.js.map +1 -1
  4. package/dist/esm/enbox.js.map +1 -1
  5. package/dist/esm/live-query.js +4 -4
  6. package/dist/esm/live-query.js.map +1 -1
  7. package/dist/esm/record.js +2 -2
  8. package/dist/esm/record.js.map +1 -1
  9. package/dist/esm/repository.js +2 -2
  10. package/dist/esm/repository.js.map +1 -1
  11. package/dist/esm/typed-enbox.js +20 -13
  12. package/dist/esm/typed-enbox.js.map +1 -1
  13. package/dist/esm/utils.js.map +1 -1
  14. package/dist/types/did-api.d.ts +2 -2
  15. package/dist/types/did-api.d.ts.map +1 -1
  16. package/dist/types/dwn-api.d.ts +3 -3
  17. package/dist/types/dwn-api.d.ts.map +1 -1
  18. package/dist/types/dwn-reader-api.d.ts +1 -1
  19. package/dist/types/dwn-reader-api.d.ts.map +1 -1
  20. package/dist/types/enbox.d.ts +2 -2
  21. package/dist/types/enbox.d.ts.map +1 -1
  22. package/dist/types/grant-revocation.d.ts +2 -2
  23. package/dist/types/grant-revocation.d.ts.map +1 -1
  24. package/dist/types/live-query.d.ts +2 -2
  25. package/dist/types/live-query.d.ts.map +1 -1
  26. package/dist/types/permission-grant.d.ts +3 -3
  27. package/dist/types/permission-grant.d.ts.map +1 -1
  28. package/dist/types/permission-request.d.ts +3 -3
  29. package/dist/types/permission-request.d.ts.map +1 -1
  30. package/dist/types/protocol.d.ts +3 -3
  31. package/dist/types/protocol.d.ts.map +1 -1
  32. package/dist/types/read-only-record.d.ts +12 -12
  33. package/dist/types/read-only-record.d.ts.map +1 -1
  34. package/dist/types/record.d.ts +9 -9
  35. package/dist/types/record.d.ts.map +1 -1
  36. package/dist/types/typed-enbox.d.ts +5 -3
  37. package/dist/types/typed-enbox.d.ts.map +1 -1
  38. package/dist/types/typed-live-query.d.ts +1 -1
  39. package/dist/types/typed-live-query.d.ts.map +1 -1
  40. package/dist/types/typed-record.d.ts +1 -1
  41. package/dist/types/typed-record.d.ts.map +1 -1
  42. package/dist/types/utils.d.ts +2 -2
  43. package/dist/types/utils.d.ts.map +1 -1
  44. package/dist/types/vc-api.d.ts +2 -2
  45. package/dist/types/vc-api.d.ts.map +1 -1
  46. package/package.json +5 -5
  47. package/src/did-api.ts +2 -2
  48. package/src/dwn-api.ts +4 -4
  49. package/src/dwn-reader-api.ts +1 -1
  50. package/src/enbox.ts +2 -2
  51. package/src/grant-revocation.ts +2 -2
  52. package/src/live-query.ts +5 -5
  53. package/src/permission-grant.ts +3 -3
  54. package/src/permission-request.ts +3 -3
  55. package/src/protocol.ts +3 -3
  56. package/src/read-only-record.ts +12 -12
  57. package/src/record.ts +11 -11
  58. package/src/repository.ts +2 -2
  59. package/src/typed-enbox.ts +25 -22
  60. package/src/typed-live-query.ts +1 -1
  61. package/src/typed-record.ts +1 -1
  62. package/src/utils.ts +2 -2
  63. package/src/vc-api.ts +2 -2
@@ -59,13 +59,13 @@ export interface PermissionRequestModel {
59
59
  */
60
60
  export class PermissionRequest implements PermissionRequestModel {
61
61
  /** The PermissionsAPI used to interact with the underlying permission request */
62
- private _permissions: AgentPermissionsApi;
62
+ private readonly _permissions: AgentPermissionsApi;
63
63
  /** The DID to use as the author and default target for the underlying permission request */
64
- private _connectedDid: string;
64
+ private readonly _connectedDid: string;
65
65
  /** The underlying DWN `RecordsWrite` message along with encoded data that represent the request */
66
66
  private _message: DwnDataEncodedRecordsWriteMessage;
67
67
  /** The parsed permission request object */
68
- private _request: DwnPermissionRequest;
68
+ private readonly _request: DwnPermissionRequest;
69
69
 
70
70
  private constructor({ api, connectedDid, message, request }: {
71
71
  api: AgentPermissionsApi;
package/src/protocol.ts CHANGED
@@ -31,13 +31,13 @@ export type ProtocolMetadata = {
31
31
  */
32
32
  export class Protocol {
33
33
  /** The {@link EnboxAgent} instance that handles DWNs requests. */
34
- private _agent: EnboxAgent;
34
+ private readonly _agent: EnboxAgent;
35
35
 
36
36
  /** Metadata associated with the protocol, including the author and optional message CID. */
37
- private _metadata: ProtocolMetadata;
37
+ private readonly _metadata: ProtocolMetadata;
38
38
 
39
39
  /** The ProtocolsConfigureMessage containing the detailed configuration for the protocol. */
40
- private _protocolsConfigureMessage: DwnMessage[DwnInterface.ProtocolsConfigure];
40
+ private readonly _protocolsConfigureMessage: DwnMessage[DwnInterface.ProtocolsConfigure];
41
41
 
42
42
  /**
43
43
  * Constructs a new instance of the Protocol class.
@@ -48,19 +48,19 @@ export type ReadOnlyRecordOptions = {
48
48
  */
49
49
  export class ReadOnlyRecord {
50
50
  // Private backing fields.
51
- private _anonymousDwn: AnonymousDwnApi;
52
- private _remoteOrigin: string;
53
- private _author: string;
54
- private _creator: string;
55
- private _descriptor: RecordsWriteDescriptor;
56
- private _recordId: string;
57
- private _contextId?: string;
58
- private _initialWrite?: RecordsWriteMessage;
59
- private _encodedData?: Blob;
51
+ private readonly _anonymousDwn: AnonymousDwnApi;
52
+ private readonly _remoteOrigin: string;
53
+ private readonly _author: string;
54
+ private readonly _creator: string;
55
+ private readonly _descriptor: RecordsWriteDescriptor;
56
+ private readonly _recordId: string;
57
+ private readonly _contextId?: string;
58
+ private readonly _initialWrite?: RecordsWriteMessage;
59
+ private readonly _encodedData?: Blob;
60
60
  private _readableStream?: ReadableStream;
61
- private _authorization: RecordsWriteMessage['authorization'];
62
- private _attestation?: RecordsWriteMessage['attestation'];
63
- private _encryption?: RecordsWriteMessage['encryption'];
61
+ private readonly _authorization: RecordsWriteMessage['authorization'];
62
+ private readonly _attestation?: RecordsWriteMessage['attestation'];
63
+ private readonly _encryption?: RecordsWriteMessage['encryption'];
64
64
 
65
65
  constructor(options: ReadOnlyRecordOptions) {
66
66
  const { rawMessage, initialWrite, encodedData, data, remoteOrigin, anonymousDwn } = options;
package/src/record.ts CHANGED
@@ -90,31 +90,31 @@ export class Record implements RecordModel {
90
90
  * Cache to minimize the amount of redundant two-phase commits we do in store() and send()
91
91
  * Retains awareness of the last 100 records stored/sent for up to 100 target DIDs each.
92
92
  */
93
- private static _sendCache = SendCache;
93
+ private static readonly _sendCache = SendCache;
94
94
 
95
95
  // Record instance metadata.
96
96
 
97
97
  /** The {@link EnboxAgent} instance that handles DWNs requests. */
98
- private _agent: EnboxAgent;
98
+ private readonly _agent: EnboxAgent;
99
99
  /** The DID of the DWN tenant under which operations are being performed. */
100
- private _connectedDid: string;
100
+ private readonly _connectedDid: string;
101
101
  /** The optional DID that is delegated to act on behalf of the connectedDid */
102
- private _delegateDid?: string;
102
+ private readonly _delegateDid?: string;
103
103
  /** cache for fetching a permission {@link PermissionGrant}, keyed by a specific MessageType and protocol */
104
- private _permissionsApi: PermissionsApi;
104
+ private readonly _permissionsApi: PermissionsApi;
105
105
  /** Encoded data of the record, if available. */
106
106
  private _encodedData?: Blob;
107
107
  /** Stream of the record's data (Web ReadableStream for cross-platform compatibility). */
108
108
  private _readableStream?: ReadableStream;
109
109
  /** The origin DID if the record was fetched from a remote DWN. */
110
- private _remoteOrigin?: string;
110
+ private readonly _remoteOrigin?: string;
111
111
 
112
112
  // Private variables for DWN `RecordsWrite` message properties.
113
113
 
114
114
  /** The DID of the entity that most recently authored or deleted the record. */
115
- private _author: string;
115
+ private readonly _author: string;
116
116
  /** The DID of the entity that originally created the record. */
117
- private _creator: string;
117
+ private readonly _creator: string;
118
118
  /** Attestation JWS signature. */
119
119
  private _attestation?: DwnMessage[DwnInterface.RecordsWrite]['attestation'];
120
120
  /** Authorization signature(s). */
@@ -132,7 +132,7 @@ export class Record implements RecordModel {
132
132
  /** Flag indicating if the initial write has been signed by the owner. */
133
133
  private _initialWriteSigned: boolean;
134
134
  /** Unique identifier of the record. */
135
- private _recordId: string;
135
+ private readonly _recordId: string;
136
136
  /** Role under which the record is written. */
137
137
  private _protocolRole?: RecordOptions['protocolRole'];
138
138
 
@@ -605,7 +605,7 @@ export class Record implements RecordModel {
605
605
  remoteOrigin : this._remoteOrigin,
606
606
  protocolRole : protocolRole ?? this._protocolRole,
607
607
  initialWrite,
608
- encodedData : data !== undefined ? dataBlob : this._encodedData,
608
+ encodedData : data === undefined ? this._encodedData : dataBlob,
609
609
  ...responseMessage as DwnMessage[DwnInterface.RecordsWrite],
610
610
  }, this._permissionsApi);
611
611
 
@@ -622,7 +622,7 @@ export class Record implements RecordModel {
622
622
  this._contextId = msg.contextId;
623
623
  this._initialWrite = initialWrite;
624
624
  this._protocolRole = protocolRole ?? this._protocolRole;
625
- this._encodedData = data !== undefined ? dataBlob : this._encodedData;
625
+ this._encodedData = data === undefined ? this._encodedData : dataBlob;
626
626
  this._readableStream = undefined; // Invalidate any consumed stream.
627
627
  this._rawMessageDirty = true; // Force rawMessage cache rebuild.
628
628
 
package/src/repository.ts CHANGED
@@ -147,7 +147,7 @@ function buildRootSingletonMethods(
147
147
  if (records.length > 0) {
148
148
  const { status, record } = await records[0].update({
149
149
  data: options.data,
150
- ...(options.tags !== undefined ? { tags: options.tags } : {}),
150
+ ...(options.tags === undefined ? {} : { tags: options.tags }),
151
151
  } as never);
152
152
  return { status, record };
153
153
  }
@@ -245,7 +245,7 @@ function buildNestedSingletonMethods(
245
245
  if (records.length > 0) {
246
246
  const { status, record } = await records[0].update({
247
247
  data: options.data,
248
- ...(options.tags !== undefined ? { tags: options.tags } : {}),
248
+ ...(options.tags === undefined ? {} : { tags: options.tags }),
249
249
  } as never);
250
250
  return { status, record };
251
251
  }
@@ -552,17 +552,19 @@ export class TypedEnbox<
552
552
  M extends SchemaMap = SchemaMap,
553
553
  > {
554
554
  /** @internal */
555
- private _dwn: DwnApi;
555
+ private readonly _dwn: DwnApi;
556
556
  /** @internal */
557
- private _definition: D;
557
+ private readonly _definition: D;
558
558
  /** @internal */
559
559
  private _configured: boolean = false;
560
560
  /** @internal */
561
561
  private _ensureReadyPromise: Promise<void> | null = null;
562
562
  /** @internal */
563
- private _validPaths: Set<string>;
563
+ private readonly _validPaths: Set<string>;
564
564
  /** @internal */
565
565
  private _records?: TypedEnbox<D, M>['records'];
566
+ /** @internal — cached result of the `hasEncryptedTypes` scan (definition is immutable). */
567
+ private readonly _hasEncryptedTypes: boolean;
566
568
 
567
569
  /**
568
570
  * @internal Create a new `TypedEnbox` instance. Use `enbox.using(protocol)` instead.
@@ -573,6 +575,8 @@ export class TypedEnbox<
573
575
  this._dwn = dwn;
574
576
  this._definition = protocol.definition;
575
577
  this._validPaths = collectPaths(this._definition.structure);
578
+ this._hasEncryptedTypes = Object.values(this._definition.types)
579
+ .some((type: ProtocolType) => type.encryptionRequired === true);
576
580
  }
577
581
 
578
582
  /**
@@ -643,10 +647,7 @@ export class TypedEnbox<
643
647
  // protocols during the connect flow. The delegate cannot derive the
644
648
  // owner's encryption keys, so configuring here would install the
645
649
  // protocol WITHOUT $encryption keys — breaking all encrypted writes.
646
- const hasEncryptedTypes = Object.values(this._definition.types)
647
- .some((type) => (type as ProtocolType)?.encryptionRequired === true);
648
-
649
- if (this._dwn.isDelegate && hasEncryptedTypes) {
650
+ if (this._dwn.isDelegate && this._hasEncryptedTypes) {
650
651
  throw new Error(
651
652
  `TypedEnbox: Protocol '${this._definition.protocol}' requires ` +
652
653
  `encryption but is not installed or has changed. In delegate mode, ` +
@@ -656,7 +657,7 @@ export class TypedEnbox<
656
657
  );
657
658
  }
658
659
 
659
- const needsEncryption = !this._dwn.isDelegate && hasEncryptedTypes;
660
+ const needsEncryption = !this._dwn.isDelegate && this._hasEncryptedTypes;
660
661
 
661
662
  const result = await this._dwn.protocols.configure({
662
663
  definition : this._definition,
@@ -799,10 +800,7 @@ export class TypedEnbox<
799
800
  // installing without `$encryption` keys would allow plaintext writes
800
801
  // that the owner's remote DWN would later reject, or worse, persist
801
802
  // unencrypted sensitive data.
802
- const hasEncryptedTypes = Object.values(this._definition.types)
803
- .some((type) => (type as ProtocolType)?.encryptionRequired === true);
804
-
805
- if (hasEncryptedTypes) {
803
+ if (this._hasEncryptedTypes) {
806
804
  throw new Error(
807
805
  `TypedEnbox: delegate cannot install protocol '${this._definition.protocol}' ` +
808
806
  'because it contains types with encryptionRequired but the owner\'s ' +
@@ -961,7 +959,7 @@ export class TypedEnbox<
961
959
  const normalizedPath = normalizePath(path);
962
960
  await this._ensureReady(normalizedPath);
963
961
  const typeName = lastSegment(normalizedPath);
964
- const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
962
+ const typeEntry = this._definition.types[typeName];
965
963
 
966
964
  // Auto-enable encryption when the type requires it.
967
965
  // Delegates CAN encrypt because the $encryption public keys in the
@@ -982,7 +980,7 @@ export class TypedEnbox<
982
980
  tags : request.tags,
983
981
  protocol : this._definition.protocol,
984
982
  protocolPath : normalizedPath,
985
- ...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
983
+ ...(typeEntry?.schema === undefined ? {} : { schema: typeEntry.schema }),
986
984
  dataFormat : request.dataFormat ?? typeEntry?.dataFormats?.[0],
987
985
  });
988
986
 
@@ -1034,7 +1032,7 @@ export class TypedEnbox<
1034
1032
  const normalizedPath = normalizePath(path);
1035
1033
  await this._ensureReady(normalizedPath);
1036
1034
  const typeName = lastSegment(normalizedPath);
1037
- const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
1035
+ const typeEntry = this._definition.types[typeName];
1038
1036
 
1039
1037
  const queryFilter = mapParentContextId(request?.filter);
1040
1038
  // Auto-enable decryption when the type requires it. Delegates use
@@ -1050,7 +1048,7 @@ export class TypedEnbox<
1050
1048
  ...queryFilter,
1051
1049
  protocol : this._definition.protocol,
1052
1050
  protocolPath : normalizedPath,
1053
- ...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
1051
+ ...(typeEntry?.schema === undefined ? {} : { schema: typeEntry.schema }),
1054
1052
  },
1055
1053
  dateSort : request?.dateSort,
1056
1054
  pagination : request?.pagination,
@@ -1093,7 +1091,7 @@ export class TypedEnbox<
1093
1091
  const normalizedPath = normalizePath(path);
1094
1092
  await this._ensureReady(normalizedPath);
1095
1093
  const typeName = lastSegment(normalizedPath);
1096
- const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
1094
+ const typeEntry = this._definition.types[typeName];
1097
1095
 
1098
1096
  const readFilter = mapParentContextId(request.filter);
1099
1097
  // Auto-enable decryption when the type requires it. See query()
@@ -1109,7 +1107,7 @@ export class TypedEnbox<
1109
1107
  ...readFilter,
1110
1108
  protocol : this._definition.protocol,
1111
1109
  protocolPath : normalizedPath,
1112
- ...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
1110
+ ...(typeEntry?.schema === undefined ? {} : { schema: typeEntry.schema }),
1113
1111
  },
1114
1112
  });
1115
1113
 
@@ -1197,7 +1195,7 @@ export class TypedEnbox<
1197
1195
  const normalizedPath = normalizePath(path);
1198
1196
  await this._ensureReady(normalizedPath);
1199
1197
  const typeName = lastSegment(normalizedPath);
1200
- const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
1198
+ const typeEntry = this._definition.types[typeName];
1201
1199
 
1202
1200
  const subFilter = mapParentContextId(request?.filter);
1203
1201
 
@@ -1207,7 +1205,7 @@ export class TypedEnbox<
1207
1205
  ...subFilter,
1208
1206
  protocol : this._definition.protocol,
1209
1207
  protocolPath : normalizedPath,
1210
- ...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
1208
+ ...(typeEntry?.schema === undefined ? {} : { schema: typeEntry.schema }),
1211
1209
  },
1212
1210
  protocolRole: request?.protocolRole,
1213
1211
  });
@@ -1290,7 +1288,12 @@ function stripEncryptionBlocks(value: unknown): unknown {
1290
1288
  * `'friend/'` → `'friend'`, `'/group/member/'` → `'group/member'`.
1291
1289
  */
1292
1290
  function normalizePath(path: string): string {
1293
- return path.replace(/^\/+|\/+$/g, '');
1291
+ // Strip leading and trailing '/' without regex quantifiers (avoids ReDoS scanners).
1292
+ let start = 0;
1293
+ while (start < path.length && path.codePointAt(start) === 47) { start++; } // 47 === '/'
1294
+ let end = path.length;
1295
+ while (end > start && path.codePointAt(end - 1) === 47) { end--; }
1296
+ return path.slice(start, end);
1294
1297
  }
1295
1298
 
1296
1299
  /**
@@ -1342,7 +1345,7 @@ function stableStringify(value: unknown): string {
1342
1345
  return '[' + value.map((item) => stableStringify(item)).join(',') + ']';
1343
1346
  }
1344
1347
 
1345
- const keys = Object.keys(value as globalThis.Record<string, unknown>).sort();
1348
+ const keys = Object.keys(value as globalThis.Record<string, unknown>).sort((a, b) => a.localeCompare(b));
1346
1349
  const pairs = keys.map((key) =>
1347
1350
  JSON.stringify(key) + ':' + stableStringify((value as globalThis.Record<string, unknown>)[key])
1348
1351
  );
@@ -65,7 +65,7 @@ export type TypedRecordChange<T> = {
65
65
  */
66
66
  export class TypedLiveQuery<T> {
67
67
  /** The underlying untyped LiveQuery instance. */
68
- private _liveQuery: LiveQuery;
68
+ private readonly _liveQuery: LiveQuery;
69
69
 
70
70
  /** Cached typed record array, built lazily from the underlying records. */
71
71
  private _typedRecords?: TypedRecord<T>[];
@@ -189,7 +189,7 @@ export type TypedRecordDeleteResult<T> = DwnResponseStatus & {
189
189
  */
190
190
  export class TypedRecord<T> {
191
191
  /** @internal The underlying untyped Record instance. */
192
- private _record: Record;
192
+ private readonly _record: Record;
193
193
 
194
194
  /**
195
195
  * @internal Wrap an untyped {@link Record} in a type-safe shell.
package/src/utils.ts CHANGED
@@ -116,14 +116,14 @@ export class SendCache {
116
116
  * A private static map that serves as the core storage mechanism for the cache. It maps record
117
117
  * IDs to a set of target DIDs, indicating which records have been sent to which targets.
118
118
  */
119
- private static cache = new Map<string, Set<string>>();
119
+ private static readonly cache = new Map<string, Set<string>>();
120
120
 
121
121
  /**
122
122
  * The maximum number of entries allowed in the cache. Once this limit is exceeded, the oldest
123
123
  * entries are evicted to make room for new ones. This limit applies both to the number of records
124
124
  * and the number of targets per record.
125
125
  */
126
- private static sendCacheLimit = 100;
126
+ private static readonly sendCacheLimit = 100;
127
127
 
128
128
  /**
129
129
  * Checks if a given record ID has been sent to a specified target DID. This method is used to
package/src/vc-api.ts CHANGED
@@ -10,10 +10,10 @@ export class VcApi {
10
10
  * Holds the instance of a {@link EnboxAgent} that represents the current execution context for
11
11
  * the `VcApi`. This agent is used to process VC requests.
12
12
  */
13
- private agent: EnboxAgent;
13
+ private readonly agent: EnboxAgent;
14
14
 
15
15
  /** The DID of the tenant under which DID operations are being performed. */
16
- private connectedDid: string;
16
+ private readonly connectedDid: string;
17
17
 
18
18
  constructor(options: { agent: EnboxAgent, connectedDid: string }) {
19
19
  this.agent = options.agent;