@enbox/api 0.1.1 → 0.2.1

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 (58) hide show
  1. package/README.md +505 -138
  2. package/dist/browser.mjs +23 -13
  3. package/dist/browser.mjs.map +4 -4
  4. package/dist/esm/advanced.js +11 -0
  5. package/dist/esm/advanced.js.map +1 -0
  6. package/dist/esm/define-protocol.js +3 -3
  7. package/dist/esm/dwn-api.js +56 -114
  8. package/dist/esm/dwn-api.js.map +1 -1
  9. package/dist/esm/dwn-reader-api.js +128 -0
  10. package/dist/esm/dwn-reader-api.js.map +1 -0
  11. package/dist/esm/index.js +3 -2
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/live-query.js +5 -4
  14. package/dist/esm/live-query.js.map +1 -1
  15. package/dist/esm/read-only-record.js +255 -0
  16. package/dist/esm/read-only-record.js.map +1 -0
  17. package/dist/esm/record.js +46 -57
  18. package/dist/esm/record.js.map +1 -1
  19. package/dist/esm/typed-web5.js +242 -0
  20. package/dist/esm/typed-web5.js.map +1 -0
  21. package/dist/esm/web5.js +71 -3
  22. package/dist/esm/web5.js.map +1 -1
  23. package/dist/types/advanced.d.ts +12 -0
  24. package/dist/types/advanced.d.ts.map +1 -0
  25. package/dist/types/define-protocol.d.ts +3 -3
  26. package/dist/types/dwn-api.d.ts +13 -92
  27. package/dist/types/dwn-api.d.ts.map +1 -1
  28. package/dist/types/dwn-reader-api.d.ts +138 -0
  29. package/dist/types/dwn-reader-api.d.ts.map +1 -0
  30. package/dist/types/index.d.ts +3 -2
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/live-query.d.ts +13 -1
  33. package/dist/types/live-query.d.ts.map +1 -1
  34. package/dist/types/protocol-types.d.ts +2 -2
  35. package/dist/types/read-only-record.d.ts +133 -0
  36. package/dist/types/read-only-record.d.ts.map +1 -0
  37. package/dist/types/record.d.ts +37 -17
  38. package/dist/types/record.d.ts.map +1 -1
  39. package/dist/types/{typed-dwn-api.d.ts → typed-web5.d.ts} +70 -73
  40. package/dist/types/typed-web5.d.ts.map +1 -0
  41. package/dist/types/web5.d.ts +79 -3
  42. package/dist/types/web5.d.ts.map +1 -1
  43. package/package.json +10 -6
  44. package/src/advanced.ts +29 -0
  45. package/src/define-protocol.ts +3 -3
  46. package/src/dwn-api.ts +91 -232
  47. package/src/dwn-reader-api.ts +255 -0
  48. package/src/index.ts +3 -2
  49. package/src/live-query.ts +20 -4
  50. package/src/protocol-types.ts +2 -2
  51. package/src/read-only-record.ts +328 -0
  52. package/src/record.ts +116 -86
  53. package/src/typed-web5.ts +445 -0
  54. package/src/web5.ts +104 -5
  55. package/dist/esm/typed-dwn-api.js +0 -182
  56. package/dist/esm/typed-dwn-api.js.map +0 -1
  57. package/dist/types/typed-dwn-api.d.ts.map +0 -1
  58. package/src/typed-dwn-api.ts +0 -370
package/src/record.ts CHANGED
@@ -60,8 +60,8 @@ export type RecordModel = ImmutableRecordProperties & OptionalRecordProperties &
60
60
  /** The unique identifier of the record. */
61
61
  recordId?: string;
62
62
 
63
- /** The timestamp indicating when the record was last modified. */
64
- messageTimestamp?: string;
63
+ /** The message timestamp (time of creation, most recent update, or deletion). */
64
+ timestamp?: string;
65
65
 
66
66
  /** The protocol role under which this record is written. */
67
67
  protocolRole?: RecordOptions['protocolRole'];
