@open-aih/payload-ddb 3.1.0 → 4.1.0

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 (162) hide show
  1. package/README.md +141 -18
  2. package/dist/count.d.ts.map +1 -1
  3. package/dist/count.js +4 -2
  4. package/dist/count.js.map +1 -1
  5. package/dist/countGlobalVersions.d.ts.map +1 -1
  6. package/dist/countGlobalVersions.js +6 -1
  7. package/dist/countGlobalVersions.js.map +1 -1
  8. package/dist/countVersions.d.ts.map +1 -1
  9. package/dist/countVersions.js +6 -1
  10. package/dist/countVersions.js.map +1 -1
  11. package/dist/create.d.ts.map +1 -1
  12. package/dist/create.js +31 -15
  13. package/dist/create.js.map +1 -1
  14. package/dist/createGlobal.d.ts.map +1 -1
  15. package/dist/createGlobal.js +2 -14
  16. package/dist/createGlobal.js.map +1 -1
  17. package/dist/createGlobalVersion.d.ts +1 -1
  18. package/dist/createGlobalVersion.d.ts.map +1 -1
  19. package/dist/createGlobalVersion.js +3 -41
  20. package/dist/createGlobalVersion.js.map +1 -1
  21. package/dist/createVersion.d.ts +6 -5
  22. package/dist/createVersion.d.ts.map +1 -1
  23. package/dist/createVersion.js +8 -48
  24. package/dist/createVersion.js.map +1 -1
  25. package/dist/deleteMany.d.ts +1 -6
  26. package/dist/deleteMany.d.ts.map +1 -1
  27. package/dist/deleteMany.js +8 -19
  28. package/dist/deleteMany.js.map +1 -1
  29. package/dist/deleteOne.d.ts.map +1 -1
  30. package/dist/deleteOne.js +20 -24
  31. package/dist/deleteOne.js.map +1 -1
  32. package/dist/deleteVersions.d.ts.map +1 -1
  33. package/dist/deleteVersions.js +8 -14
  34. package/dist/deleteVersions.js.map +1 -1
  35. package/dist/find.d.ts.map +1 -1
  36. package/dist/find.js +15 -2
  37. package/dist/find.js.map +1 -1
  38. package/dist/findDistinct.d.ts.map +1 -1
  39. package/dist/findDistinct.js +3 -1
  40. package/dist/findDistinct.js.map +1 -1
  41. package/dist/findGlobal.d.ts.map +1 -1
  42. package/dist/findGlobal.js +3 -2
  43. package/dist/findGlobal.js.map +1 -1
  44. package/dist/findGlobalVersions.d.ts.map +1 -1
  45. package/dist/findGlobalVersions.js +10 -2
  46. package/dist/findGlobalVersions.js.map +1 -1
  47. package/dist/findOne.d.ts.map +1 -1
  48. package/dist/findOne.js +17 -6
  49. package/dist/findOne.js.map +1 -1
  50. package/dist/findVersions.d.ts.map +1 -1
  51. package/dist/findVersions.js +10 -2
  52. package/dist/findVersions.js.map +1 -1
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +7 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/queryDrafts.d.ts.map +1 -1
  58. package/dist/queryDrafts.js +24 -4
  59. package/dist/queryDrafts.js.map +1 -1
  60. package/dist/updateGlobal.d.ts +5 -4
  61. package/dist/updateGlobal.d.ts.map +1 -1
  62. package/dist/updateGlobal.js +22 -36
  63. package/dist/updateGlobal.js.map +1 -1
  64. package/dist/updateGlobalVersion.d.ts.map +1 -1
  65. package/dist/updateGlobalVersion.js +3 -49
  66. package/dist/updateGlobalVersion.js.map +1 -1
  67. package/dist/updateJobs.d.ts +21 -0
  68. package/dist/updateJobs.d.ts.map +1 -0
  69. package/dist/updateJobs.js +83 -0
  70. package/dist/updateJobs.js.map +1 -0
  71. package/dist/updateMany.d.ts +9 -3
  72. package/dist/updateMany.d.ts.map +1 -1
  73. package/dist/updateMany.js +47 -30
  74. package/dist/updateMany.js.map +1 -1
  75. package/dist/updateOne.d.ts +6 -1
  76. package/dist/updateOne.d.ts.map +1 -1
  77. package/dist/updateOne.js +49 -49
  78. package/dist/updateOne.js.map +1 -1
  79. package/dist/updateVersion.d.ts.map +1 -1
  80. package/dist/updateVersion.js +3 -52
  81. package/dist/updateVersion.js.map +1 -1
  82. package/dist/upsert.d.ts +6 -0
  83. package/dist/upsert.d.ts.map +1 -1
  84. package/dist/upsert.js +57 -41
  85. package/dist/upsert.js.map +1 -1
  86. package/dist/utilities/backfillUniqueGuards.d.ts +42 -0
  87. package/dist/utilities/backfillUniqueGuards.d.ts.map +1 -0
  88. package/dist/utilities/backfillUniqueGuards.js +39 -0
  89. package/dist/utilities/backfillUniqueGuards.js.map +1 -0
  90. package/dist/utilities/buildFilterExpression.d.ts +29 -14
  91. package/dist/utilities/buildFilterExpression.d.ts.map +1 -1
  92. package/dist/utilities/buildFilterExpression.js +55 -14
  93. package/dist/utilities/buildFilterExpression.js.map +1 -1
  94. package/dist/utilities/castWhere.d.ts +84 -0
  95. package/dist/utilities/castWhere.d.ts.map +1 -0
  96. package/dist/utilities/castWhere.js +254 -0
  97. package/dist/utilities/castWhere.js.map +1 -0
  98. package/dist/utilities/findFirst.d.ts +3 -2
  99. package/dist/utilities/findFirst.d.ts.map +1 -1
  100. package/dist/utilities/findFirst.js +14 -46
  101. package/dist/utilities/findFirst.js.map +1 -1
  102. package/dist/utilities/geo.d.ts +36 -0
  103. package/dist/utilities/geo.d.ts.map +1 -0
  104. package/dist/utilities/geo.js +108 -0
  105. package/dist/utilities/geo.js.map +1 -0
  106. package/dist/utilities/mapWithConcurrency.d.ts +12 -0
  107. package/dist/utilities/mapWithConcurrency.d.ts.map +1 -0
  108. package/dist/utilities/mapWithConcurrency.js +32 -0
  109. package/dist/utilities/mapWithConcurrency.js.map +1 -0
  110. package/dist/utilities/matchesWhere.d.ts +25 -5
  111. package/dist/utilities/matchesWhere.d.ts.map +1 -1
  112. package/dist/utilities/matchesWhere.js +150 -38
  113. package/dist/utilities/matchesWhere.js.map +1 -1
  114. package/dist/utilities/operators.d.ts +22 -12
  115. package/dist/utilities/operators.d.ts.map +1 -1
  116. package/dist/utilities/operators.js +36 -31
  117. package/dist/utilities/operators.js.map +1 -1
  118. package/dist/utilities/optimistic.d.ts +29 -0
  119. package/dist/utilities/optimistic.d.ts.map +1 -0
  120. package/dist/utilities/optimistic.js +58 -0
  121. package/dist/utilities/optimistic.js.map +1 -0
  122. package/dist/utilities/prepareWhere.d.ts +63 -0
  123. package/dist/utilities/prepareWhere.d.ts.map +1 -0
  124. package/dist/utilities/prepareWhere.js +198 -0
  125. package/dist/utilities/prepareWhere.js.map +1 -0
  126. package/dist/utilities/queryCount.d.ts +6 -3
  127. package/dist/utilities/queryCount.d.ts.map +1 -1
  128. package/dist/utilities/queryCount.js +16 -45
  129. package/dist/utilities/queryCount.js.map +1 -1
  130. package/dist/utilities/queryMatching.d.ts +8 -7
  131. package/dist/utilities/queryMatching.d.ts.map +1 -1
  132. package/dist/utilities/queryMatching.js +17 -54
  133. package/dist/utilities/queryMatching.js.map +1 -1
  134. package/dist/utilities/queryPartition.d.ts +26 -0
  135. package/dist/utilities/queryPartition.d.ts.map +1 -0
  136. package/dist/utilities/queryPartition.js +55 -0
  137. package/dist/utilities/queryPartition.js.map +1 -0
  138. package/dist/utilities/resolveJoins.d.ts +37 -0
  139. package/dist/utilities/resolveJoins.d.ts.map +1 -0
  140. package/dist/utilities/resolveJoins.js +195 -0
  141. package/dist/utilities/resolveJoins.js.map +1 -0
  142. package/dist/utilities/stripInternalKeys.d.ts +17 -3
  143. package/dist/utilities/stripInternalKeys.d.ts.map +1 -1
  144. package/dist/utilities/stripInternalKeys.js +25 -4
  145. package/dist/utilities/stripInternalKeys.js.map +1 -1
  146. package/dist/utilities/uniqueGuards.d.ts +39 -0
  147. package/dist/utilities/uniqueGuards.d.ts.map +1 -0
  148. package/dist/utilities/uniqueGuards.js +106 -0
  149. package/dist/utilities/uniqueGuards.js.map +1 -0
  150. package/dist/utilities/updateVersionRow.d.ts +19 -0
  151. package/dist/utilities/updateVersionRow.d.ts.map +1 -0
  152. package/dist/utilities/updateVersionRow.js +40 -0
  153. package/dist/utilities/updateVersionRow.js.map +1 -0
  154. package/dist/utilities/writeErrors.d.ts +11 -0
  155. package/dist/utilities/writeErrors.d.ts.map +1 -0
  156. package/dist/utilities/writeErrors.js +27 -0
  157. package/dist/utilities/writeErrors.js.map +1 -0
  158. package/dist/utilities/writeItems.d.ts +124 -0
  159. package/dist/utilities/writeItems.d.ts.map +1 -0
  160. package/dist/utilities/writeItems.js +383 -0
  161. package/dist/utilities/writeItems.js.map +1 -0
  162. package/package.json +21 -14
