UNPKG

@enbox/api

Version:

SDK for accessing the features and capabilities of Web5

814 lines 43.3 kB
/** * NOTE: Added reference types here to avoid a `pnpm` bug during build. * https://github.com/TBD54566975/web5-js/pull/507 */ /// <reference types="@enbox/dwn-sdk-js" /> var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { DwnInterface, getPaginationCursor, getRecordAuthor, isDwnMessage, AgentPermissionsApi, getRecordProtocolRole } from '@enbox/agent'; import { Convert, isEmptyObject, NodeStream, removeUndefinedProperties, Stream } from '@enbox/common'; import { dataToBlob, SendCache } from './utils.js'; /** * The `Record` class encapsulates a single record's data and metadata, providing a more * developer-friendly interface for working with Decentralized Web Node (DWN) records. * * Methods are provided to read, update, and manage the record's lifecycle, including writing to * remote DWNs. * * Note: The `messageTimestamp` of the most recent RecordsWrite message is * logically equivalent to the date/time at which a Record was most * recently modified. Since this Record class implementation is * intended to simplify the developer experience of working with * logical records (and not individual DWN messages) the * `messageTimestamp` is mapped to `dateModified`. * * @beta */ export class Record { /** The `RecordsWriteMessage` descriptor unless the record is in a deleted state */ get _recordsWriteDescriptor() { if (isDwnMessage(DwnInterface.RecordsWrite, this.rawMessage)) { return this._descriptor; } return undefined; // returns undefined if the descriptor does not represent a RecordsWrite message. } /** The `RecordsWrite` descriptor from the current record or the initial write if the record is in a delete state. */ get _immutableProperties() { return this._recordsWriteDescriptor || this._initialWrite.descriptor; } // Getters for immutable Record properties. /** Record's ID */ get id() { return this._recordId; } /** Record's context ID. If the record is deleted, the context Id comes from the initial write */ get contextId() { return this.deleted ? this._initialWrite.contextId : this._contextId; } /** Record's creation date */ get dateCreated() { return this._immutableProperties.dateCreated; } /** Record's parent ID */ get parentId() { return this._immutableProperties.parentId; } /** Record's protocol */ get protocol() { return this._immutableProperties.protocol; } /** Record's protocol path */ get protocolPath() { return this._immutableProperties.protocolPath; } /** Record's recipient */ get recipient() { return this._immutableProperties.recipient; } /** Record's schema */ get schema() { return this._immutableProperties.schema; } // Getters for mutable DWN RecordsWrite properties that may be undefined in a deleted state. /** Record's data format */ get dataFormat() { var _a; return (_a = this._recordsWriteDescriptor) === null || _a === void 0 ? void 0 : _a.dataFormat; } /** Record's CID */ get dataCid() { var _a; return (_a = this._recordsWriteDescriptor) === null || _a === void 0 ? void 0 : _a.dataCid; } /** Record's data size */ get dataSize() { var _a; return (_a = this._recordsWriteDescriptor) === null || _a === void 0 ? void 0 : _a.dataSize; } /** Record's published date */ get datePublished() { var _a; return (_a = this._recordsWriteDescriptor) === null || _a === void 0 ? void 0 : _a.datePublished; } /** Record's published status (true/false) */ get published() { var _a; return (_a = this._recordsWriteDescriptor) === null || _a === void 0 ? void 0 : _a.published; } /** Tags of the record */ get tags() { var _a; return (_a = this._recordsWriteDescriptor) === null || _a === void 0 ? void 0 : _a.tags; } // Getters for for properties that depend on the current state of the Record. /** DID that is the logical author of the Record. */ get author() { return this._author; } /** DID that is the original creator of the Record. */ get creator() { return this._creator; } /** Record's modified date */ get dateModified() { return this._descriptor.messageTimestamp; } /** Record's encryption */ get encryption() { return this._encryption; } /** Record's signatures attestation */ get authorization() { return this._authorization; } /** Record's signatures attestation */ get attestation() { return this._attestation; } /** Role under which the author is writing the record */ get protocolRole() { return this._protocolRole; } /** Record's deleted state (true/false) */ get deleted() { return isDwnMessage(DwnInterface.RecordsDelete, this.rawMessage); } /** Record's initial write if the record has been updated */ get initialWrite() { return this._initialWrite; } /** * Returns a copy of the raw `RecordsWriteMessage` that was used to create the current `Record` instance. */ get rawMessage() { const messageType = this._descriptor.interface + this._descriptor.method; let message; if (messageType === DwnInterface.RecordsWrite) { message = JSON.parse(JSON.stringify({ contextId: this._contextId, recordId: this._recordId, descriptor: this._descriptor, attestation: this._attestation, authorization: this._authorization, encryption: this._encryption, })); } else { message = JSON.parse(JSON.stringify({ descriptor: this._descriptor, authorization: this._authorization, })); } removeUndefinedProperties(message); return message; } constructor(agent, options, permissionsApi) { this._agent = agent; // Store the author DID that originally signed the message as a convenience for developers, so // that they don't have to decode the signer's DID from the JWS. this._author = options.author; // The creator is the author of the initial write, or the author of the record if there is no initial write. this._creator = options.initialWrite ? getRecordAuthor(options.initialWrite) : options.author; // Store the `connectedDid`, and optionally the `delegateDid` and `permissionsApi` in order to be able // to perform operations on the record (update, delete, data) as a delegate of the connected DID. this._connectedDid = options.connectedDid; this._delegateDid = options.delegateDid; this._permissionsApi = permissionsApi !== null && permissionsApi !== void 0 ? permissionsApi : new AgentPermissionsApi({ agent }); // If the record was queried or read from a remote DWN, the `remoteOrigin` DID will be // defined. This value is used to send subsequent read requests to the same remote DWN in the // event the record's data payload was too large to be returned in query results. or must be // read again (e.g., if the data stream is consumed). this._remoteOrigin = options.remoteOrigin; // RecordsWriteMessage properties. this._attestation = options.attestation; this._authorization = options.authorization; this._contextId = options.contextId; this._descriptor = options.descriptor; this._encryption = options.encryption; this._initialWrite = options.initialWrite; this._recordId = this.isRecordsDeleteDescriptor(options.descriptor) ? options.descriptor.recordId : options.recordId; this._protocolRole = options.protocolRole; if (options.encodedData) { // If `encodedData` is set, then it is expected that: // type is Blob if the Record object was instantiated by dwn.records.create()/write(). // type is Base64 URL encoded string if the Record object was instantiated by dwn.records.query(). // If it is a string, we need to Base64 URL decode to bytes and instantiate a Blob. this._encodedData = (typeof options.encodedData === 'string') ? new Blob([Convert.base64Url(options.encodedData).toUint8Array()], { type: this.dataFormat }) : options.encodedData; } if (options.data) { // If the record was created from a RecordsRead reply then it will have a `data` property. // If the `data` property is a web ReadableStream, convert it to a Node.js Readable. this._readableStream = Stream.isReadableStream(options.data) ? NodeStream.fromWebReadable({ readableStream: options.data }) : options.data; } } /** * Returns the data of the current record. * If the record data is not available, it attempts to fetch the data from the DWN. * @returns a data stream with convenience methods such as `blob()`, `json()`, `text()`, and `stream()`, similar to the fetch API response * @throws `Error` if the record has already been deleted. * * @beta */ get data() { const self = this; // Capture the context of the `Record` instance. const dataObj = { /** * Returns the data of the current record as a `Blob`. * * @returns A promise that resolves to a Blob containing the record's data. * @throws If the record data is not available or cannot be converted to a `Blob`. * * @beta */ blob() { return __awaiter(this, void 0, void 0, function* () { return new Blob([yield NodeStream.consumeToBytes({ readable: yield this.stream() })], { type: self.dataFormat }); }); }, /** * Returns the data of the current record as a `Uint8Array`. * * @returns A Promise that resolves to a `Uint8Array` containing the record's data bytes. * @throws If the record data is not available or cannot be converted to a byte array. * * @beta */ bytes() { return __awaiter(this, void 0, void 0, function* () { return yield NodeStream.consumeToBytes({ readable: yield this.stream() }); }); }, /** * Parses the data of the current record as JSON and returns it as a JavaScript object. * * @returns A Promise that resolves to a JavaScript object parsed from the record's JSON data. * @throws If the record data is not available, not in JSON format, or cannot be parsed. * * @beta */ json() { return __awaiter(this, void 0, void 0, function* () { return yield NodeStream.consumeToJson({ readable: yield this.stream() }); }); }, /** * Returns the data of the current record as a `string`. * * @returns A promise that resolves to a `string` containing the record's text data. * @throws If the record data is not available or cannot be converted to text. * * @beta */ text() { return __awaiter(this, void 0, void 0, function* () { return yield NodeStream.consumeToText({ readable: yield this.stream() }); }); }, /** * Provides a `Readable` stream containing the record's data. * * @returns A promise that resolves to a Node.js `Readable` stream of the record's data. * @throws If the record data is not available in-memory and cannot be fetched. * * @beta */ stream() { return __awaiter(this, void 0, void 0, function* () { if (self._encodedData) { /** If `encodedData` is set, it indicates that the Record was instantiated by * `dwn.records.create()`/`dwn.records.write()` or the record's data payload was small * enough to be returned in `dwn.records.query()` results. In either case, the data is * already available in-memory and can be returned as a Node.js `Readable` stream. */ self._readableStream = NodeStream.fromWebReadable({ readableStream: self._encodedData.stream() }); } else if (!NodeStream.isReadable({ readable: self._readableStream })) { /** If the data stream for this `Record` instance has already been partially or fully * consumed, then the data must be fetched again from either: */ self._readableStream = self._remoteOrigin ? // A. ...a remote DWN if the record was originally queried from a remote DWN. yield self.readRecordData({ target: self._remoteOrigin, isRemote: true }) : // B. ...a local DWN if the record was originally queried from the local DWN. yield self.readRecordData({ target: self._connectedDid, isRemote: false }); } if (!self._readableStream) { throw new Error('Record data is not available.'); } return self._readableStream; }); }, /** * Attaches callbacks for the resolution and/or rejection of the `Promise` returned by * `stream()`. * * This method is a proxy to the `then` method of the `Promise` returned by `stream()`, * allowing for a seamless integration with promise-based workflows. * @param onFulfilled - A function to asynchronously execute when the `stream()` promise * becomes fulfilled. * @param onRejected - A function to asynchronously execute when the `stream()` promise * becomes rejected. * @returns A `Promise` for the completion of which ever callback is executed. */ then(onFulfilled, onRejected) { return this.stream().then(onFulfilled, onRejected); }, /** * Attaches a rejection handler callback to the `Promise` returned by the `stream()` method. * This method is a shorthand for `.then(undefined, onRejected)`, specifically designed for handling * rejection cases in the promise chain initiated by accessing the record's data. It ensures that * errors during data retrieval or processing can be caught and handled appropriately. * * @param onRejected - A function to asynchronously execute when the `stream()` promise * becomes rejected. * @returns A `Promise` that resolves to the value of the callback if it is called, or to its * original fulfillment value if the promise is instead fulfilled. */ catch(onRejected) { return this.stream().catch(onRejected); } }; return dataObj; } /** * Stores the current record state as well as any initial write to the owner's DWN. * * @param importRecord - if true, the record will signed by the owner before storing it to the owner's DWN. Defaults to false. * @returns the status of the store request * * @beta */ store(importRecord = false) { return __awaiter(this, void 0, void 0, function* () { // if we are importing the record we sign it as the owner return this.processRecord({ signAsOwner: importRecord, store: true }); }); } /** * Signs the current record state as well as any initial write and optionally stores it to the owner's DWN. * This is useful when importing a record that was signed by someone else into your own DWN. * * @param store - if true, the record will be stored to the owner's DWN after signing. Defaults to true. * @returns the status of the import request * * @beta */ import(store = true) { return __awaiter(this, void 0, void 0, function* () { return this.processRecord({ store, signAsOwner: true }); }); } /** * Send the current record to a remote DWN by specifying their DID * If no DID is specified, the target is assumed to be the owner (connectedDID). * * If an initial write is present and the Record class send cache has no awareness of it, the initial write is sent first * (vs waiting for the regular DWN sync) * * @param target - the optional DID to send the record to, if none is set it is sent to the connectedDid * @returns the status of the send record request * @throws `Error` if the record has already been deleted. * * @beta */ send(target) { return __awaiter(this, void 0, void 0, function* () { const initialWrite = this._initialWrite; target !== null && target !== void 0 ? target : (target = this._connectedDid); // Is there an initial write? Do we know if we've already sent it to this target? if (initialWrite && !Record._sendCache.check(this._recordId, target)) { // We do have an initial write, so prepare it for sending to the target. const rawMessage = Object.assign({}, initialWrite); removeUndefinedProperties(rawMessage); // Send the initial write to the target. yield this._agent.sendDwnRequest({ messageType: DwnInterface.RecordsWrite, author: this._connectedDid, target: target, rawMessage }); // Set the cache to maintain awareness that we don't need to send the initial write next time. Record._sendCache.set(this._recordId, target); } let sendRequestOptions; if (this.deleted) { sendRequestOptions = { messageType: DwnInterface.RecordsDelete, author: this._connectedDid, target: target, rawMessage: Object.assign({}, this.rawMessage) }; } else { sendRequestOptions = { messageType: DwnInterface.RecordsWrite, author: this._connectedDid, target: target, dataStream: yield this.data.blob(), rawMessage: Object.assign({}, this.rawMessage) }; } // Send the current/latest state to the target. const { reply } = yield this._agent.sendDwnRequest(sendRequestOptions); return reply; }); } /** * Returns a JSON representation of the Record instance. * It's called by `JSON.stringify(...)` automatically. */ toJSON() { return { attestation: this.attestation, author: this.author, authorization: this.authorization, contextId: this.contextId, dataCid: this.dataCid, dataFormat: this.dataFormat, dataSize: this.dataSize, dateCreated: this.dateCreated, messageTimestamp: this.dateModified, datePublished: this.datePublished, encryption: this.encryption, parentId: this.parentId, protocol: this.protocol, protocolPath: this.protocolPath, protocolRole: this.protocolRole, published: this.published, recipient: this.recipient, recordId: this.id, schema: this.schema, tags: this.tags, }; } /** * Convenience method to return the string representation of the Record instance. * Called automatically in string concatenation, String() type conversion, and template literals. */ toString() { let str = `Record: {\n`; str += ` ID: ${this.id}\n`; str += this.contextId ? ` Context ID: ${this.contextId}\n` : ''; str += this.protocol ? ` Protocol: ${this.protocol}\n` : ''; str += this.schema ? ` Schema: ${this.schema}\n` : ''; // Only display data properties if the record has not been deleted. if (!this.deleted) { str += ` Data CID: ${this.dataCid}\n`; str += ` Data Format: ${this.dataFormat}\n`; str += ` Data Size: ${this.dataSize}\n`; } str += ` Deleted: ${this.deleted}\n`; str += ` Created: ${this.dateCreated}\n`; str += ` Modified: ${this.dateModified}\n`; str += `}`; return str; } /** * Returns a pagination cursor for the current record given a sort order. * * @param sort the sort order to use for the pagination cursor. * @returns A promise that resolves to a pagination cursor for the current record. */ paginationCursor(sort) { return __awaiter(this, void 0, void 0, function* () { return isDwnMessage(DwnInterface.RecordsWrite, this.rawMessage) ? getPaginationCursor(this.rawMessage, sort) : undefined; }); } /** * Update the current record on the DWN. * @param params - Parameters to update the record. * @returns the status of the update request * @throws `Error` if the record has already been deleted. * * @beta */ update(_a) { var { dateModified, data, protocolRole, store = true } = _a, params = __rest(_a, ["dateModified", "data", "protocolRole", "store"]); return __awaiter(this, void 0, void 0, function* () { if (this.deleted) { throw new Error('Record: Cannot revive a deleted record.'); } // if there is a parentId, we remove it from the descriptor and set a parentContextId const _b = this._recordsWriteDescriptor, { parentId } = _b, descriptor = __rest(_b, ["parentId"]); const parentContextId = parentId ? this._contextId.split('/').slice(0, -1).join('/') : undefined; // Begin assembling the update message. let updateMessage = Object.assign(Object.assign(Object.assign({}, descriptor), params), { parentContextId, protocolRole: protocolRole !== null && protocolRole !== void 0 ? protocolRole : this._protocolRole, messageTimestamp: dateModified, recordId: this._recordId }); // NOTE: The original Record's tags are copied to the update message, so that the tags are not lost. // However if a user passes new tags in the `RecordUpdateParams` object, they will overwrite the original tags. // If the updated tag object is empty or set to null, we remove the tags property to avoid schema validation errors in the DWN SDK. if (isEmptyObject(updateMessage.tags) || updateMessage.tags === null) { delete updateMessage.tags; } let dataBlob; if (data !== undefined) { // If `data` is being updated then `dataCid` and `dataSize` must be undefined and the `data` // value must be converted to a Blob and later passed as a top-level property to // `agent.processDwnRequest()`. delete updateMessage.dataCid; delete updateMessage.dataSize; ({ dataBlob } = dataToBlob(data, updateMessage.dataFormat)); } // Throw an error if an attempt is made to modify immutable properties. // Note: `data` and `dateModified` have already been handled. const mutableDescriptorProperties = new Set(['data', 'dataCid', 'dataFormat', 'dataSize', 'datePublished', 'messageTimestamp', 'published', 'tags']); Record.verifyPermittedMutation(Object.keys(params), mutableDescriptorProperties); // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation // will throw an error if `published` is false but `datePublished` is set. if (params.published === false && updateMessage.datePublished !== undefined) { delete updateMessage.datePublished; } const requestOptions = { author: this._connectedDid, dataStream: dataBlob, messageParams: Object.assign({}, updateMessage), messageType: DwnInterface.RecordsWrite, target: this._connectedDid, store }; if (this._delegateDid) { const { message: delegatedGrant } = yield this._permissionsApi.getPermissionForRequest({ connectedDid: this._connectedDid, delegateDid: this._delegateDid, protocol: this.protocol, delegate: true, cached: true, messageType: requestOptions.messageType }); requestOptions.messageParams.delegatedGrant = delegatedGrant; requestOptions.granteeDid = this._delegateDid; } const agentResponse = yield this._agent.processDwnRequest(requestOptions); const { message, reply: { status } } = agentResponse; const responseMessage = message; if (200 <= status.code && status.code <= 299) { // copy the original raw message to the initial write before we update the values. if (!this._initialWrite) { // If there is no initial write, we need to create one from the current record state. // We checked in the beginning of the function that the rawMessage is a RecordsWrite message. this._initialWrite = Object.assign({}, this.rawMessage); } // Only update the local Record instance mutable properties if the record was successfully (over)written. this._authorization = responseMessage.authorization; this._protocolRole = updateMessage.protocolRole; mutableDescriptorProperties.forEach(property => { this._descriptor[property] = responseMessage.descriptor[property]; }); // Cache data. if (data !== undefined) { this._encodedData = dataBlob; } } return { status }; }); } /** * Delete the current record on the DWN. * @param params - Parameters to delete the record. * @returns the status of the delete request */ delete(deleteParams) { var _a; return __awaiter(this, void 0, void 0, function* () { const { store = true, signAsOwner, dateModified, prune = false } = deleteParams || {}; const signAsOwnerValue = signAsOwner && this._delegateDid === undefined; const signAsOwnerDelegate = signAsOwner && this._delegateDid !== undefined; if (this.deleted && !this._initialWrite) { throw new Error('Record: Record is in an invalid state, initial write is missing.'); } if (!this._initialWrite) { // If there is no initial write, we need to create one from the current record state. // We checked in the beginning of the function that the initialWrite is not set if the rawMessage is a RecordsDelete message. // So we can safely assume that the rawMessage is a RecordsWrite message. this._initialWrite = Object.assign({}, this.rawMessage); } yield this.processInitialWriteIfNeeded({ store, signAsOwner }); // prepare delete options let deleteOptions = { messageType: DwnInterface.RecordsDelete, author: this._connectedDid, target: this._connectedDid, signAsOwner: signAsOwnerValue, signAsOwnerDelegate, store }; // Check to see if the provided protocolRole within the deleteParams is different from the current protocolRole. const differentRole = (deleteParams === null || deleteParams === void 0 ? void 0 : deleteParams.protocolRole) ? getRecordProtocolRole(this.rawMessage) !== deleteParams.protocolRole : false; // If the record is already in a deleted state but the protocolRole is different, we need to construct a delete message with the new protocolRole // otherwise we can just use the existing delete message. if (this.deleted && !differentRole) { deleteOptions.rawMessage = this.rawMessage; } else { // otherwise we construct a delete message given the `RecordDeleteParams` deleteOptions.messageParams = { prune: prune, recordId: this._recordId, messageTimestamp: dateModified, protocolRole: (_a = deleteParams === null || deleteParams === void 0 ? void 0 : deleteParams.protocolRole) !== null && _a !== void 0 ? _a : this._protocolRole // if no protocolRole is provided, use the current protocolRole }; } if (this._delegateDid) { const { message: delegatedGrant } = yield this._permissionsApi.getPermissionForRequest({ connectedDid: this._connectedDid, delegateDid: this._delegateDid, protocol: this.protocol, delegate: true, cached: true, messageType: deleteOptions.messageType }); deleteOptions.messageParams = Object.assign(Object.assign({}, deleteOptions.messageParams), { delegatedGrant }); deleteOptions.granteeDid = this._delegateDid; } const agentResponse = yield this._agent.processDwnRequest(deleteOptions); const { message, reply: { status } } = agentResponse; if (status.code !== 202) { // If the delete was not successful, return the status. return { status }; } // If the delete was successful, update the Record author to the author of the delete message. this._author = getRecordAuthor(message); this._descriptor = message.descriptor; this._authorization = message.authorization; // clear out properties that are not relevant for a deleted record this._encodedData = undefined; this._encryption = undefined; this._attestation = undefined; this._contextId = undefined; return { status }; }); } /** * Process the initial write, if it hasn't already been processed, with the options set for storing and/or signing as the owner. */ processInitialWriteIfNeeded({ store, signAsOwner }) { return __awaiter(this, void 0, void 0, function* () { if (this.initialWrite && ((signAsOwner && !this._initialWriteSigned) || (store && !this._initialWriteStored))) { const signAsOwnerValue = signAsOwner && this._delegateDid === undefined; const signAsOwnerDelegate = signAsOwner && this._delegateDid !== undefined; const initialWriteRequest = { messageType: DwnInterface.RecordsWrite, rawMessage: this.initialWrite, author: this._connectedDid, target: this._connectedDid, signAsOwner: signAsOwnerValue, signAsOwnerDelegate, store, }; if (this._delegateDid) { const { message: delegatedGrant } = yield this._permissionsApi.getPermissionForRequest({ connectedDid: this._connectedDid, delegateDid: this._delegateDid, protocol: this.protocol, delegate: true, cached: true, messageType: initialWriteRequest.messageType }); initialWriteRequest.messageParams = Object.assign(Object.assign({}, initialWriteRequest.messageParams), { delegatedGrant }); initialWriteRequest.granteeDid = this._delegateDid; } // Process the prepared initial write, with the options set for storing and/or signing as the owner. const agentResponse = yield this._agent.processDwnRequest(initialWriteRequest); const { message, reply: { status } } = agentResponse; const responseMessage = message; if (200 <= status.code && status.code <= 299) { if (store) this._initialWriteStored = true; if (signAsOwner) { this._initialWriteSigned = true; this.initialWrite.authorization = responseMessage.authorization; } } } }); } /** * Handles the various conditions around there being an initial write, whether to store initial/current state, * and whether to add an owner signature to the initial write to enable storage when protocol rules require it. */ processRecord({ store, signAsOwner }) { return __awaiter(this, void 0, void 0, function* () { const signAsOwnerValue = signAsOwner && this._delegateDid === undefined; const signAsOwnerDelegate = signAsOwner && this._delegateDid !== undefined; yield this.processInitialWriteIfNeeded({ store, signAsOwner }); let requestOptions; // Now that we've processed a potential initial write, we can process the current record state. // If the record has been deleted, we need to send a delete request. Otherwise, we send a write request. if (this.deleted) { requestOptions = { messageType: DwnInterface.RecordsDelete, rawMessage: this.rawMessage, author: this._connectedDid, target: this._connectedDid, signAsOwner: signAsOwnerValue, signAsOwnerDelegate, store, }; } else { requestOptions = { messageType: DwnInterface.RecordsWrite, rawMessage: this.rawMessage, author: this._connectedDid, target: this._connectedDid, dataStream: yield this.data.blob(), signAsOwner: signAsOwnerValue, signAsOwnerDelegate, store, }; } if (this._delegateDid) { const { message: delegatedGrant } = yield this._permissionsApi.getPermissionForRequest({ connectedDid: this._connectedDid, delegateDid: this._delegateDid, protocol: this.protocol, delegate: true, cached: true, messageType: requestOptions.messageType }); requestOptions.messageParams = Object.assign(Object.assign({}, requestOptions.messageParams), { delegatedGrant }); requestOptions.granteeDid = this._delegateDid; } const agentResponse = yield this._agent.processDwnRequest(requestOptions); const { message, reply: { status } } = agentResponse; const responseMessage = message; if (200 <= status.code && status.code <= 299) { // If we are signing as the owner, make sure to update the current record state's authorization, because now it will have the owner's signature on it. if (signAsOwner) this._authorization = responseMessage.authorization; } return { status }; }); } /** * Fetches the record's data from the specified DWN. * * This private method is called when the record data is not available in-memory * and needs to be fetched from either a local or a remote DWN. * It makes a read request to the specified DWN and processes the response to provide * a Node.js `Readable` stream of the record's data. * * @param params - Parameters for fetching the record's data. * @param params.target - The DID of the DWN to fetch the data from. * @param params.isRemote - Indicates whether the target DWN is a remote node. * @returns A Promise that resolves to a Node.js `Readable` stream of the record's data. * @throws If there is an error while fetching or processing the data from the DWN. * * @beta */ readRecordData({ target, isRemote }) { return __awaiter(this, void 0, void 0, function* () { const readRequest = { author: this._connectedDid, messageParams: { filter: { recordId: this.id }, protocolRole: this._protocolRole }, messageType: DwnInterface.RecordsRead, target, }; if (this._delegateDid) { // 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. // This allows users to read publicly available data without needing explicit grants. // // NOTE: When a read-only Record class is implemented, callers would have that returned instead when they don't have an explicit permission. // This should fail if a permission is not found, although it should not happen in practice. // TODO: https://github.com/TBD54566975/web5-js/issues/898 try { const { message: delegatedGrant } = yield this._permissionsApi.getPermissionForRequest({ connectedDid: this._connectedDid, delegateDid: this._delegateDid, protocol: this.protocol, delegate: true, cached: true, messageType: readRequest.messageType }); readRequest.messageParams = Object.assign(Object.assign({}, readRequest.messageParams), { delegatedGrant }); readRequest.granteeDid = this._delegateDid; } catch (error) { // If there is an error fetching the grant, we will attempt to read the data as the delegate. readRequest.author = this._delegateDid; } } const agentResponsePromise = isRemote ? this._agent.sendDwnRequest(readRequest) : this._agent.processDwnRequest(readRequest); try { const { reply: { status, entry } } = yield agentResponsePromise; if (status.code !== 200) { throw new Error(`${status.code}: ${status.detail}`); } const dataStream = entry.data; // If the data stream is a web ReadableStream, convert it to a Node.js Readable. const nodeReadable = Stream.isReadableStream(dataStream) ? NodeStream.fromWebReadable({ readableStream: dataStream }) : dataStream; return nodeReadable; } catch (error) { throw new Error(`Error encountered while attempting to read data: ${error.message}`); } }); } /** * Verifies if the properties to be mutated are mutable. * * This private method is used to ensure that only mutable properties of the `Record` instance * are being changed. It checks whether the properties specified for mutation are among the * set of properties that are allowed to be modified. If any of the properties to be mutated * are not in the set of mutable properties, the method throws an error. * * @param propertiesToMutate - An iterable of property names that are intended to be mutated. * @param mutableDescriptorProperties - A set of property names that are allowed to be mutated. * * @throws If any of the properties in `propertiesToMutate` are not in `mutableDescriptorProperties`. * * @beta */ static verifyPermittedMutation(propertiesToMutate, mutableDescriptorProperties) { for (const property of propertiesToMutate) { if (!mutableDescriptorProperties.has(property)) { throw new Error(`${property} is an immutable property. Its value cannot be changed.`); } } } /** * Checks if the descriptor is a RecordsDelete descriptor. * * @param descriptor a RecordsWrite or RecordsDelete descriptor */ isRecordsDeleteDescriptor(descriptor) { return descriptor.interface + descriptor.method === DwnInterface.RecordsDelete; } } /** * Cache to minimize the amount of redundant two-phase commits we do in store() and send() * Retains awareness of the last 100 records stored/sent for up to 100 target DIDs each. */ Record._sendCache = SendCache; //# sourceMappingURL=record.js.map