@enbox/dwn-sdk-js 0.3.6 → 0.3.7

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.
@@ -15,6 +15,7 @@ export declare class RecordsWriteHandler implements MethodHandler {
15
15
  */
16
16
  cloneAndAddEncodedData(message: RecordsWriteMessage, dataBytes: Uint8Array): Promise<RecordsQueryReplyEntry>;
17
17
  private processMessageWithDataStream;
18
+ private existingInitialWriteLacksData;
18
19
  private processMessageWithoutDataStream;
19
20
  /**
20
21
  * Validates the expected `dataCid` and `dataSize` in the descriptor vs the received data.
@@ -1 +1 @@
1
- {"version":3,"file":"records-write.d.ts","sourceRoot":"","sources":["../../../../src/handlers/records-write.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAqB7F,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAC;IAAC,UAAU,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;CAAC,CAAC;AAE5G,qBAAa,mBAAoB,YAAW,aAAa;IAE3C,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,mBAAmB;IAEzC,MAAM,CAAC,EAClB,MAAM,EACN,OAAO,EACP,UAAU,EACX,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAwM7C;;OAEG;IACU,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,UAAU,GAAE,OAAO,CAAC,sBAAsB,CAAC;YAM1G,4BAA4B;YA8C5B,+BAA+B;IAyC7C;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAqBpC;;;;;;OAMG;YACW,qBAAqB;YA4ErB,qBAAqB;CAqCpC"}
1
+ {"version":3,"file":"records-write.d.ts","sourceRoot":"","sources":["../../../../src/handlers/records-write.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAqB7F,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAC;IAAC,UAAU,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;CAAC,CAAC;AAE5G,qBAAa,mBAAoB,YAAW,aAAa;IAE3C,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,mBAAmB;IAEzC,MAAM,CAAC,EAClB,MAAM,EACN,OAAO,EACP,UAAU,EACX,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA+N7C;;OAEG;IACU,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,UAAU,GAAE,OAAO,CAAC,sBAAsB,CAAC;YAM1G,4BAA4B;YA8C5B,6BAA6B;YA2B7B,+BAA+B;IAyC7C;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAqBpC;;;;;;OAMG;YACW,qBAAqB;YA4ErB,qBAAqB;CAqCpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"records-write.spec.d.ts","sourceRoot":"","sources":["../../../../tests/handlers/records-write.spec.ts"],"names":[],"mappings":"AAmDA,wBAAgB,uBAAuB,IAAI,IAAI,CA2/I9C"}
1
+ {"version":3,"file":"records-write.spec.d.ts","sourceRoot":"","sources":["../../../../tests/handlers/records-write.spec.ts"],"names":[],"mappings":"AAmDA,wBAAgB,uBAAuB,IAAI,IAAI,CA6gJ9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enbox/dwn-sdk-js",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "A reference implementation of https://identity.foundation/decentralized-web-node/spec/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -58,6 +58,31 @@ export class RecordsWriteHandler implements MethodHandler {
58
58
  };
59
59
  const { messages: existingMessages } = await this.deps.messageStore.query(tenant, [ query ]);
60
60
 
61
+ // If the exact same message already exists, return 409 immediately.
62
+ // This prevents duplicate delivery races from re-processing large data
63
+ // streams and hitting unique constraints in SQL-backed data stores.
64
+ //
65
+ // Exception: an initial write may have been stored earlier without data
66
+ // (204). A later delivery of the same message with data must be allowed
67
+ // to complete the record.
68
+ const incomingCid = await Message.getCid(message);
69
+ for (const existingMessage of existingMessages) {
70
+ if (await Message.getCid(existingMessage) !== incomingCid) {
71
+ continue;
72
+ }
73
+
74
+ const canCompleteMissingData = await this.existingInitialWriteLacksData(
75
+ tenant,
76
+ existingMessage as RecordsWriteMessage,
77
+ message,
78
+ dataStream !== undefined,
79
+ );
80
+
81
+ if (!canCompleteMissingData) {
82
+ return { status: { code: 409, detail: 'Conflict' } };
83
+ }
84
+ }
85
+
61
86
  // if the incoming write is not the initial write, then it must not modify any immutable properties defined by the initial write
62
87
  const newMessageIsInitialWrite = await recordsWrite.isInitialWrite();
63
88
  let initialWrite: RecordsWriteMessage | undefined;
@@ -101,15 +126,13 @@ export class RecordsWriteHandler implements MethodHandler {
101
126
  // message is an initial write that lacks both inline encodedData and
102
127
  // DataStore data — indicating it was stored without data.
103
128
  let existingLacksData = false;
104
- if (newestExistingMessage !== undefined && dataStream !== undefined) {
105
- const isInitial = await RecordsWrite.isInitialWrite(newestExistingMessage);
106
- if (isInitial) {
107
- const hasInlineData = !!(newestExistingMessage as any).encodedData;
108
- const hasStoredData = this.deps.dataStore
109
- ? !!(await this.deps.dataStore.get(tenant, recordsWrite.message.recordId, message.descriptor.dataCid))
110
- : false;
111
- existingLacksData = !hasInlineData && !hasStoredData;
112
- }
129
+ if (newestExistingMessage) {
130
+ existingLacksData = await this.existingInitialWriteLacksData(
131
+ tenant,
132
+ newestExistingMessage as RecordsWriteMessage,
133
+ message,
134
+ dataStream !== undefined,
135
+ );
113
136
  }
114
137
 
115
138
  if (!existingLacksData) {
@@ -288,6 +311,33 @@ export class RecordsWriteHandler implements MethodHandler {
288
311
  return messageWithOptionalEncodedData;
289
312
  }
290
313
 
314
+ private async existingInitialWriteLacksData(
315
+ tenant: string,
316
+ existingMessage: RecordsWriteMessage,
317
+ incomingMessage: RecordsWriteMessage,
318
+ incomingHasData: boolean,
319
+ ): Promise<boolean> {
320
+ if (!incomingHasData) {
321
+ return false;
322
+ }
323
+
324
+ const isInitial = await RecordsWrite.isInitialWrite(existingMessage);
325
+ if (!isInitial) {
326
+ return false;
327
+ }
328
+
329
+ const hasInlineData = !!(existingMessage as RecordsQueryReplyEntry).encodedData;
330
+ const hasStoredData = this.deps.dataStore
331
+ ? !!(await this.deps.dataStore.get(
332
+ tenant,
333
+ existingMessage.recordId,
334
+ incomingMessage.descriptor.dataCid,
335
+ ))
336
+ : false;
337
+
338
+ return !hasInlineData && !hasStoredData;
339
+ }
340
+
291
341
  private async processMessageWithoutDataStream(
292
342
  tenant: string,
293
343
  message: RecordsWriteMessage,