@@ -1,10 +1,16 @@
1
1
  import type { UpdateMany } from 'payload';
2
2
  /**
3
3
  * v1 strategy: query-and-collect matches, sort, slice to `limit`, then
4
- * read-merge-write each in parallel. Same merge semantics as `updateOne`:
5
- * `data` is overlaid on the target and `id` is preserved from the target.
4
+ * read-merge-write each, at most `WRITE_CONCURRENCY` at a time. Same merge
5
+ * semantics as `updateOne`: `data` is overlaid on the target and `id` is
6
+ * preserved from the target.
6
7
  *
7
- * `Promise.all` is order-preserving, so the returned array matches the
8
+ * Each write is conditional on its row being unchanged since it was read. A
9
+ * row that changed underneath is re-read and re-checked against `where`: if
10
+ * it still matches, the patch is re-applied to the fresh row; if it no longer
11
+ * matches (or is gone), it is skipped and left out of the result.
12
+ *
13
+ * `mapWithConcurrency` is order-preserving, so the returned array matches the
8
14
  * sorted target order. Bulk DynamoDB transact-writes (`TransactWriteItems`,
9
15
  * 100-item / 4 MB cap) are the natural next optimization but require the
10
16
  * transaction wiring we deliberately stubbed out for now.
@@ -1 +1 @@
1
- {"version":3,"file":"updateMany.d.ts","sourceRoot":"","sources":["../src/updateMany.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAWzC;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,EAAE,UA2CxB,CAAA"}
1
+ {"version":3,"file":"updateMany.d.ts","sourceRoot":"","sources":["../src/updateMany.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAczC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,EAAE,UA2CxB,CAAA"}
@@ -1,48 +1,65 @@
1
- import { PutCommand } from '@aws-sdk/lib-dynamodb';
2
1
  import { applySorts } from './utilities/applySorts.js';
3
- import { normalizeForDynamo } from './utilities/normalizeForDynamo.js';
2
+ import { mapWithConcurrency } from './utilities/mapWithConcurrency.js';
3
+ import { matchesWhere } from './utilities/matchesWhere.js';
4
+ import { getById, writeWithRetry } from './utilities/optimistic.js';
5
+ import { prepareCollectionWhere, prepareSort } from './utilities/prepareWhere.js';
4
6
  import { queryMatching } from './utilities/queryMatching.js';
5
7
  import { projectForCollection } from './utilities/resolveSchema.js';
8
+ import { rethrowUniqueViolation } from './utilities/writeErrors.js';
9
+ import { putItem, WRITE_CONCURRENCY } from './utilities/writeItems.js';
6
10
  /**
7
11
  * v1 strategy: query-and-collect matches, sort, slice to `limit`, then
8
- * read-merge-write each in parallel. Same merge semantics as `updateOne`:
9
- * `data` is overlaid on the target and `id` is preserved from the target.
12
+ * read-merge-write each, at most `WRITE_CONCURRENCY` at a time. Same merge
13
+ * semantics as `updateOne`: `data` is overlaid on the target and `id` is
14
+ * preserved from the target.
10
15
  *
11
- * `Promise.all` is order-preserving, so the returned array matches the
16
+ * Each write is conditional on its row being unchanged since it was read. A
17
+ * row that changed underneath is re-read and re-checked against `where`: if
18
+ * it still matches, the patch is re-applied to the fresh row; if it no longer
19
+ * matches (or is gone), it is skipped and left out of the result.
20
+ *
21
+ * `mapWithConcurrency` is order-preserving, so the returned array matches the
12
22
  * sorted target order. Bulk DynamoDB transact-writes (`TransactWriteItems`,
13
23
  * 100-item / 4 MB cap) are the natural next optimization but require the
14
24
  * transaction wiring we deliberately stubbed out for now.
15
- */ export const updateMany = async function updateMany({ collection, data, limit, returning, sort, where }) {
16
- const docClient = this.docClient;
17
- if (!docClient) {
18
- throw new Error('payload-ddb: docClient is not initialized — call connect() first.');
19
- }
25
+ */ export const updateMany = async function updateMany({ collection, data, limit, locale, req, returning, sort, where: rawWhere }) {
26
+ const where = await prepareCollectionWhere(this, collection, rawWhere, locale);
20
27
  const partition = this.resolvePartition(collection);
21
28
  const matched = await queryMatching(this, partition, where);
22
- applySorts(matched, sort);
29
+ applySorts(matched, prepareSort(this, {
30
+ kind: 'collection',
31
+ slug: collection
32
+ }, sort, locale));
23
33
  const targets = limit && limit > 0 ? matched.slice(0, limit) : matched;
24
34
  // Hoisted so every row in the batch shares one timestamp — keeps audit-style
25
35
  // queries deterministic. Explicit `updatedAt` in `data` still wins.
26
36
  const updatedAt = data['updatedAt'] ?? new Date().toISOString();
27
- const updated = await Promise.all(targets.map(async (target)=>{
28
- const merged = {
29
- ...target,
30
- ...data,
31
- id: target['id'],
32
- updatedAt
33
- };
34
- const projected = projectForCollection(this, collection, merged);
35
- await docClient.send(new PutCommand({
36
- TableName: this.tableName,
37
- Item: normalizeForDynamo({
38
- ...projected,
39
- pk: partition,
40
- sk: String(projected['id'])
41
- })
42
- }));
43
- return projected;
44
- }));
45
- return returning === false ? null : updated;
37
+ let updated;
38
+ try {
39
+ updated = await mapWithConcurrency(targets, WRITE_CONCURRENCY, (target)=>writeWithRetry(target, async ()=>{
40
+ const fresh = await getById(this, partition, target['id']);
41
+ return fresh && matchesWhere(fresh, where) ? fresh : null;
42
+ }, async (current)=>{
43
+ const merged = {
44
+ ...current,
45
+ ...data,
46
+ id: current['id'],
47
+ updatedAt
48
+ };
49
+ const projected = projectForCollection(this, collection, merged);
50
+ await putItem(this, partition, projected, {
51
+ replaces: current,
52
+ unique: {
53
+ collection
54
+ }
55
+ });
56
+ return projected;
57
+ }));
58
+ } catch (err) {
59
+ rethrowUniqueViolation(err, collection, req);
60
+ throw err;
61
+ }
62
+ return returning === false ? null : updated.filter((doc)=>doc !== null);
46
63
  };
47
64
 
48
65
  //# sourceMappingURL=updateMany.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/updateMany.ts"],"sourcesContent":["import type { UpdateMany } from 'payload'\n\nimport { PutCommand } from '@aws-sdk/lib-dynamodb'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { applySorts } from './utilities/applySorts.js'\nimport { normalizeForDynamo } from './utilities/normalizeForDynamo.js'\nimport { queryMatching } from './utilities/queryMatching.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\n\n/**\n * v1 strategy: query-and-collect matches, sort, slice to `limit`, then\n * read-merge-write each in parallel. Same merge semantics as `updateOne`:\n * `data` is overlaid on the target and `id` is preserved from the target.\n *\n * `Promise.all` is order-preserving, so the returned array matches the\n * sorted target order. Bulk DynamoDB transact-writes (`TransactWriteItems`,\n * 100-item / 4 MB cap) are the natural next optimization but require the\n * transaction wiring we deliberately stubbed out for now.\n */\nexport const updateMany: UpdateMany = async function updateMany(\n this: DynamoAdapter,\n { collection, data, limit, returning, sort, where },\n) {\n const docClient = this.docClient\n if (!docClient) {\n throw new Error('payload-ddb: docClient is not initialized — call connect() first.')\n }\n\n const partition = this.resolvePartition(collection)\n const matched = await queryMatching(this, partition, where)\n applySorts(matched, sort)\n\n const targets = limit && limit > 0 ? matched.slice(0, limit) : matched\n\n // Hoisted so every row in the batch shares one timestamp — keeps audit-style\n // queries deterministic. Explicit `updatedAt` in `data` still wins.\n const updatedAt = data['updatedAt'] ?? new Date().toISOString()\n\n const updated = await Promise.all(\n targets.map(async (target) => {\n const merged: Record<string, unknown> = {\n ...target,\n ...data,\n id: target['id'],\n updatedAt,\n }\n const projected = projectForCollection(this, collection, merged)\n await docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: normalizeForDynamo({\n ...projected,\n pk: partition,\n sk: String(projected['id']),\n }),\n }),\n )\n return projected\n }),\n )\n\n return returning === false ? null : updated\n}\n"],"names":["PutCommand","applySorts","normalizeForDynamo","queryMatching","projectForCollection","updateMany","collection","data","limit","returning","sort","where","docClient","Error","partition","resolvePartition","matched","targets","slice","updatedAt","Date","toISOString","updated","Promise","all","map","target","merged","id","projected","send","TableName","tableName","Item","pk","sk","String"],"mappings":"AAEA,SAASA,UAAU,QAAQ,wBAAuB;AAIlD,SAASC,UAAU,QAAQ,4BAA2B;AACtD,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,aAAa,QAAQ,+BAA8B;AAC5D,SAASC,oBAAoB,QAAQ,+BAA8B;AAEnE;;;;;;;;;CASC,GACD,OAAO,MAAMC,aAAyB,eAAeA,WAEnD,EAAEC,UAAU,EAAEC,IAAI,EAAEC,KAAK,EAAEC,SAAS,EAAEC,IAAI,EAAEC,KAAK,EAAE;IAEnD,MAAMC,YAAY,IAAI,CAACA,SAAS;IAChC,IAAI,CAACA,WAAW;QACd,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,YAAY,IAAI,CAACC,gBAAgB,CAACT;IACxC,MAAMU,UAAU,MAAMb,cAAc,IAAI,EAAEW,WAAWH;IACrDV,WAAWe,SAASN;IAEpB,MAAMO,UAAUT,SAASA,QAAQ,IAAIQ,QAAQE,KAAK,CAAC,GAAGV,SAASQ;IAE/D,6EAA6E;IAC7E,oEAAoE;IACpE,MAAMG,YAAYZ,IAAI,CAAC,YAAY,IAAI,IAAIa,OAAOC,WAAW;IAE7D,MAAMC,UAAU,MAAMC,QAAQC,GAAG,CAC/BP,QAAQQ,GAAG,CAAC,OAAOC;QACjB,MAAMC,SAAkC;YACtC,GAAGD,MAAM;YACT,GAAGnB,IAAI;YACPqB,IAAIF,MAAM,CAAC,KAAK;YAChBP;QACF;QACA,MAAMU,YAAYzB,qBAAqB,IAAI,EAAEE,YAAYqB;QACzD,MAAMf,UAAUkB,IAAI,CAClB,IAAI9B,WAAW;YACb+B,WAAW,IAAI,CAACC,SAAS;YACzBC,MAAM/B,mBAAmB;gBACvB,GAAG2B,SAAS;gBACZK,IAAIpB;gBACJqB,IAAIC,OAAOP,SAAS,CAAC,KAAK;YAC5B;QACF;QAEF,OAAOA;IACT;IAGF,OAAOpB,cAAc,QAAQ,OAAOa;AACtC,EAAC"}
1
+ {"version":3,"sources":["../src/updateMany.ts"],"sourcesContent":["import type { UpdateMany } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { applySorts } from './utilities/applySorts.js'\nimport { mapWithConcurrency } from './utilities/mapWithConcurrency.js'\nimport { matchesWhere } from './utilities/matchesWhere.js'\nimport { getById, writeWithRetry } from './utilities/optimistic.js'\nimport { prepareCollectionWhere, prepareSort } from './utilities/prepareWhere.js'\nimport { queryMatching } from './utilities/queryMatching.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\nimport { rethrowUniqueViolation } from './utilities/writeErrors.js'\nimport { putItem, WRITE_CONCURRENCY } from './utilities/writeItems.js'\n\n/**\n * v1 strategy: query-and-collect matches, sort, slice to `limit`, then\n * read-merge-write each, at most `WRITE_CONCURRENCY` at a time. Same merge\n * semantics as `updateOne`: `data` is overlaid on the target and `id` is\n * preserved from the target.\n *\n * Each write is conditional on its row being unchanged since it was read. A\n * row that changed underneath is re-read and re-checked against `where`: if\n * it still matches, the patch is re-applied to the fresh row; if it no longer\n * matches (or is gone), it is skipped and left out of the result.\n *\n * `mapWithConcurrency` is order-preserving, so the returned array matches the\n * sorted target order. Bulk DynamoDB transact-writes (`TransactWriteItems`,\n * 100-item / 4 MB cap) are the natural next optimization but require the\n * transaction wiring we deliberately stubbed out for now.\n */\nexport const updateMany: UpdateMany = async function updateMany(\n this: DynamoAdapter,\n { collection, data, limit, locale, req, returning, sort, where: rawWhere },\n) {\n const where = await prepareCollectionWhere(this, collection, rawWhere, locale)\n const partition = this.resolvePartition(collection)\n const matched = await queryMatching(this, partition, where)\n applySorts(matched, prepareSort(this, { kind: 'collection', slug: collection }, sort, locale))\n\n const targets = limit && limit > 0 ? matched.slice(0, limit) : matched\n\n // Hoisted so every row in the batch shares one timestamp — keeps audit-style\n // queries deterministic. Explicit `updatedAt` in `data` still wins.\n const updatedAt = data['updatedAt'] ?? new Date().toISOString()\n\n let updated: (null | Record<string, unknown>)[]\n try {\n updated = await mapWithConcurrency(targets, WRITE_CONCURRENCY, (target) =>\n writeWithRetry(\n target,\n async () => {\n const fresh = await getById(this, partition, target['id'])\n return fresh && matchesWhere(fresh, where) ? fresh : null\n },\n async (current) => {\n const merged: Record<string, unknown> = {\n ...current,\n ...data,\n id: current['id'],\n updatedAt,\n }\n const projected = projectForCollection(this, collection, merged)\n await putItem(this, partition, projected, { replaces: current, unique: { collection } })\n return projected\n },\n ),\n )\n } catch (err) {\n rethrowUniqueViolation(err, collection, req)\n throw err\n }\n\n return returning === false ? null : updated.filter((doc) => doc !== null)\n}\n"],"names":["applySorts","mapWithConcurrency","matchesWhere","getById","writeWithRetry","prepareCollectionWhere","prepareSort","queryMatching","projectForCollection","rethrowUniqueViolation","putItem","WRITE_CONCURRENCY","updateMany","collection","data","limit","locale","req","returning","sort","where","rawWhere","partition","resolvePartition","matched","kind","slug","targets","slice","updatedAt","Date","toISOString","updated","target","fresh","current","merged","id","projected","replaces","unique","err","filter","doc"],"mappings":"AAIA,SAASA,UAAU,QAAQ,4BAA2B;AACtD,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,YAAY,QAAQ,8BAA6B;AAC1D,SAASC,OAAO,EAAEC,cAAc,QAAQ,4BAA2B;AACnE,SAASC,sBAAsB,EAAEC,WAAW,QAAQ,8BAA6B;AACjF,SAASC,aAAa,QAAQ,+BAA8B;AAC5D,SAASC,oBAAoB,QAAQ,+BAA8B;AACnE,SAASC,sBAAsB,QAAQ,6BAA4B;AACnE,SAASC,OAAO,EAAEC,iBAAiB,QAAQ,4BAA2B;AAEtE;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,aAAyB,eAAeA,WAEnD,EAAEC,UAAU,EAAEC,IAAI,EAAEC,KAAK,EAAEC,MAAM,EAAEC,GAAG,EAAEC,SAAS,EAAEC,IAAI,EAAEC,OAAOC,QAAQ,EAAE;IAE1E,MAAMD,QAAQ,MAAMf,uBAAuB,IAAI,EAAEQ,YAAYQ,UAAUL;IACvE,MAAMM,YAAY,IAAI,CAACC,gBAAgB,CAACV;IACxC,MAAMW,UAAU,MAAMjB,cAAc,IAAI,EAAEe,WAAWF;IACrDpB,WAAWwB,SAASlB,YAAY,IAAI,EAAE;QAAEmB,MAAM;QAAcC,MAAMb;IAAW,GAAGM,MAAMH;IAEtF,MAAMW,UAAUZ,SAASA,QAAQ,IAAIS,QAAQI,KAAK,CAAC,GAAGb,SAASS;IAE/D,6EAA6E;IAC7E,oEAAoE;IACpE,MAAMK,YAAYf,IAAI,CAAC,YAAY,IAAI,IAAIgB,OAAOC,WAAW;IAE7D,IAAIC;IACJ,IAAI;QACFA,UAAU,MAAM/B,mBAAmB0B,SAAShB,mBAAmB,CAACsB,SAC9D7B,eACE6B,QACA;gBACE,MAAMC,QAAQ,MAAM/B,QAAQ,IAAI,EAAEmB,WAAWW,MAAM,CAAC,KAAK;gBACzD,OAAOC,SAAShC,aAAagC,OAAOd,SAASc,QAAQ;YACvD,GACA,OAAOC;gBACL,MAAMC,SAAkC;oBACtC,GAAGD,OAAO;oBACV,GAAGrB,IAAI;oBACPuB,IAAIF,OAAO,CAAC,KAAK;oBACjBN;gBACF;gBACA,MAAMS,YAAY9B,qBAAqB,IAAI,EAAEK,YAAYuB;gBACzD,MAAM1B,QAAQ,IAAI,EAAEY,WAAWgB,WAAW;oBAAEC,UAAUJ;oBAASK,QAAQ;wBAAE3B;oBAAW;gBAAE;gBACtF,OAAOyB;YACT;IAGN,EAAE,OAAOG,KAAK;QACZhC,uBAAuBgC,KAAK5B,YAAYI;QACxC,MAAMwB;IACR;IAEA,OAAOvB,cAAc,QAAQ,OAAOc,QAAQU,MAAM,CAAC,CAACC,MAAQA,QAAQ;AACtE,EAAC"}
@@ -4,7 +4,12 @@ import type { UpdateOne } from 'payload';
4
4
  * Payload sends a partial `data` patch; DynamoDB `UpdateItem` would let us
5
5
  * push the diff server-side, but its expression syntax (reserved words,
6
6
  * nested attribute paths, list indexing, removal semantics) is enough work to
7
- * earn its own milestone. Two round-trips is acceptable for now.
7
+ * earn its own milestone.
8
+ *
9
+ * The write is conditional on the row still being at the revision that was
10
+ * read. If another writer got in first, the patch is re-applied to the fresh
11
+ * row (`writeWithRetry`), so concurrent updates to different fields both
12
+ * land.
8
13
  *
9
14
  * `id` on the resulting item always comes from the located target — we don't
10
15
  * let `data` rewrite the primary key, which would silently orphan the row.
@@ -1 +1 @@
1
- {"version":3,"file":"updateOne.d.ts","sourceRoot":"","sources":["../src/updateOne.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAWxC;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,EAAE,SAwDvB,CAAA"}
1
+ {"version":3,"file":"updateOne.d.ts","sourceRoot":"","sources":["../src/updateOne.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAWxC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,EAAE,SA0CvB,CAAA"}
package/dist/updateOne.js CHANGED
@@ -1,68 +1,68 @@
1
- import { GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb';
2
1
  import { findFirst } from './utilities/findFirst.js';
3
- import { normalizeForDynamo } from './utilities/normalizeForDynamo.js';
2
+ import { getById, writeWithRetry } from './utilities/optimistic.js';
3
+ import { prepareCollectionWhere } from './utilities/prepareWhere.js';
4
4
  import { projectForCollection } from './utilities/resolveSchema.js';
5
- import { stripInternalKeys } from './utilities/stripInternalKeys.js';
5
+ import { rethrowUniqueViolation } from './utilities/writeErrors.js';
6
+ import { putItem } from './utilities/writeItems.js';
6
7
  /**
7
8
  * v1 strategy: read-merge-write rather than building an `UpdateExpression`.
8
9
  * Payload sends a partial `data` patch; DynamoDB `UpdateItem` would let us
9
10
  * push the diff server-side, but its expression syntax (reserved words,
10
11
  * nested attribute paths, list indexing, removal semantics) is enough work to
11
- * earn its own milestone. Two round-trips is acceptable for now.
12
+ * earn its own milestone.
13
+ *
14
+ * The write is conditional on the row still being at the revision that was
15
+ * read. If another writer got in first, the patch is re-applied to the fresh
16
+ * row (`writeWithRetry`), so concurrent updates to different fields both
17
+ * land.
12
18
  *
13
19
  * `id` on the resulting item always comes from the located target — we don't
14
20
  * let `data` rewrite the primary key, which would silently orphan the row.
15
21
  */ export const updateOne = async function updateOne(args) {
16
- const docClient = this.docClient;
17
- if (!docClient) {
18
- throw new Error('payload-ddb: docClient is not initialized — call connect() first.');
19
- }
20
- const partition = this.resolvePartition(args.collection);
21
- let target = null;
22
- if (args.id !== undefined && args.id !== null) {
23
- const result = await docClient.send(new GetCommand({
24
- TableName: this.tableName,
25
- Key: {
26
- pk: partition,
27
- sk: String(args.id)
28
- },
29
- ConsistentRead: true
30
- }));
31
- target = result.Item ? stripInternalKeys(result.Item) : null;
32
- } else if (args.where) {
33
- target = await findFirst(this, {
22
+ const { collection, data } = args;
23
+ const partition = this.resolvePartition(collection);
24
+ const byId = args.id !== undefined && args.id !== null;
25
+ const where = byId ? undefined : await prepareCollectionWhere(this, collection, args.where, args.locale);
26
+ const read = ()=>{
27
+ if (byId) return getById(this, partition, args.id);
28
+ return where ? findFirst(this, {
34
29
  partition,
35
- where: args.where
30
+ where
31
+ }) : Promise.resolve(null);
32
+ };
33
+ let updated;
34
+ try {
35
+ updated = await writeWithRetry(await read(), read, async (target)=>{
36
+ const merged = {
37
+ ...target,
38
+ ...data,
39
+ id: target['id'],
40
+ // Bump `updatedAt` automatically. Explicit timestamps in `data` (e.g.
41
+ // from version restores or migrations) still win because they land in
42
+ // the spread above and we only override here when `data` didn't
43
+ // supply one.
44
+ updatedAt: data['updatedAt'] ?? new Date().toISOString()
45
+ };
46
+ // Strip the merged result against the collection schema. Catches both
47
+ // unknown keys in the incoming patch and any pre-existing leaked keys
48
+ // on the persisted target — every update doubles as incremental cleanup.
49
+ const projected = projectForCollection(this, collection, merged);
50
+ await putItem(this, partition, projected, {
51
+ replaces: target,
52
+ unique: {
53
+ collection
54
+ }
55
+ });
56
+ return projected;
36
57
  });
58
+ } catch (err) {
59
+ rethrowUniqueViolation(err, collection, args.req);
60
+ throw err;
37
61
  }
38
- if (!target) {
39
- return null;
40
- }
41
- const merged = {
42
- ...target,
43
- ...args.data,
44
- id: target['id'],
45
- // Bump `updatedAt` automatically. Explicit timestamps in `data` (e.g. from
46
- // version restores or migrations) still win because they land in the
47
- // spread above and we only override here when `data` didn't supply one.
48
- updatedAt: args.data['updatedAt'] ?? new Date().toISOString()
49
- };
50
- // Strip the merged result against the collection schema. Catches both
51
- // unknown keys in the incoming patch and any pre-existing leaked keys on
52
- // the persisted target — every update doubles as incremental cleanup.
53
- const projected = projectForCollection(this, args.collection, merged);
54
- await docClient.send(new PutCommand({
55
- TableName: this.tableName,
56
- Item: normalizeForDynamo({
57
- ...projected,
58
- pk: partition,
59
- sk: String(projected['id'])
60
- })
61
- }));
62
- if (args.returning === false) {
62
+ if (!updated || args.returning === false) {
63
63
  return null;
64
64
  }
65
- return projected;
65
+ return updated;
66
66
  };
67
67
 
68
68
  //# sourceMappingURL=updateOne.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/updateOne.ts"],"sourcesContent":["import type { UpdateOne } from 'payload'\n\nimport { GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { normalizeForDynamo } from './utilities/normalizeForDynamo.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\nimport { stripInternalKeys } from './utilities/stripInternalKeys.js'\n\n/**\n * v1 strategy: read-merge-write rather than building an `UpdateExpression`.\n * Payload sends a partial `data` patch; DynamoDB `UpdateItem` would let us\n * push the diff server-side, but its expression syntax (reserved words,\n * nested attribute paths, list indexing, removal semantics) is enough work to\n * earn its own milestone. Two round-trips is acceptable for now.\n *\n * `id` on the resulting item always comes from the located target — we don't\n * let `data` rewrite the primary key, which would silently orphan the row.\n */\nexport const updateOne: UpdateOne = async function updateOne(this: DynamoAdapter, args) {\n const docClient = this.docClient\n if (!docClient) {\n throw new Error('payload-ddb: docClient is not initialized — call connect() first.')\n }\n\n const partition = this.resolvePartition(args.collection)\n\n let target: null | Record<string, unknown> = null\n\n if (args.id !== undefined && args.id !== null) {\n const result = await docClient.send(\n new GetCommand({\n TableName: this.tableName,\n Key: { pk: partition, sk: String(args.id) },\n ConsistentRead: true,\n }),\n )\n target = result.Item ? stripInternalKeys(result.Item) : null\n } else if (args.where) {\n target = await findFirst(this, { partition, where: args.where })\n }\n\n if (!target) {\n return null as never\n }\n\n const merged: Record<string, unknown> = {\n ...target,\n ...args.data,\n id: target['id'],\n // Bump `updatedAt` automatically. Explicit timestamps in `data` (e.g. from\n // version restores or migrations) still win because they land in the\n // spread above and we only override here when `data` didn't supply one.\n updatedAt: args.data['updatedAt'] ?? new Date().toISOString(),\n }\n // Strip the merged result against the collection schema. Catches both\n // unknown keys in the incoming patch and any pre-existing leaked keys on\n // the persisted target — every update doubles as incremental cleanup.\n const projected = projectForCollection(this, args.collection, merged)\n\n await docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: normalizeForDynamo({\n ...projected,\n pk: partition,\n sk: String(projected['id']),\n }),\n }),\n )\n\n if (args.returning === false) {\n return null as never\n }\n return projected as never\n}\n"],"names":["GetCommand","PutCommand","findFirst","normalizeForDynamo","projectForCollection","stripInternalKeys","updateOne","args","docClient","Error","partition","resolvePartition","collection","target","id","undefined","result","send","TableName","tableName","Key","pk","sk","String","ConsistentRead","Item","where","merged","data","updatedAt","Date","toISOString","projected","returning"],"mappings":"AAEA,SAASA,UAAU,EAAEC,UAAU,QAAQ,wBAAuB;AAI9D,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,oBAAoB,QAAQ,+BAA8B;AACnE,SAASC,iBAAiB,QAAQ,mCAAkC;AAEpE;;;;;;;;;CASC,GACD,OAAO,MAAMC,YAAuB,eAAeA,UAA+BC,IAAI;IACpF,MAAMC,YAAY,IAAI,CAACA,SAAS;IAChC,IAAI,CAACA,WAAW;QACd,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,YAAY,IAAI,CAACC,gBAAgB,CAACJ,KAAKK,UAAU;IAEvD,IAAIC,SAAyC;IAE7C,IAAIN,KAAKO,EAAE,KAAKC,aAAaR,KAAKO,EAAE,KAAK,MAAM;QAC7C,MAAME,SAAS,MAAMR,UAAUS,IAAI,CACjC,IAAIjB,WAAW;YACbkB,WAAW,IAAI,CAACC,SAAS;YACzBC,KAAK;gBAAEC,IAAIX;gBAAWY,IAAIC,OAAOhB,KAAKO,EAAE;YAAE;YAC1CU,gBAAgB;QAClB;QAEFX,SAASG,OAAOS,IAAI,GAAGpB,kBAAkBW,OAAOS,IAAI,IAAI;IAC1D,OAAO,IAAIlB,KAAKmB,KAAK,EAAE;QACrBb,SAAS,MAAMX,UAAU,IAAI,EAAE;YAAEQ;YAAWgB,OAAOnB,KAAKmB,KAAK;QAAC;IAChE;IAEA,IAAI,CAACb,QAAQ;QACX,OAAO;IACT;IAEA,MAAMc,SAAkC;QACtC,GAAGd,MAAM;QACT,GAAGN,KAAKqB,IAAI;QACZd,IAAID,MAAM,CAAC,KAAK;QAChB,2EAA2E;QAC3E,qEAAqE;QACrE,wEAAwE;QACxEgB,WAAWtB,KAAKqB,IAAI,CAAC,YAAY,IAAI,IAAIE,OAAOC,WAAW;IAC7D;IACA,sEAAsE;IACtE,yEAAyE;IACzE,sEAAsE;IACtE,MAAMC,YAAY5B,qBAAqB,IAAI,EAAEG,KAAKK,UAAU,EAAEe;IAE9D,MAAMnB,UAAUS,IAAI,CAClB,IAAIhB,WAAW;QACbiB,WAAW,IAAI,CAACC,SAAS;QACzBM,MAAMtB,mBAAmB;YACvB,GAAG6B,SAAS;YACZX,IAAIX;YACJY,IAAIC,OAAOS,SAAS,CAAC,KAAK;QAC5B;IACF;IAGF,IAAIzB,KAAK0B,SAAS,KAAK,OAAO;QAC5B,OAAO;IACT;IACA,OAAOD;AACT,EAAC"}
1
+ {"version":3,"sources":["../src/updateOne.ts"],"sourcesContent":["import type { UpdateOne } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { getById, writeWithRetry } from './utilities/optimistic.js'\nimport { prepareCollectionWhere } from './utilities/prepareWhere.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\nimport { rethrowUniqueViolation } from './utilities/writeErrors.js'\nimport { putItem } from './utilities/writeItems.js'\n\n/**\n * v1 strategy: read-merge-write rather than building an `UpdateExpression`.\n * Payload sends a partial `data` patch; DynamoDB `UpdateItem` would let us\n * push the diff server-side, but its expression syntax (reserved words,\n * nested attribute paths, list indexing, removal semantics) is enough work to\n * earn its own milestone.\n *\n * The write is conditional on the row still being at the revision that was\n * read. If another writer got in first, the patch is re-applied to the fresh\n * row (`writeWithRetry`), so concurrent updates to different fields both\n * land.\n *\n * `id` on the resulting item always comes from the located target — we don't\n * let `data` rewrite the primary key, which would silently orphan the row.\n */\nexport const updateOne: UpdateOne = async function updateOne(this: DynamoAdapter, args) {\n const { collection, data } = args\n const partition = this.resolvePartition(collection)\n\n const byId = args.id !== undefined && args.id !== null\n const where = byId\n ? undefined\n : await prepareCollectionWhere(this, collection, args.where, args.locale)\n const read = () => {\n if (byId) return getById(this, partition, args.id)\n return where ? findFirst(this, { partition, where }) : Promise.resolve(null)\n }\n\n let updated: null | Record<string, unknown>\n try {\n updated = await writeWithRetry(await read(), read, async (target) => {\n const merged: Record<string, unknown> = {\n ...target,\n ...data,\n id: target['id'],\n // Bump `updatedAt` automatically. Explicit timestamps in `data` (e.g.\n // from version restores or migrations) still win because they land in\n // the spread above and we only override here when `data` didn't\n // supply one.\n updatedAt: data['updatedAt'] ?? new Date().toISOString(),\n }\n // Strip the merged result against the collection schema. Catches both\n // unknown keys in the incoming patch and any pre-existing leaked keys\n // on the persisted target — every update doubles as incremental cleanup.\n const projected = projectForCollection(this, collection, merged)\n await putItem(this, partition, projected, { replaces: target, unique: { collection } })\n return projected\n })\n } catch (err) {\n rethrowUniqueViolation(err, collection, args.req)\n throw err\n }\n\n if (!updated || args.returning === false) {\n return null as never\n }\n return updated as never\n}\n"],"names":["findFirst","getById","writeWithRetry","prepareCollectionWhere","projectForCollection","rethrowUniqueViolation","putItem","updateOne","args","collection","data","partition","resolvePartition","byId","id","undefined","where","locale","read","Promise","resolve","updated","target","merged","updatedAt","Date","toISOString","projected","replaces","unique","err","req","returning"],"mappings":"AAIA,SAASA,SAAS,QAAQ,2BAA0B;AACpD,SAASC,OAAO,EAAEC,cAAc,QAAQ,4BAA2B;AACnE,SAASC,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,oBAAoB,QAAQ,+BAA8B;AACnE,SAASC,sBAAsB,QAAQ,6BAA4B;AACnE,SAASC,OAAO,QAAQ,4BAA2B;AAEnD;;;;;;;;;;;;;;CAcC,GACD,OAAO,MAAMC,YAAuB,eAAeA,UAA+BC,IAAI;IACpF,MAAM,EAAEC,UAAU,EAAEC,IAAI,EAAE,GAAGF;IAC7B,MAAMG,YAAY,IAAI,CAACC,gBAAgB,CAACH;IAExC,MAAMI,OAAOL,KAAKM,EAAE,KAAKC,aAAaP,KAAKM,EAAE,KAAK;IAClD,MAAME,QAAQH,OACVE,YACA,MAAMZ,uBAAuB,IAAI,EAAEM,YAAYD,KAAKQ,KAAK,EAAER,KAAKS,MAAM;IAC1E,MAAMC,OAAO;QACX,IAAIL,MAAM,OAAOZ,QAAQ,IAAI,EAAEU,WAAWH,KAAKM,EAAE;QACjD,OAAOE,QAAQhB,UAAU,IAAI,EAAE;YAAEW;YAAWK;QAAM,KAAKG,QAAQC,OAAO,CAAC;IACzE;IAEA,IAAIC;IACJ,IAAI;QACFA,UAAU,MAAMnB,eAAe,MAAMgB,QAAQA,MAAM,OAAOI;YACxD,MAAMC,SAAkC;gBACtC,GAAGD,MAAM;gBACT,GAAGZ,IAAI;gBACPI,IAAIQ,MAAM,CAAC,KAAK;gBAChB,sEAAsE;gBACtE,sEAAsE;gBACtE,gEAAgE;gBAChE,cAAc;gBACdE,WAAWd,IAAI,CAAC,YAAY,IAAI,IAAIe,OAAOC,WAAW;YACxD;YACA,sEAAsE;YACtE,sEAAsE;YACtE,yEAAyE;YACzE,MAAMC,YAAYvB,qBAAqB,IAAI,EAAEK,YAAYc;YACzD,MAAMjB,QAAQ,IAAI,EAAEK,WAAWgB,WAAW;gBAAEC,UAAUN;gBAAQO,QAAQ;oBAAEpB;gBAAW;YAAE;YACrF,OAAOkB;QACT;IACF,EAAE,OAAOG,KAAK;QACZzB,uBAAuByB,KAAKrB,YAAYD,KAAKuB,GAAG;QAChD,MAAMD;IACR;IAEA,IAAI,CAACT,WAAWb,KAAKwB,SAAS,KAAK,OAAO;QACxC,OAAO;IACT;IACA,OAAOX;AACT,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"updateVersion.d.ts","sourceRoot":"","sources":["../src/updateVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAW5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAE,aA2D3B,CAAA"}
1
+ {"version":3,"file":"updateVersion.d.ts","sourceRoot":"","sources":["../src/updateVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAE,aAK3B,CAAA"}
@@ -1,8 +1,4 @@
1
- import { GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb';
2
- import { findFirst } from './utilities/findFirst.js';
3
- import { normalizeForDynamo } from './utilities/normalizeForDynamo.js';
4
- import { projectVersionRow } from './utilities/resolveSchema.js';
5
- import { stripInternalKeys } from './utilities/stripInternalKeys.js';
1
+ import { updateVersionRow } from './utilities/updateVersionRow.js';
6
2
  /**
7
3
  * Read-merge-write against the versions partition. `versionData` is
8
4
  * splatted onto the existing row, which means Payload-passed metadata
@@ -15,55 +11,10 @@ import { stripInternalKeys } from './utilities/stripInternalKeys.js';
15
11
  * cascade because it knows it's making a new latest; `updateVersion` writes
16
12
  * exactly what it's told.
17
13
  */ export const updateVersion = async function updateVersion(args) {
18
- const docClient = this.docClient;
19
- if (!docClient) {
20
- throw new Error('payload-ddb: docClient is not initialized — call connect() first.');
21
- }
22
- const partition = this.resolveVersionsPartition(args.collection);
23
- let target = null;
24
- if (args.id !== undefined && args.id !== null) {
25
- const result = await docClient.send(new GetCommand({
26
- TableName: this.tableName,
27
- Key: {
28
- pk: partition,
29
- sk: String(args.id)
30
- },
31
- ConsistentRead: true
32
- }));
33
- target = result.Item ? stripInternalKeys(result.Item) : null;
34
- } else if (args.where) {
35
- target = await findFirst(this, {
36
- partition,
37
- where: args.where
38
- });
39
- }
40
- if (!target) {
41
- return null;
42
- }
43
- const merged = {
44
- ...target,
45
- ...args.versionData,
46
- id: target['id']
47
- };
48
- // Project both the version-row metadata layer and the inner `version`
49
- // snapshot. Catches stray keys at either layer plus stale leaks already
50
- // persisted on the target.
51
- const projected = projectVersionRow(this, {
14
+ return updateVersionRow(this, {
52
15
  kind: 'collection',
53
16
  slug: args.collection
54
- }, merged);
55
- await docClient.send(new PutCommand({
56
- TableName: this.tableName,
57
- Item: normalizeForDynamo({
58
- ...projected,
59
- pk: partition,
60
- sk: String(projected['id'])
61
- })
62
- }));
63
- if (args.returning === false) {
64
- return null;
65
- }
66
- return projected;
17
+ }, args);
67
18
  };
68
19
 
69
20
  //# sourceMappingURL=updateVersion.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/updateVersion.ts"],"sourcesContent":["import type { UpdateVersion } from 'payload'\n\nimport { GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { normalizeForDynamo } from './utilities/normalizeForDynamo.js'\nimport { projectVersionRow } from './utilities/resolveSchema.js'\nimport { stripInternalKeys } from './utilities/stripInternalKeys.js'\n\n/**\n * Read-merge-write against the versions partition. `versionData` is\n * splatted onto the existing row, which means Payload-passed metadata\n * (`createdAt`, `updatedAt`, `latest`, `parent`, `publishedLocale`) and the\n * inner `version` payload all overlay correctly.\n *\n * The adapter does not cascade `latest=true` flips — Payload is responsible\n * for clearing the previous latest by issuing a separate `updateVersion`\n * call. This matches the `createVersion` boundary: `createVersion` owns the\n * cascade because it knows it's making a new latest; `updateVersion` writes\n * exactly what it's told.\n */\nexport const updateVersion: UpdateVersion = async function updateVersion(\n this: DynamoAdapter,\n args,\n) {\n const docClient = this.docClient\n if (!docClient) {\n throw new Error('payload-ddb: docClient is not initialized — call connect() first.')\n }\n\n const partition = this.resolveVersionsPartition(args.collection)\n\n let target: null | Record<string, unknown> = null\n\n if (args.id !== undefined && args.id !== null) {\n const result = await docClient.send(\n new GetCommand({\n TableName: this.tableName,\n Key: { pk: partition, sk: String(args.id) },\n ConsistentRead: true,\n }),\n )\n target = result.Item ? stripInternalKeys(result.Item) : null\n } else if (args.where) {\n target = await findFirst(this, { partition, where: args.where })\n }\n\n if (!target) {\n return null as never\n }\n\n const merged: Record<string, unknown> = {\n ...target,\n ...args.versionData,\n id: target['id'],\n }\n // Project both the version-row metadata layer and the inner `version`\n // snapshot. Catches stray keys at either layer plus stale leaks already\n // persisted on the target.\n const projected = projectVersionRow(\n this,\n { kind: 'collection', slug: args.collection },\n merged,\n )\n\n await docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: normalizeForDynamo({\n ...projected,\n pk: partition,\n sk: String(projected['id']),\n }),\n }),\n )\n\n if (args.returning === false) {\n return null as never\n }\n return projected as never\n}\n"],"names":["GetCommand","PutCommand","findFirst","normalizeForDynamo","projectVersionRow","stripInternalKeys","updateVersion","args","docClient","Error","partition","resolveVersionsPartition","collection","target","id","undefined","result","send","TableName","tableName","Key","pk","sk","String","ConsistentRead","Item","where","merged","versionData","projected","kind","slug","returning"],"mappings":"AAEA,SAASA,UAAU,EAAEC,UAAU,QAAQ,wBAAuB;AAI9D,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,iBAAiB,QAAQ,+BAA8B;AAChE,SAASC,iBAAiB,QAAQ,mCAAkC;AAEpE;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,gBAA+B,eAAeA,cAEzDC,IAAI;IAEJ,MAAMC,YAAY,IAAI,CAACA,SAAS;IAChC,IAAI,CAACA,WAAW;QACd,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,YAAY,IAAI,CAACC,wBAAwB,CAACJ,KAAKK,UAAU;IAE/D,IAAIC,SAAyC;IAE7C,IAAIN,KAAKO,EAAE,KAAKC,aAAaR,KAAKO,EAAE,KAAK,MAAM;QAC7C,MAAME,SAAS,MAAMR,UAAUS,IAAI,CACjC,IAAIjB,WAAW;YACbkB,WAAW,IAAI,CAACC,SAAS;YACzBC,KAAK;gBAAEC,IAAIX;gBAAWY,IAAIC,OAAOhB,KAAKO,EAAE;YAAE;YAC1CU,gBAAgB;QAClB;QAEFX,SAASG,OAAOS,IAAI,GAAGpB,kBAAkBW,OAAOS,IAAI,IAAI;IAC1D,OAAO,IAAIlB,KAAKmB,KAAK,EAAE;QACrBb,SAAS,MAAMX,UAAU,IAAI,EAAE;YAAEQ;YAAWgB,OAAOnB,KAAKmB,KAAK;QAAC;IAChE;IAEA,IAAI,CAACb,QAAQ;QACX,OAAO;IACT;IAEA,MAAMc,SAAkC;QACtC,GAAGd,MAAM;QACT,GAAGN,KAAKqB,WAAW;QACnBd,IAAID,MAAM,CAAC,KAAK;IAClB;IACA,sEAAsE;IACtE,wEAAwE;IACxE,2BAA2B;IAC3B,MAAMgB,YAAYzB,kBAChB,IAAI,EACJ;QAAE0B,MAAM;QAAcC,MAAMxB,KAAKK,UAAU;IAAC,GAC5Ce;IAGF,MAAMnB,UAAUS,IAAI,CAClB,IAAIhB,WAAW;QACbiB,WAAW,IAAI,CAACC,SAAS;QACzBM,MAAMtB,mBAAmB;YACvB,GAAG0B,SAAS;YACZR,IAAIX;YACJY,IAAIC,OAAOM,SAAS,CAAC,KAAK;QAC5B;IACF;IAGF,IAAItB,KAAKyB,SAAS,KAAK,OAAO;QAC5B,OAAO;IACT;IACA,OAAOH;AACT,EAAC"}
1
+ {"version":3,"sources":["../src/updateVersion.ts"],"sourcesContent":["import type { UpdateVersion } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { updateVersionRow } from './utilities/updateVersionRow.js'\n\n/**\n * Read-merge-write against the versions partition. `versionData` is\n * splatted onto the existing row, which means Payload-passed metadata\n * (`createdAt`, `updatedAt`, `latest`, `parent`, `publishedLocale`) and the\n * inner `version` payload all overlay correctly.\n *\n * The adapter does not cascade `latest=true` flips — Payload is responsible\n * for clearing the previous latest by issuing a separate `updateVersion`\n * call. This matches the `createVersion` boundary: `createVersion` owns the\n * cascade because it knows it's making a new latest; `updateVersion` writes\n * exactly what it's told.\n */\nexport const updateVersion: UpdateVersion = async function updateVersion(\n this: DynamoAdapter,\n args,\n) {\n return updateVersionRow(this, { kind: 'collection', slug: args.collection }, args) as never\n}\n"],"names":["updateVersionRow","updateVersion","args","kind","slug","collection"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,kCAAiC;AAElE;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,gBAA+B,eAAeA,cAEzDC,IAAI;IAEJ,OAAOF,iBAAiB,IAAI,EAAE;QAAEG,MAAM;QAAcC,MAAMF,KAAKG,UAAU;IAAC,GAAGH;AAC/E,EAAC"}
package/dist/upsert.d.ts CHANGED
@@ -2,6 +2,12 @@ import type { Upsert } from 'payload';
2
2
  /**
3
3
  * Locate by `where`; merge-and-put if found, put-with-fresh-id otherwise.
4
4
  *
5
+ * Both branches are conditional — the update on the row being unchanged
6
+ * since it was read, the insert on no row having appeared at that id — and
7
+ * the whole locate-then-write is retried when the condition fails, so two
8
+ * concurrent upserts of the same thing converge instead of clobbering each
9
+ * other.
10
+ *
5
11
  * Inlined rather than dispatching to `create`/`updateOne` via `.call(this)` —
6
12
  * piping through their `this`-typed signatures requires casts that obscure
7
13
  * the small amount of logic this function actually owns.
@@ -1 +1 @@
1
- {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../src/upsert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAWrC;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,EAAE,MAgDpB,CAAA"}
1
+ {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../src/upsert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAarC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,MAAM,EAAE,MA+CpB,CAAA"}
package/dist/upsert.js CHANGED
@@ -1,55 +1,71 @@
1
- import { PutCommand } from '@aws-sdk/lib-dynamodb';
2
1
  import { randomUUID } from 'node:crypto';
3
2
  import { findFirst } from './utilities/findFirst.js';
4
- import { normalizeForDynamo } from './utilities/normalizeForDynamo.js';
3
+ import { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js';
4
+ import { prepareCollectionWhere } from './utilities/prepareWhere.js';
5
5
  import { projectForCollection } from './utilities/resolveSchema.js';
6
+ import { rethrowUniqueViolation } from './utilities/writeErrors.js';
7
+ import { isConditionalCheckFailed, putItem } from './utilities/writeItems.js';
6
8
  /**
7
9
  * Locate by `where`; merge-and-put if found, put-with-fresh-id otherwise.
8
10
  *
11
+ * Both branches are conditional — the update on the row being unchanged
12
+ * since it was read, the insert on no row having appeared at that id — and
13
+ * the whole locate-then-write is retried when the condition fails, so two
14
+ * concurrent upserts of the same thing converge instead of clobbering each
15
+ * other.
16
+ *
9
17
  * Inlined rather than dispatching to `create`/`updateOne` via `.call(this)` —
10
18
  * piping through their `this`-typed signatures requires casts that obscure
11
19
  * the small amount of logic this function actually owns.
12
- */ export const upsert = async function upsert({ collection, data, returning, where }) {
13
- const docClient = this.docClient;
14
- if (!docClient) {
15
- throw new Error('payload-ddb: docClient is not initialized — call connect() first.');
16
- }
20
+ */ export const upsert = async function upsert({ collection, data, locale, req, returning, where: rawWhere }) {
21
+ const where = await prepareCollectionWhere(this, collection, rawWhere, locale);
17
22
  const partition = this.resolvePartition(collection);
18
- const found = await findFirst(this, {
19
- partition,
20
- where
21
- });
22
- const now = new Date().toISOString();
23
- let item;
24
- if (found) {
25
- // Bump `updatedAt` automatically; explicit value in `data` still wins.
26
- item = {
27
- ...found,
28
- ...data,
29
- id: found['id'],
30
- updatedAt: data['updatedAt'] ?? now
31
- };
32
- } else {
33
- const id = data['id'] ?? randomUUID();
34
- // Same nullish-coalesce pattern as `create`: explicit values in `data`
35
- // win, but a payload-passed `undefined` doesn't wipe the default.
36
- item = {
37
- ...data,
38
- id,
39
- createdAt: data['createdAt'] ?? now,
40
- updatedAt: data['updatedAt'] ?? now
41
- };
23
+ for(let attempt = 1;; attempt++){
24
+ const found = await findFirst(this, {
25
+ partition,
26
+ where
27
+ });
28
+ const now = new Date().toISOString();
29
+ let item;
30
+ if (found) {
31
+ // Bump `updatedAt` automatically; explicit value in `data` still wins.
32
+ item = {
33
+ ...found,
34
+ ...data,
35
+ id: found['id'],
36
+ updatedAt: data['updatedAt'] ?? now
37
+ };
38
+ } else {
39
+ const id = data['id'] ?? randomUUID();
40
+ // Same nullish-coalesce pattern as `create`: explicit values in `data`
41
+ // win, but a payload-passed `undefined` doesn't wipe the default.
42
+ item = {
43
+ ...data,
44
+ id,
45
+ createdAt: data['createdAt'] ?? now,
46
+ updatedAt: data['updatedAt'] ?? now
47
+ };
48
+ }
49
+ const projected = projectForCollection(this, collection, item);
50
+ try {
51
+ await putItem(this, partition, projected, {
52
+ ...found ? {
53
+ replaces: found
54
+ } : {
55
+ mustNotExist: true
56
+ },
57
+ unique: {
58
+ collection
59
+ }
60
+ });
61
+ return returning === false ? null : projected;
62
+ } catch (err) {
63
+ rethrowUniqueViolation(err, collection, req);
64
+ if (!isConditionalCheckFailed(err)) throw err;
65
+ if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError();
66
+ await backoff(attempt);
67
+ }
42
68
  }
43
- const projected = projectForCollection(this, collection, item);
44
- await docClient.send(new PutCommand({
45
- TableName: this.tableName,
46
- Item: normalizeForDynamo({
47
- ...projected,
48
- pk: partition,
49
- sk: String(projected['id'])
50
- })
51
- }));
52
- return returning === false ? null : projected;
53
69
  };
54
70
 
55
71
  //# sourceMappingURL=upsert.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/upsert.ts"],"sourcesContent":["import type { Upsert } from 'payload'\n\nimport { PutCommand } from '@aws-sdk/lib-dynamodb'\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { normalizeForDynamo } from './utilities/normalizeForDynamo.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\n\n/**\n * Locate by `where`; merge-and-put if found, put-with-fresh-id otherwise.\n *\n * Inlined rather than dispatching to `create`/`updateOne` via `.call(this)` —\n * piping through their `this`-typed signatures requires casts that obscure\n * the small amount of logic this function actually owns.\n */\nexport const upsert: Upsert = async function upsert(\n this: DynamoAdapter,\n { collection, data, returning, where },\n) {\n const docClient = this.docClient\n if (!docClient) {\n throw new Error('payload-ddb: docClient is not initialized — call connect() first.')\n }\n\n const partition = this.resolvePartition(collection)\n const found = await findFirst(this, { partition, where })\n\n const now = new Date().toISOString()\n let item: Record<string, unknown>\n if (found) {\n // Bump `updatedAt` automatically; explicit value in `data` still wins.\n item = {\n ...found,\n ...data,\n id: found['id'],\n updatedAt: data['updatedAt'] ?? now,\n }\n } else {\n const id = data['id'] ?? randomUUID()\n // Same nullish-coalesce pattern as `create`: explicit values in `data`\n // win, but a payload-passed `undefined` doesn't wipe the default.\n item = {\n ...data,\n id,\n createdAt: data['createdAt'] ?? now,\n updatedAt: data['updatedAt'] ?? now,\n }\n }\n\n const projected = projectForCollection(this, collection, item)\n\n await docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: normalizeForDynamo({\n ...projected,\n pk: partition,\n sk: String(projected['id']),\n }),\n }),\n )\n\n return returning === false ? (null as never) : (projected as never)\n}\n"],"names":["PutCommand","randomUUID","findFirst","normalizeForDynamo","projectForCollection","upsert","collection","data","returning","where","docClient","Error","partition","resolvePartition","found","now","Date","toISOString","item","id","updatedAt","createdAt","projected","send","TableName","tableName","Item","pk","sk","String"],"mappings":"AAEA,SAASA,UAAU,QAAQ,wBAAuB;AAClD,SAASC,UAAU,QAAQ,cAAa;AAIxC,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,oBAAoB,QAAQ,+BAA8B;AAEnE;;;;;;CAMC,GACD,OAAO,MAAMC,SAAiB,eAAeA,OAE3C,EAAEC,UAAU,EAAEC,IAAI,EAAEC,SAAS,EAAEC,KAAK,EAAE;IAEtC,MAAMC,YAAY,IAAI,CAACA,SAAS;IAChC,IAAI,CAACA,WAAW;QACd,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,YAAY,IAAI,CAACC,gBAAgB,CAACP;IACxC,MAAMQ,QAAQ,MAAMZ,UAAU,IAAI,EAAE;QAAEU;QAAWH;IAAM;IAEvD,MAAMM,MAAM,IAAIC,OAAOC,WAAW;IAClC,IAAIC;IACJ,IAAIJ,OAAO;QACT,uEAAuE;QACvEI,OAAO;YACL,GAAGJ,KAAK;YACR,GAAGP,IAAI;YACPY,IAAIL,KAAK,CAAC,KAAK;YACfM,WAAWb,IAAI,CAAC,YAAY,IAAIQ;QAClC;IACF,OAAO;QACL,MAAMI,KAAKZ,IAAI,CAAC,KAAK,IAAIN;QACzB,uEAAuE;QACvE,kEAAkE;QAClEiB,OAAO;YACL,GAAGX,IAAI;YACPY;YACAE,WAAWd,IAAI,CAAC,YAAY,IAAIQ;YAChCK,WAAWb,IAAI,CAAC,YAAY,IAAIQ;QAClC;IACF;IAEA,MAAMO,YAAYlB,qBAAqB,IAAI,EAAEE,YAAYY;IAEzD,MAAMR,UAAUa,IAAI,CAClB,IAAIvB,WAAW;QACbwB,WAAW,IAAI,CAACC,SAAS;QACzBC,MAAMvB,mBAAmB;YACvB,GAAGmB,SAAS;YACZK,IAAIf;YACJgB,IAAIC,OAAOP,SAAS,CAAC,KAAK;QAC5B;IACF;IAGF,OAAOd,cAAc,QAAS,OAAkBc;AAClD,EAAC"}
1
+ {"version":3,"sources":["../src/upsert.ts"],"sourcesContent":["import type { Upsert } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js'\nimport { prepareCollectionWhere } from './utilities/prepareWhere.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\nimport { rethrowUniqueViolation } from './utilities/writeErrors.js'\nimport { isConditionalCheckFailed, putItem } from './utilities/writeItems.js'\n\n/**\n * Locate by `where`; merge-and-put if found, put-with-fresh-id otherwise.\n *\n * Both branches are conditional — the update on the row being unchanged\n * since it was read, the insert on no row having appeared at that id — and\n * the whole locate-then-write is retried when the condition fails, so two\n * concurrent upserts of the same thing converge instead of clobbering each\n * other.\n *\n * Inlined rather than dispatching to `create`/`updateOne` via `.call(this)` —\n * piping through their `this`-typed signatures requires casts that obscure\n * the small amount of logic this function actually owns.\n */\nexport const upsert: Upsert = async function upsert(\n this: DynamoAdapter,\n { collection, data, locale, req, returning, where: rawWhere },\n) {\n const where = await prepareCollectionWhere(this, collection, rawWhere, locale)\n const partition = this.resolvePartition(collection)\n\n for (let attempt = 1; ; attempt++) {\n const found = await findFirst(this, { partition, where })\n\n const now = new Date().toISOString()\n let item: Record<string, unknown>\n if (found) {\n // Bump `updatedAt` automatically; explicit value in `data` still wins.\n item = {\n ...found,\n ...data,\n id: found['id'],\n updatedAt: data['updatedAt'] ?? now,\n }\n } else {\n const id = data['id'] ?? randomUUID()\n // Same nullish-coalesce pattern as `create`: explicit values in `data`\n // win, but a payload-passed `undefined` doesn't wipe the default.\n item = {\n ...data,\n id,\n createdAt: data['createdAt'] ?? now,\n updatedAt: data['updatedAt'] ?? now,\n }\n }\n\n const projected = projectForCollection(this, collection, item)\n\n try {\n await putItem(this, partition, projected, {\n ...(found ? { replaces: found } : { mustNotExist: true }),\n unique: { collection },\n })\n return returning === false ? (null as never) : (projected as never)\n } catch (err) {\n rethrowUniqueViolation(err, collection, req)\n if (!isConditionalCheckFailed(err)) throw err\n if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError()\n await backoff(attempt)\n }\n }\n}\n"],"names":["randomUUID","findFirst","backoff","conflictError","MAX_WRITE_ATTEMPTS","prepareCollectionWhere","projectForCollection","rethrowUniqueViolation","isConditionalCheckFailed","putItem","upsert","collection","data","locale","req","returning","where","rawWhere","partition","resolvePartition","attempt","found","now","Date","toISOString","item","id","updatedAt","createdAt","projected","replaces","mustNotExist","unique","err"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AAIxC,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,OAAO,EAAEC,aAAa,EAAEC,kBAAkB,QAAQ,4BAA2B;AACtF,SAASC,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,oBAAoB,QAAQ,+BAA8B;AACnE,SAASC,sBAAsB,QAAQ,6BAA4B;AACnE,SAASC,wBAAwB,EAAEC,OAAO,QAAQ,4BAA2B;AAE7E;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,SAAiB,eAAeA,OAE3C,EAAEC,UAAU,EAAEC,IAAI,EAAEC,MAAM,EAAEC,GAAG,EAAEC,SAAS,EAAEC,OAAOC,QAAQ,EAAE;IAE7D,MAAMD,QAAQ,MAAMX,uBAAuB,IAAI,EAAEM,YAAYM,UAAUJ;IACvE,MAAMK,YAAY,IAAI,CAACC,gBAAgB,CAACR;IAExC,IAAK,IAAIS,UAAU,IAAKA,UAAW;QACjC,MAAMC,QAAQ,MAAMpB,UAAU,IAAI,EAAE;YAAEiB;YAAWF;QAAM;QAEvD,MAAMM,MAAM,IAAIC,OAAOC,WAAW;QAClC,IAAIC;QACJ,IAAIJ,OAAO;YACT,uEAAuE;YACvEI,OAAO;gBACL,GAAGJ,KAAK;gBACR,GAAGT,IAAI;gBACPc,IAAIL,KAAK,CAAC,KAAK;gBACfM,WAAWf,IAAI,CAAC,YAAY,IAAIU;YAClC;QACF,OAAO;YACL,MAAMI,KAAKd,IAAI,CAAC,KAAK,IAAIZ;YACzB,uEAAuE;YACvE,kEAAkE;YAClEyB,OAAO;gBACL,GAAGb,IAAI;gBACPc;gBACAE,WAAWhB,IAAI,CAAC,YAAY,IAAIU;gBAChCK,WAAWf,IAAI,CAAC,YAAY,IAAIU;YAClC;QACF;QAEA,MAAMO,YAAYvB,qBAAqB,IAAI,EAAEK,YAAYc;QAEzD,IAAI;YACF,MAAMhB,QAAQ,IAAI,EAAES,WAAWW,WAAW;gBACxC,GAAIR,QAAQ;oBAAES,UAAUT;gBAAM,IAAI;oBAAEU,cAAc;gBAAK,CAAC;gBACxDC,QAAQ;oBAAErB;gBAAW;YACvB;YACA,OAAOI,cAAc,QAAS,OAAkBc;QAClD,EAAE,OAAOI,KAAK;YACZ1B,uBAAuB0B,KAAKtB,YAAYG;YACxC,IAAI,CAACN,yBAAyByB,MAAM,MAAMA;YAC1C,IAAIb,WAAWhB,oBAAoB,MAAMD;YACzC,MAAMD,QAAQkB;QAChB;IACF;AACF,EAAC"}