@@ -152,8 +152,8 @@ export type RecordUpdateParams = {
152
152
  /** The size of the data in bytes. */
153
153
  dataSize?: DwnMessageDescriptor[DwnInterface.RecordsWrite]['dataSize'];
154
154
 
155
- /** The timestamp indicating when the record was last modified. */
156
- dateModified?: DwnMessageDescriptor[DwnInterface.RecordsWrite]['messageTimestamp'];
155
+ /** The timestamp of the update message. */
156
+ timestamp?: DwnMessageDescriptor[DwnInterface.RecordsWrite]['messageTimestamp'];
157
157
 
158
158
  /** The timestamp indicating when the record was published. */
159
159
  datePublished?: DwnMessageDescriptor[DwnInterface.RecordsWrite]['datePublished'];
@@ -196,13 +196,33 @@ export type RecordDeleteParams = {
196
196
  /** Whether or not to prune any children this record may have. */
197
197
  prune?: DwnMessageDescriptor[DwnInterface.RecordsDelete]['prune'];
198
198
 
199
- /** The timestamp indicating when the record was deleted. */
200
- dateModified?: DwnMessageDescriptor[DwnInterface.RecordsDelete]['messageTimestamp'];
199
+ /** The timestamp of the delete message. */
200
+ timestamp?: DwnMessageDescriptor[DwnInterface.RecordsDelete]['messageTimestamp'];
201
201
 
202
202
  /** The protocol role under which this record will be deleted. */
203
203
  protocolRole?: string;
204
204
  };
205
205
 
206
+ /**
207
+ * The result of a {@link Record.update} operation.
208
+ *
209
+ * @beta
210
+ */
211
+ export type RecordUpdateResult = DwnResponseStatus & {
212
+ /** The updated Record instance reflecting the new state. */
213
+ record: Record;
214
+ };
215
+
216
+ /**
217
+ * The result of a {@link Record.delete} operation.
218
+ *
219
+ * @beta
220
+ */
221
+ export type RecordDeleteResult = DwnResponseStatus & {
222
+ /** The deleted Record instance reflecting the deleted state. */
223
+ record: Record;
224
+ };
225
+
206
226
  /**
207
227
  * The `Record` class encapsulates a single record's data and metadata, providing a more
208
228
  * developer-friendly interface for working with Decentralized Web Node (DWN) records.
@@ -210,12 +230,9 @@ export type RecordDeleteParams = {
210
230
  * Methods are provided to read, update, and manage the record's lifecycle, including writing to
211
231
  * remote DWNs.
212
232
  *
213
- * Note: The `messageTimestamp` of the most recent RecordsWrite message is
214
- * logically equivalent to the date/time at which a Record was most
215
- * recently modified. Since this Record class implementation is
216
- * intended to simplify the developer experience of working with
217
- * logical records (and not individual DWN messages) the
218
- * `messageTimestamp` is mapped to `dateModified`.
233
+ * Note: The DWN SDK's `messageTimestamp` is exposed as `timestamp` on
234
+ * the Record class. It represents the time of the most recent
235
+ * message (create, update, or delete) for this logical record.
219
236
  *
220
237
  * @beta
221
238
  */
@@ -270,9 +287,14 @@ export class Record implements RecordModel {
270
287
  /** Role under which the record is written. */
271
288
  private _protocolRole?: RecordOptions['protocolRole'];
272
289
 
290
+ /** Cached reconstructed raw message, invalidated when record state changes. */
291
+ private _rawMessageCache?: DwnMessage[DwnInterface.RecordsWrite] | DwnMessage[DwnInterface.RecordsDelete];
292
+ /** Dirty flag indicating the cached raw message needs to be rebuilt. */
293
+ private _rawMessageDirty: boolean = true;
294
+
273
295
  /** The `RecordsWriteMessage` descriptor unless the record is in a deleted state */
274
296
  private get _recordsWriteDescriptor(): DwnMessageDescriptor[DwnInterface.RecordsWrite] | undefined {
275
- if (isDwnMessage(DwnInterface.RecordsWrite, this.rawMessage)) {
297
+ if (!this.isRecordsDeleteDescriptor(this._descriptor)) {
276
298
  return this._descriptor as DwnMessageDescriptor[DwnInterface.RecordsWrite];
277
299
  }
278
300
 
@@ -338,8 +360,8 @@ export class Record implements RecordModel {
338
360
  /** DID that is the original creator of the Record. */
339
361
  get creator(): string { return this._creator; }
340
362
 
341
- /** Record's modified date */
342
- get dateModified(): string { return this._descriptor.messageTimestamp; }
363
+ /** Record's message timestamp (time of creation, most recent update, or deletion). */
364
+ get timestamp(): string { return this._descriptor.messageTimestamp; }
343
365
 
344
366
  /** Record's encryption */
345
367
  get encryption(): DwnMessage[DwnInterface.RecordsWrite]['encryption'] { return this._encryption; }
@@ -354,15 +376,20 @@ export class Record implements RecordModel {
354
376
  get protocolRole(): string | undefined { return this._protocolRole; }
355
377
 
356
378
  /** Record's deleted state (true/false) */
357
- get deleted(): boolean { return isDwnMessage(DwnInterface.RecordsDelete, this.rawMessage); }
379
+ get deleted(): boolean { return this.isRecordsDeleteDescriptor(this._descriptor); }
358
380
 
359
381
  /** Record's initial write if the record has been updated */
360
382
  get initialWrite(): RecordOptions['initialWrite'] { return this._initialWrite; }
361
383
 
362
384
  /**
363
385
  * Returns a copy of the raw `RecordsWriteMessage` that was used to create the current `Record` instance.
386
+ * The result is cached and only rebuilt when the record's state changes (via `update()` or `delete()`).
364
387
  */
365
388
  get rawMessage(): DwnMessage[DwnInterface.RecordsWrite] | DwnMessage[DwnInterface.RecordsDelete] {
389
+ if (!this._rawMessageDirty && this._rawMessageCache) {
390
+ return this._rawMessageCache;
391
+ }
392
+
366
393
  const messageType = this._descriptor.interface + this._descriptor.method;
367
394
  let message: DwnMessage[DwnInterface.RecordsWrite] | DwnMessage[DwnInterface.RecordsDelete];
368
395
  if (messageType === DwnInterface.RecordsWrite) {
@@ -382,6 +409,9 @@ export class Record implements RecordModel {
382
409
  }
383
410
 
384
411
  removeUndefinedProperties(message);
412
+
413
+ this._rawMessageCache = message;
414
+ this._rawMessageDirty = false;
385
415
  return message;
386
416
  }
387
417
 
@@ -661,7 +691,7 @@ export class Record implements RecordModel {
661
691
  messageType : DwnInterface.RecordsWrite,
662
692
  author : this._connectedDid,
663
693
  target : target,
664
- dataStream : await this.data.blob(),
694
+ dataStream : this._encodedData ?? await this.data.blob(),
665
695
  rawMessage : { ...this.rawMessage }
666
696
  };
667
697
  }
@@ -677,26 +707,26 @@ export class Record implements RecordModel {
677
707
  */
678
708
  toJSON(): RecordModel {
679
709
  return {
680
- attestation : this.attestation,
681
- author : this.author,
682
- authorization : this.authorization,
683
- contextId : this.contextId,
684
- dataCid : this.dataCid,
685
- dataFormat : this.dataFormat,
686
- dataSize : this.dataSize,
687
- dateCreated : this.dateCreated,
688
- messageTimestamp : this.dateModified,
689
- datePublished : this.datePublished,
690
- encryption : this.encryption,
691
- parentId : this.parentId,
692
- protocol : this.protocol,
693
- protocolPath : this.protocolPath,
694
- protocolRole : this.protocolRole,
695
- published : this.published,
696
- recipient : this.recipient,
697
- recordId : this.id,
698
- schema : this.schema,
699
- tags : this.tags,
710
+ attestation : this.attestation,
711
+ author : this.author,
712
+ authorization : this.authorization,
713
+ contextId : this.contextId,
714
+ dataCid : this.dataCid,
715
+ dataFormat : this.dataFormat,
716
+ dataSize : this.dataSize,
717
+ dateCreated : this.dateCreated,
718
+ datePublished : this.datePublished,
719
+ encryption : this.encryption,
720
+ parentId : this.parentId,
721
+ protocol : this.protocol,
722
+ protocolPath : this.protocolPath,
723
+ protocolRole : this.protocolRole,
724
+ published : this.published,
725
+ recipient : this.recipient,
726
+ recordId : this.id,
727
+ schema : this.schema,
728
+ tags : this.tags,
729
+ timestamp : this.timestamp,
700
730
  };
701
731
  }
702
732
 
@@ -720,7 +750,7 @@ export class Record implements RecordModel {
720
750
 
721
751
  str += ` Deleted: ${this.deleted}\n`;
722
752
  str += ` Created: ${this.dateCreated}\n`;
723
- str += ` Modified: ${this.dateModified}\n`;
753
+ str += ` Timestamp: ${this.timestamp}\n`;
724
754
  str += `}`;
725
755
  return str;
726
756
  }
@@ -743,7 +773,7 @@ export class Record implements RecordModel {
743
773
  *
744
774
  * @beta
745
775
  */
746
- async update({ dateModified, data, encryption, protocolRole, store = true, ...params }: RecordUpdateParams): Promise<DwnResponseStatus> {
776
+ async update({ timestamp, data, encryption, protocolRole, store = true, ...params }: RecordUpdateParams): Promise<RecordUpdateResult> {
747
777
 
748
778
  if (this.deleted) {
749
779
  throw new Error('Record: Cannot revive a deleted record.');
@@ -763,7 +793,7 @@ export class Record implements RecordModel {
763
793
  ...params,
764
794
  parentContextId,
765
795
  protocolRole : protocolRole ?? this._protocolRole, // Use the current protocolRole if not provided.
766
- messageTimestamp : dateModified, // Map Record class `dateModified` property to DWN SDK `messageTimestamp`
796
+ messageTimestamp : timestamp, // Map Record class `timestamp` property to DWN SDK `messageTimestamp`
767
797
  recordId : this._recordId
768
798
  };
769
799
 
@@ -785,7 +815,7 @@ export class Record implements RecordModel {
785
815
  }
786
816
 
787
817
  // Throw an error if an attempt is made to modify immutable properties.
788
- // Note: `data` and `dateModified` have already been handled.
818
+ // Note: `data` and `timestamp` have already been handled.
789
819
  const mutableDescriptorProperties = new Set(['data', 'dataCid', 'dataFormat', 'dataSize', 'datePublished', 'messageTimestamp', 'published', 'tags']);
790
820
  Record.verifyPermittedMutation(Object.keys(params), mutableDescriptorProperties);
791
821
 
@@ -820,41 +850,38 @@ export class Record implements RecordModel {
820
850
 
821
851
  const agentResponse = await this._agent.processDwnRequest(requestOptions);
822
852
 
823
- const { message, reply: { status } } = agentResponse;
824
- const responseMessage = message;
825
-
826
- if (200 <= status.code && status.code <= 299) {
827
- // copy the original raw message to the initial write before we update the values.
828
- if (!this._initialWrite) {
829
- // If there is no initial write, we need to create one from the current record state.
830
- // We checked in the beginning of the function that the rawMessage is a RecordsWrite message.
831
- this._initialWrite = { ...this.rawMessage as DwnMessage[DwnInterface.RecordsWrite] };
832
- }
833
-
834
- // Only update the local Record instance mutable properties if the record was successfully (over)written.
835
- this._authorization = responseMessage.authorization;
836
- this._encryption = responseMessage.encryption;
837
- this._protocolRole = updateMessage.protocolRole;
838
- mutableDescriptorProperties.forEach(property => {
839
- this._descriptor[property] = responseMessage.descriptor[property];
840
- });
853
+ const { message: responseMessage, reply: { status } } = agentResponse;
841
854
 
842
- // Cache data.
843
- if (data !== undefined) {
844
- this._encodedData = dataBlob;
845
- }
855
+ if (!(200 <= status.code && status.code <= 299)) {
856
+ // Return a shallow copy of this record on failure — no state change.
857
+ return { status, record: this };
846
858
  }
847
859
 
848
- return { status };
860
+ // Determine the initial write for the new Record instance.
861
+ const initialWrite = this._initialWrite ?? { ...this.rawMessage as DwnMessage[DwnInterface.RecordsWrite] };
862
+
863
+ // Construct a new Record instance reflecting the updated state.
864
+ const updatedRecord = new Record(this._agent, {
865
+ author : this._author,
866
+ connectedDid : this._connectedDid,
867
+ delegateDid : this._delegateDid,
868
+ remoteOrigin : this._remoteOrigin,
869
+ protocolRole : protocolRole ?? this._protocolRole,
870
+ initialWrite,
871
+ encodedData : data !== undefined ? dataBlob : this._encodedData,
872
+ ...responseMessage as DwnMessage[DwnInterface.RecordsWrite],
873
+ }, this._permissionsApi);
874
+
875
+ return { status, record: updatedRecord };
849
876
  }
850
877
 
851
878
  /**
852
879
  * Delete the current record on the DWN.
853
880
  * @param params - Parameters to delete the record.
854
- * @returns the status of the delete request
881
+ * @returns the status and a new Record instance reflecting the deleted state
855
882
  */
856
- async delete(deleteParams?: RecordDeleteParams): Promise<DwnResponseStatus> {
857
- const { store = true, signAsOwner, dateModified, prune = false } = deleteParams || {};
883
+ async delete(deleteParams?: RecordDeleteParams): Promise<RecordDeleteResult> {
884
+ const { store = true, signAsOwner, timestamp, prune = false } = deleteParams || {};
858
885
 
859
886
  const signAsOwnerValue = signAsOwner && this._delegateDid === undefined;
860
887
  const signAsOwnerDelegate = signAsOwner && this._delegateDid !== undefined;
@@ -893,7 +920,7 @@ export class Record implements RecordModel {
893
920
  deleteOptions.messageParams = {
894
921
  prune : prune,
895
922
  recordId : this._recordId,
896
- messageTimestamp : dateModified,
923
+ messageTimestamp : timestamp,
897
924
  protocolRole : deleteParams?.protocolRole ?? this._protocolRole // if no protocolRole is provided, use the current protocolRole
898
925
  };
899
926
  }
@@ -920,22 +947,23 @@ export class Record implements RecordModel {
920
947
  const { message, reply: { status } } = agentResponse;
921
948
 
922
949
  if (status.code !== 202) {
923
- // If the delete was not successful, return the status.
924
- return { status };
950
+ // If the delete was not successful, return this record unchanged.
951
+ return { status, record: this };
925
952
  }
926
953
 
927
- // If the delete was successful, update the Record author to the author of the delete message.
928
- this._author = getRecordAuthor(message);
929
- this._descriptor = message.descriptor;
930
- this._authorization = message.authorization;
931
-
932
- // clear out properties that are not relevant for a deleted record
933
- this._encodedData = undefined;
934
- this._encryption = undefined;
935
- this._attestation = undefined;
936
- this._contextId = undefined;
937
-
938
- return { status };
954
+ // Construct a new Record instance reflecting the deleted state.
955
+ const initialWrite = this._initialWrite;
956
+ const deletedRecord = new Record(this._agent, {
957
+ author : getRecordAuthor(message),
958
+ connectedDid : this._connectedDid,
959
+ delegateDid : this._delegateDid,
960
+ remoteOrigin : this._remoteOrigin,
961
+ protocolRole : deleteParams?.protocolRole ?? this._protocolRole,
962
+ initialWrite,
963
+ ...message as DwnMessage[DwnInterface.RecordsDelete],
964
+ }, this._permissionsApi);
965
+
966
+ return { status, record: deletedRecord };
939
967
  }
940
968
 
941
969
  /**
@@ -1051,7 +1079,10 @@ export class Record implements RecordModel {
1051
1079
  if (200 <= status.code && status.code <= 299) {
1052
1080
  // If we are signing as the owner, make sure to update the current record state's
1053
1081
  // authorization, because now it will have the owner's signature on it.
1054
- if (signAsOwner) {this._authorization = responseMessage.authorization;}
1082
+ if (signAsOwner) {
1083
+ this._authorization = responseMessage.authorization;
1084
+ this._rawMessageDirty = true;
1085
+ }
1055
1086
  }
1056
1087
 
1057
1088
  return { status };
@@ -1088,9 +1119,8 @@ export class Record implements RecordModel {
1088
1119
  // When reading the data as a delegate, if we don't find a grant we will attempt to read it with the delegate DID as the author.
1089
1120
  // This allows users to read publicly available data without needing explicit grants.
1090
1121
  //
1091
- // NOTE: When a read-only Record class is implemented, callers would have that returned instead when they don't have an explicit permission.
1092
- // This should fail if a permission is not found, although it should not happen in practice.
1093
- // TODO: https://github.com/enboxorg/enbox/issues/898
1122
+ // NOTE: For anonymous/public record data access, callers can use `ReadOnlyRecord` via `Web5.anonymous()`.
1123
+ // See: https://github.com/enboxorg/enbox/issues/898
1094
1124
  try {
1095
1125
  const { message: delegatedGrant } = await this._permissionsApi.getPermissionForRequest({
1096
1126
  connectedDid : this._connectedDid,