From de784278c9c023bda46c1a50837496049f915be0 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Mon, 21 Oct 2024 16:38:05 -0400 Subject: [PATCH 1/3] records can be updated without being stored --- packages/api/src/record.ts | 6 +- packages/api/tests/record.spec.ts | 152 ++++++++++++++++-------------- 2 files changed, 84 insertions(+), 74 deletions(-) diff --git a/packages/api/src/record.ts b/packages/api/src/record.ts index e93b2463f..5ae0bed0b 100644 --- a/packages/api/src/record.ts +++ b/packages/api/src/record.ts @@ -141,6 +141,9 @@ export type RecordUpdateParams = { */ dataCid?: DwnMessageDescriptor[DwnInterface.RecordsWrite]['dataCid']; + /** Whether or not to store the updated message. */ + store?: boolean; + /** The data format/MIME type of the supplied data */ dataFormat?: string; @@ -706,7 +709,7 @@ export class Record implements RecordModel { * * @beta */ - async update({ dateModified, data, protocolRole, ...params }: RecordUpdateParams): Promise { + async update({ dateModified, data, protocolRole, store = true, ...params }: RecordUpdateParams): Promise { if (this.deleted) { throw new Error('Record: Cannot revive a deleted record.'); @@ -760,6 +763,7 @@ export class Record implements RecordModel { messageParams : { ...updateMessage }, messageType : DwnInterface.RecordsWrite, target : this._connectedDid, + store }; if (this._delegateDid) { diff --git a/packages/api/tests/record.spec.ts b/packages/api/tests/record.spec.ts index 8a14b5bb0..6a7e64d8c 100644 --- a/packages/api/tests/record.spec.ts +++ b/packages/api/tests/record.spec.ts @@ -25,7 +25,7 @@ if (!globalThis.crypto) globalThis.crypto = webcrypto; let testDwnUrls: string[] = [testDwnUrl]; -describe('Record', () => { +describe.only('Record', () => { let dataText: string; let dataBlob: Blob; let dataFormat: string; @@ -2611,58 +2611,6 @@ describe('Record', () => { expect(readResultAfterUpdate.status.code).to.equal(401); }); - it('updates a record locally that only written to a remote DWN', async () => { - // Create a record but do not store it on the local DWN. - const { status, record } = await dwnAlice.records.write({ - store : false, - data : 'Hello, world!', - message : { - schema : 'foo/bar', - dataFormat : 'text/plain' - } - }); - expect(status.code).to.equal(202); - expect(record).to.not.be.undefined; - - // Store the data CID of the record before it is updated. - const dataCidBeforeDataUpdate = record!.dataCid; - - // Write the record to a remote DWN. - const { status: sendStatus } = await record!.send(aliceDid.uri); - expect(sendStatus.code).to.equal(202); - - // fails because record has not been stored in the local dwn yet - let updateResult = await record!.update({ data: 'bye' }); - expect(updateResult.status.code).to.equal(400); - expect(updateResult.status.detail).to.equal('RecordsWriteGetInitialWriteNotFound: Initial write is not found.'); - - const { status: recordStoreStatus }= await record.store(); - expect(recordStoreStatus.code).to.equal(202); - - // now succeeds with the update - updateResult = await record!.update({ data: 'bye' }); - expect(updateResult.status.code).to.equal(202); - - // Confirm that the record was written to the local DWN. - const readResult = await dwnAlice.records.read({ - message: { - filter: { - recordId: record!.id - } - } - }); - expect(readResult.status.code).to.equal(200); - expect(readResult.record).to.not.be.undefined; - - // Confirm that the data CID of the record was updated. - expect(readResult.record.dataCid).to.not.equal(dataCidBeforeDataUpdate); - expect(readResult.record.dataCid).to.equal(record!.dataCid); - - // Confirm that the data payload of the record was modified. - const updatedData = await record!.data.text(); - expect(updatedData).to.equal('bye'); - }); - it('allows to update a record locally that was initially read from a remote DWN if store() is issued', async () => { // Create a record but do not store it on the local DWN. const { status, record } = await dwnAlice.records.write({ @@ -2725,7 +2673,7 @@ describe('Record', () => { expect(readResult.record.dataCid).to.equal(readRecord.dataCid); }); - it('updates a record locally that was initially queried from a remote DWN', async () => { + it('updates a record that was queried from a remote DWN without storing it', async () => { // Create a record but do not store it on the local DWN. const { status, record } = await dwnAlice.records.write({ store : false, @@ -2746,7 +2694,7 @@ describe('Record', () => { expect(sendStatus.code).to.equal(202); // Query the record from the remote DWN. - const queryResult = await dwnAlice.records.query({ + let queryResult = await dwnAlice.records.query({ from : aliceDid.uri, message : { filter: { @@ -2758,37 +2706,95 @@ describe('Record', () => { expect(queryResult.records).to.not.be.undefined; expect(queryResult.records.length).to.equal(1); - // Attempt to update the queried record, which will fail because we haven't stored the queried record locally yet + // Attempt to update the queried record const [ queriedRecord ] = queryResult.records; - let updateResult = await queriedRecord!.update({ data: 'bye' }); - expect(updateResult.status.code).to.equal(400); - expect(updateResult.status.detail).to.equal('RecordsWriteGetInitialWriteNotFound: Initial write is not found.'); - - // store the queried record - const { status: queriedStoreStatus } = await queriedRecord.store(); - expect(queriedStoreStatus.code).to.equal(202); - - updateResult = await queriedRecord!.update({ data: 'bye' }); + let updateResult = await queriedRecord!.update({ data: 'Updated, world!', store: false }); expect(updateResult.status.code).to.equal(202); - // Confirm that the record was written to the local DWN. - const readResult = await dwnAlice.records.read({ + // confirm that the record does not exist locally + queryResult = await dwnAlice.records.read({ message: { filter: { recordId: record!.id } } }); + expect(queryResult.status.code).to.equal(404); + }); + + it('updates a record which has a parent reference from a remote DWN without storing it or its parent', async () => { + // create a parent thread + const { status: threadStatus, record: threadRecord } = await dwnAlice.records.write({ + store : false, + data : 'Hello, world!', + message : { + protocol : protocolDefinition.protocol, + schema : protocolDefinition.types.thread.schema, + protocolPath : 'thread' + } + }); + + expect(threadStatus.code).to.equal(202); + expect(threadRecord).to.not.be.undefined; + + const { status: threadSendStatus } = await threadRecord.send(); + expect(threadSendStatus.code).to.equal(202); + + // create an email with the thread as a parent + const { status: emailStatus, record: emailRecord } = await dwnAlice.records.write({ + store : false, + data : 'Hello, world!', + message : { + parentContextId : threadRecord.contextId, + protocol : protocolDefinition.protocol, + protocolPath : 'thread/email', + schema : protocolDefinition.types.email.schema + } + }); + expect(emailStatus.code).to.equal(202); + expect(emailRecord).to.not.be.undefined; + + const { status: emailSendStatus } = await emailRecord!.send(); + expect(emailSendStatus.code).to.equal(202); + + // update email record + const { status: updateStatus } = await emailRecord!.update({ data: 'updated email record', store: false }); + expect(updateStatus.code).to.equal(202); + + const { status: updateEmailSendStatus } = await emailRecord!.send(); + expect(updateEmailSendStatus.code).to.equal(202); + + let readResult = await dwnAlice.records.read({ + from: aliceDid.uri, + message: { + filter: { + recordId: emailRecord.id + } + } + }); + expect(readResult.status.code).to.equal(200); expect(readResult.record).to.not.be.undefined; + expect(await readResult.record.data.text()).to.equal('updated email record'); - // Confirm that the data CID of the record was updated. - expect(readResult.record.dataCid).to.not.equal(dataCidBeforeDataUpdate); - expect(readResult.record.dataCid).to.equal(queriedRecord!.dataCid); + // confirm that records do not exist locally + readResult = await dwnAlice.records.read({ + message: { + filter: { + recordId: emailRecord.id + } + } + }); + expect(readResult.status.code).to.equal(404); - // Confirm that the data payload of the record was modified. - const updatedData = await queriedRecord!.data.text(); - expect(updatedData).to.equal('bye'); + readResult = await dwnAlice.records.read({ + message: { + filter: { + recordId: threadRecord.id + } + } + }); + expect(readResult.status.code).to.equal(404); }); it('updates a record which has a parent reference', async () => { From dc6ced883475d9d7002ac2b628bd4d1a2277a294 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Mon, 21 Oct 2024 16:40:12 -0400 Subject: [PATCH 2/3] lint and remote exclusive test --- packages/api/tests/record.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/tests/record.spec.ts b/packages/api/tests/record.spec.ts index 6a7e64d8c..e0bd8066f 100644 --- a/packages/api/tests/record.spec.ts +++ b/packages/api/tests/record.spec.ts @@ -25,7 +25,7 @@ if (!globalThis.crypto) globalThis.crypto = webcrypto; let testDwnUrls: string[] = [testDwnUrl]; -describe.only('Record', () => { +describe('Record', () => { let dataText: string; let dataBlob: Blob; let dataFormat: string; @@ -2765,8 +2765,8 @@ describe.only('Record', () => { expect(updateEmailSendStatus.code).to.equal(202); let readResult = await dwnAlice.records.read({ - from: aliceDid.uri, - message: { + from : aliceDid.uri, + message : { filter: { recordId: emailRecord.id } From 1b5b50bda045558577f2ac8a8b3365d7f1960545 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Mon, 21 Oct 2024 16:41:02 -0400 Subject: [PATCH 3/3] add changeset --- .changeset/old-buckets-kiss.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/old-buckets-kiss.md diff --git a/.changeset/old-buckets-kiss.md b/.changeset/old-buckets-kiss.md new file mode 100644 index 000000000..e9fa387eb --- /dev/null +++ b/.changeset/old-buckets-kiss.md @@ -0,0 +1,5 @@ +--- +"@web5/api": patch +--- + +Allow records to be updated without storing.