@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/uniqueGuards.ts"],"sourcesContent":["import type { FlattenedField } from 'payload'\n\nimport { createHash } from 'node:crypto'\nimport { fieldShouldBeLocalized } from 'payload/shared'\n\nimport type { DynamoAdapter } from '../types.js'\n\n/**\n * Uniqueness for fields declared `unique: true`.\n *\n * DynamoDB can only enforce uniqueness on a primary key, so each unique\n * value gets a small *guard row* whose key *is* the value:\n *\n * pk = \"<collection partition>#unique#<stored field path>\"\n * sk = \"<type tag>:<value>\"\n * owner = \"<id of the document holding the value>\"\n *\n * A document and its guards are written in one `TransactWriteItems`, each\n * guard conditioned on \"no such row, or it's already mine\". A second\n * document claiming the same value fails that condition and the whole write\n * is cancelled. Guards live in their own partitions, so they never show up\n * in a collection's reads.\n *\n * Covered: scalar unique fields at the top level or inside groups / named\n * tabs, including localized ones (one guard per locale). Not covered:\n * unique fields inside arrays or blocks, and `hasMany` fields — a list has\n * no single value to key on.\n *\n * Empty values (`null`, `undefined`, `''`) are not guarded: any number of\n * documents may leave a unique field blank.\n */\n\nexport type UniqueGuard = {\n key: { pk: string; sk: string }\n /** Schema path of the field, without locale — what a validation error names. */\n path: string\n}\n\ntype UniqueField = { field: FlattenedField; path: string[] }\n\nconst cache = new WeakMap<DynamoAdapter, Map<string, boolean>>()\n\n/** Whether a collection declares any unique field the adapter guards. */\nexport function hasUniqueFields(adapter: DynamoAdapter, collectionSlug: string): boolean {\n let known = cache.get(adapter)\n if (!known) {\n known = new Map()\n cache.set(adapter, known)\n }\n const cached = known.get(collectionSlug)\n if (cached !== undefined) return cached\n\n const fields = adapter.payload?.collections?.[collectionSlug]?.config.flattenedFields\n const result = fields ? containsUnique(fields) : false\n known.set(collectionSlug, result)\n return result\n}\n\nfunction isGuardable(field: FlattenedField): boolean {\n return (\n 'unique' in field &&\n Boolean(field.unique) &&\n field.name !== 'id' &&\n !('hasMany' in field && field.hasMany)\n )\n}\n\nfunction containsUnique(fields: FlattenedField[]): boolean {\n return fields.some((field) => {\n if (field.type === 'array' || field.type === 'blocks') return false\n if ('flattenedFields' in field) return containsUnique(field.flattenedFields)\n return isGuardable(field)\n })\n}\n\n/** Every guard a document's current values call for. */\nexport function uniqueGuardsFor(\n adapter: DynamoAdapter,\n collectionSlug: string,\n partition: string,\n doc: Record<string, unknown>,\n): UniqueGuard[] {\n if (!hasUniqueFields(adapter, collectionSlug)) return []\n const fields = adapter.payload.collections[collectionSlug]!.config.flattenedFields\n const localeCodes = adapter.payload.config.localization\n ? adapter.payload.config.localization.localeCodes\n : null\n\n const guards: UniqueGuard[] = []\n collect(doc, fields, [], [], false)\n return guards\n\n function collect(\n source: unknown,\n level: FlattenedField[],\n schemaPath: string[],\n storedPath: string[],\n parentIsLocalized: boolean,\n ): void {\n if (!source || typeof source !== 'object' || Array.isArray(source)) return\n const record = source as Record<string, unknown>\n\n for (const field of level) {\n if (field.type === 'array' || field.type === 'blocks') continue\n const isContainer = 'flattenedFields' in field\n if (!isContainer && !isGuardable(field)) continue\n\n const value = record[field.name]\n const schema = [...schemaPath, field.name]\n const stored = [...storedPath, field.name]\n const localized = Boolean(localeCodes) && fieldShouldBeLocalized({ field, parentIsLocalized })\n\n // A localized field holds one value per locale; visit each.\n const variants: [unknown, string[]][] =\n localized && value && typeof value === 'object' && !Array.isArray(value)\n ? Object.entries(value as Record<string, unknown>)\n .filter(([locale]) => localeCodes!.includes(locale))\n .map(([locale, inner]) => [inner, [...stored, locale]])\n : localized\n ? []\n : [[value, stored]]\n\n for (const [inner, innerStored] of variants) {\n if (isContainer) {\n collect(inner, field.flattenedFields, schema, innerStored, parentIsLocalized || localized)\n continue\n }\n const sk = encodeValue(inner)\n if (sk === null) continue\n guards.push({\n key: { pk: `${partition}#unique#${innerStored.join('.')}`, sk },\n path: schema.join('.'),\n })\n }\n }\n }\n}\n\n/**\n * Turn a value into a sort key. The type tag keeps `1` and `'1'` apart;\n * long strings are hashed because a sort key is capped at 1024 bytes.\n * Returns `null` for values that aren't guarded.\n */\nfunction encodeValue(value: unknown): null | string {\n switch (typeof value) {\n case 'boolean':\n return `b:${value}`\n case 'number':\n return Number.isFinite(value) ? `n:${value}` : null\n case 'string':\n if (value === '') return null\n return value.length > 256\n ? `h:${createHash('sha256').update(value).digest('hex')}`\n : `s:${value}`\n default:\n return null\n }\n}\n\nexport function sameGuard(a: UniqueGuard, b: UniqueGuard): boolean {\n return a.key.pk === b.key.pk && a.key.sk === b.key.sk\n}\n"],"names":["createHash","fieldShouldBeLocalized","cache","WeakMap","hasUniqueFields","adapter","collectionSlug","known","get","Map","set","cached","undefined","fields","payload","collections","config","flattenedFields","result","containsUnique","isGuardable","field","Boolean","unique","name","hasMany","some","type","uniqueGuardsFor","partition","doc","localeCodes","localization","guards","collect","source","level","schemaPath","storedPath","parentIsLocalized","Array","isArray","record","isContainer","value","schema","stored","localized","variants","Object","entries","filter","locale","includes","map","inner","innerStored","sk","encodeValue","push","key","pk","join","path","Number","isFinite","length","update","digest","sameGuard","a","b"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Where } from 'payload';
2
+ import type { DynamoAdapter } from '../types.js';
3
+ /**
4
+ * The shared body of `updateVersion` and `updateGlobalVersion`: locate one
5
+ * version row (by id, else by `where`), overlay `versionData`, and write it
6
+ * back — conditional on the row being unchanged since it was read, and
7
+ * retried on the fresh row when it wasn't.
8
+ */
9
+ export declare function updateVersionRow(adapter: DynamoAdapter, parent: {
10
+ kind: 'collection' | 'global';
11
+ slug: string;
12
+ }, args: {
13
+ id?: number | string;
14
+ locale?: string;
15
+ returning?: boolean;
16
+ versionData: Record<string, unknown>;
17
+ where?: Where;
18
+ }): Promise<null | Record<string, unknown>>;
19
+ //# sourceMappingURL=updateVersionRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateVersionRow.d.ts","sourceRoot":"","sources":["../../src/utilities/updateVersionRow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAQhD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE;IAAE,IAAI,EAAE,YAAY,GAAG,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACvD,IAAI,EAAE;IACJ,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,KAAK,CAAC,EAAE,KAAK,CAAA;CACd,GACA,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA2BzC"}
@@ -0,0 +1,40 @@
1
+ import { findFirst } from './findFirst.js';
2
+ import { getById, writeWithRetry } from './optimistic.js';
3
+ import { prepareVersionsWhere } from './prepareWhere.js';
4
+ import { projectVersionRow } from './resolveSchema.js';
5
+ import { putItem } from './writeItems.js';
6
+ /**
7
+ * The shared body of `updateVersion` and `updateGlobalVersion`: locate one
8
+ * version row (by id, else by `where`), overlay `versionData`, and write it
9
+ * back — conditional on the row being unchanged since it was read, and
10
+ * retried on the fresh row when it wasn't.
11
+ */ export async function updateVersionRow(adapter, parent, args) {
12
+ const partition = adapter.resolveVersionsPartition(parent.slug);
13
+ const byId = args.id !== undefined && args.id !== null;
14
+ const where = byId ? undefined : await prepareVersionsWhere(adapter, parent, args.where, args.locale);
15
+ const read = ()=>{
16
+ if (byId) return getById(adapter, partition, args.id);
17
+ return where ? findFirst(adapter, {
18
+ partition,
19
+ where
20
+ }) : Promise.resolve(null);
21
+ };
22
+ const updated = await writeWithRetry(await read(), read, async (target)=>{
23
+ const merged = {
24
+ ...target,
25
+ ...args.versionData,
26
+ id: target['id']
27
+ };
28
+ // Project both the version-row metadata layer and the inner `version`
29
+ // snapshot. Catches stray keys at either layer plus stale leaks already
30
+ // persisted on the target.
31
+ const projected = projectVersionRow(adapter, parent, merged);
32
+ await putItem(adapter, partition, projected, {
33
+ replaces: target
34
+ });
35
+ return projected;
36
+ });
37
+ return args.returning === false ? null : updated;
38
+ }
39
+
40
+ //# sourceMappingURL=updateVersionRow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/updateVersionRow.ts"],"sourcesContent":["import type { Where } from 'payload'\n\nimport type { DynamoAdapter } from '../types.js'\n\nimport { findFirst } from './findFirst.js'\nimport { getById, writeWithRetry } from './optimistic.js'\nimport { prepareVersionsWhere } from './prepareWhere.js'\nimport { projectVersionRow } from './resolveSchema.js'\nimport { putItem } from './writeItems.js'\n\n/**\n * The shared body of `updateVersion` and `updateGlobalVersion`: locate one\n * version row (by id, else by `where`), overlay `versionData`, and write it\n * back — conditional on the row being unchanged since it was read, and\n * retried on the fresh row when it wasn't.\n */\nexport async function updateVersionRow(\n adapter: DynamoAdapter,\n parent: { kind: 'collection' | 'global'; slug: string },\n args: {\n id?: number | string\n locale?: string\n returning?: boolean\n versionData: Record<string, unknown>\n where?: Where\n },\n): Promise<null | Record<string, unknown>> {\n const partition = adapter.resolveVersionsPartition(parent.slug)\n\n const byId = args.id !== undefined && args.id !== null\n const where = byId\n ? undefined\n : await prepareVersionsWhere(adapter, parent, args.where, args.locale)\n const read = () => {\n if (byId) return getById(adapter, partition, args.id)\n return where ? findFirst(adapter, { partition, where }) : Promise.resolve(null)\n }\n\n const updated = await writeWithRetry(await read(), read, async (target) => {\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(adapter, parent, merged)\n await putItem(adapter, partition, projected, { replaces: target })\n return projected\n })\n\n return args.returning === false ? null : updated\n}\n"],"names":["findFirst","getById","writeWithRetry","prepareVersionsWhere","projectVersionRow","putItem","updateVersionRow","adapter","parent","args","partition","resolveVersionsPartition","slug","byId","id","undefined","where","locale","read","Promise","resolve","updated","target","merged","versionData","projected","replaces","returning"],"mappings":"AAIA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,OAAO,EAAEC,cAAc,QAAQ,kBAAiB;AACzD,SAASC,oBAAoB,QAAQ,oBAAmB;AACxD,SAASC,iBAAiB,QAAQ,qBAAoB;AACtD,SAASC,OAAO,QAAQ,kBAAiB;AAEzC;;;;;CAKC,GACD,OAAO,eAAeC,iBACpBC,OAAsB,EACtBC,MAAuD,EACvDC,IAMC;IAED,MAAMC,YAAYH,QAAQI,wBAAwB,CAACH,OAAOI,IAAI;IAE9D,MAAMC,OAAOJ,KAAKK,EAAE,KAAKC,aAAaN,KAAKK,EAAE,KAAK;IAClD,MAAME,QAAQH,OACVE,YACA,MAAMZ,qBAAqBI,SAASC,QAAQC,KAAKO,KAAK,EAAEP,KAAKQ,MAAM;IACvE,MAAMC,OAAO;QACX,IAAIL,MAAM,OAAOZ,QAAQM,SAASG,WAAWD,KAAKK,EAAE;QACpD,OAAOE,QAAQhB,UAAUO,SAAS;YAAEG;YAAWM;QAAM,KAAKG,QAAQC,OAAO,CAAC;IAC5E;IAEA,MAAMC,UAAU,MAAMnB,eAAe,MAAMgB,QAAQA,MAAM,OAAOI;QAC9D,MAAMC,SAAkC;YACtC,GAAGD,MAAM;YACT,GAAGb,KAAKe,WAAW;YACnBV,IAAIQ,MAAM,CAAC,KAAK;QAClB;QACA,sEAAsE;QACtE,wEAAwE;QACxE,2BAA2B;QAC3B,MAAMG,YAAYrB,kBAAkBG,SAASC,QAAQe;QACrD,MAAMlB,QAAQE,SAASG,WAAWe,WAAW;YAAEC,UAAUJ;QAAO;QAChE,OAAOG;IACT;IAEA,OAAOhB,KAAKkB,SAAS,KAAK,QAAQ,OAAON;AAC3C"}
@@ -0,0 +1,11 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import { ValidationError } from 'payload';
3
+ /**
4
+ * The error Payload's other adapters raise for a duplicate key: a
5
+ * `ValidationError` naming the field, which core turns into a 400 with a
6
+ * field-level "Value must be unique" message.
7
+ */
8
+ export declare function mustBeUniqueError(path: string, collection: string, req: Partial<PayloadRequest> | undefined): ValidationError;
9
+ /** Re-throw a unique-field violation in Payload's terms; pass anything else through. */
10
+ export declare function rethrowUniqueViolation(err: unknown, collection: string, req: Partial<PayloadRequest> | undefined): void;
11
+ //# sourceMappingURL=writeErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeErrors.d.ts","sourceRoot":"","sources":["../../src/utilities/writeErrors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAIzC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,GACvC,eAAe,CAcjB;AAED,wFAAwF;AACxF,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,GACvC,IAAI,CAIN"}
@@ -0,0 +1,27 @@
1
+ import { ValidationError } from 'payload';
2
+ import { UniqueViolationError } from './writeItems.js';
3
+ /**
4
+ * The error Payload's other adapters raise for a duplicate key: a
5
+ * `ValidationError` naming the field, which core turns into a 400 with a
6
+ * field-level "Value must be unique" message.
7
+ */ export function mustBeUniqueError(path, collection, req) {
8
+ return new ValidationError({
9
+ collection,
10
+ errors: [
11
+ {
12
+ message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',
13
+ path
14
+ }
15
+ ],
16
+ ...req ? {
17
+ req
18
+ } : {}
19
+ }, req?.t);
20
+ }
21
+ /** Re-throw a unique-field violation in Payload's terms; pass anything else through. */ export function rethrowUniqueViolation(err, collection, req) {
22
+ if (err instanceof UniqueViolationError) {
23
+ throw mustBeUniqueError(err.path, collection, req);
24
+ }
25
+ }
26
+
27
+ //# sourceMappingURL=writeErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/writeErrors.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport { UniqueViolationError } from './writeItems.js'\n\n/**\n * The error Payload's other adapters raise for a duplicate key: a\n * `ValidationError` naming the field, which core turns into a 400 with a\n * field-level \"Value must be unique\" message.\n */\nexport function mustBeUniqueError(\n path: string,\n collection: string,\n req: Partial<PayloadRequest> | undefined,\n): ValidationError {\n return new ValidationError(\n {\n collection,\n errors: [\n {\n message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',\n path,\n },\n ],\n ...(req ? { req } : {}),\n },\n req?.t,\n )\n}\n\n/** Re-throw a unique-field violation in Payload's terms; pass anything else through. */\nexport function rethrowUniqueViolation(\n err: unknown,\n collection: string,\n req: Partial<PayloadRequest> | undefined,\n): void {\n if (err instanceof UniqueViolationError) {\n throw mustBeUniqueError(err.path, collection, req)\n }\n}\n"],"names":["ValidationError","UniqueViolationError","mustBeUniqueError","path","collection","req","errors","message","t","rethrowUniqueViolation","err"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAEzC,SAASC,oBAAoB,QAAQ,kBAAiB;AAEtD;;;;CAIC,GACD,OAAO,SAASC,kBACdC,IAAY,EACZC,UAAkB,EAClBC,GAAwC;IAExC,OAAO,IAAIL,gBACT;QACEI;QACAE,QAAQ;YACN;gBACEC,SAASF,KAAKG,IAAIH,IAAIG,CAAC,CAAC,6BAA6B;gBACrDL;YACF;SACD;QACD,GAAIE,MAAM;YAAEA;QAAI,IAAI,CAAC,CAAC;IACxB,GACAA,KAAKG;AAET;AAEA,sFAAsF,GACtF,OAAO,SAASC,uBACdC,GAAY,EACZN,UAAkB,EAClBC,GAAwC;IAExC,IAAIK,eAAeT,sBAAsB;QACvC,MAAMC,kBAAkBQ,IAAIP,IAAI,EAAEC,YAAYC;IAChD;AACF"}
@@ -0,0 +1,124 @@
1
+ import type { DynamoAdapter } from '../types.js';
2
+ import { type UniqueGuard } from './uniqueGuards.js';
3
+ /**
4
+ * The single place the adapter's methods mutate the table. Every `create*`,
5
+ * `update*`, `upsert` and `delete*` method hands its finished document (or
6
+ * the id to remove) to one of the helpers below instead of building its own
7
+ * DynamoDB command.
8
+ *
9
+ * Keeping writes behind one module means the row's physical shape — the
10
+ * `pk`/`sk` composite key, the `_rev` revision counter, value normalization,
11
+ * and any future index attributes — is derived in exactly one spot
12
+ * (`toItem`), and anything that needs to see every mutation (e.g. buffering
13
+ * writes for a Payload-level transaction) has one seam to hook into.
14
+ *
15
+ * Three guarantees are enforced here, each by a condition DynamoDB checks
16
+ * atomically with the write:
17
+ *
18
+ * - **Insert-only** (`mustNotExist`): a new document can't replace one that
19
+ * already has its id.
20
+ * - **No lost updates** (`replaces`): a read-merge-write only lands if the
21
+ * row is still at the revision that was read. See `stripInternalKeys`.
22
+ * - **Unique fields** (`unique`): the document and a guard row per unique
23
+ * value are written in one transaction. See `uniqueGuards`.
24
+ *
25
+ * Callers stay responsible for everything Payload-shaped: locating the
26
+ * target, merging the patch, defaulting timestamps, and projecting against
27
+ * the collection/global schema. These helpers only persist what they're
28
+ * given.
29
+ *
30
+ * Not routed through here: `scrubUnknownFields`, a one-shot maintenance pass
31
+ * that re-puts raw rows at their original keys without normalizing them.
32
+ */
33
+ /**
34
+ * How many single-row writes a bulk operation keeps in flight at once.
35
+ * Matches `BatchWriteItem`'s page size and stays well under the SDK's default
36
+ * socket pool (50), so a large `deleteMany` / `updateMany` doesn't starve
37
+ * every other request the process is making.
38
+ */
39
+ export declare const WRITE_CONCURRENCY = 25;
40
+ /**
41
+ * The write's own precondition didn't hold: the id was already taken
42
+ * (`mustNotExist`), or the row changed since it was read (`replaces`).
43
+ */
44
+ export declare class ConditionFailedError extends Error {
45
+ constructor();
46
+ }
47
+ /** Another document already holds this value of a unique field. */
48
+ export declare class UniqueViolationError extends Error {
49
+ readonly path: string;
50
+ constructor(path: string);
51
+ }
52
+ /**
53
+ * True when `err` means "this write's precondition didn't hold" — the signal
54
+ * a read-merge-write loop retries on, and that `create` reports as a
55
+ * duplicate id.
56
+ */
57
+ export declare function isConditionalCheckFailed(err: unknown): boolean;
58
+ /**
59
+ * Claim one unique guard for `owner` outside of a document write (used by
60
+ * `backfillUniqueGuards`). Returns `null` when the guard is now the owner's,
61
+ * or the id of the document that already holds it.
62
+ */
63
+ export declare function claimGuard(adapter: DynamoAdapter, guard: UniqueGuard, owner: string): Promise<null | string>;
64
+ export type PutOptions = {
65
+ /** Insert-only: fail if a row already occupies this id. */
66
+ mustNotExist?: boolean;
67
+ /**
68
+ * The row, as read, that this write was merged onto. The write lands only
69
+ * if the stored row is still at that revision.
70
+ */
71
+ replaces?: Record<string, unknown>;
72
+ /** Enforce the unique fields of this collection for `doc`. */
73
+ unique?: {
74
+ collection: string;
75
+ };
76
+ };
77
+ /**
78
+ * Write `doc` as the full row at (`partition`, `doc.id`). The adapter's
79
+ * update paths are read-merge-write, so this is the write half of both
80
+ * inserts and updates.
81
+ *
82
+ * Throws `ConditionFailedError` when `mustNotExist` / `replaces` doesn't
83
+ * hold, and `UniqueViolationError` when a unique value is taken.
84
+ */
85
+ export declare function putItem(adapter: DynamoAdapter, partition: string, doc: Record<string, unknown>, options?: PutOptions): Promise<void>;
86
+ export type DeleteOptions = {
87
+ returnOld?: boolean;
88
+ /**
89
+ * The document being deleted, in a collection with unique fields: its
90
+ * guards are released in the same transaction.
91
+ */
92
+ unique?: {
93
+ collection: string;
94
+ doc: Record<string, unknown>;
95
+ };
96
+ };
97
+ /**
98
+ * Delete the row at (`partition`, `id`). With `returnOld`, the deleted row
99
+ * comes back (internal keys stripped) so callers can delete and capture the
100
+ * document in one round trip; `null` if no row existed. Without it the
101
+ * result is always `null`.
102
+ */
103
+ export declare function deleteItem(adapter: DynamoAdapter, partition: string, id: unknown, options?: DeleteOptions): Promise<null | Record<string, unknown>>;
104
+ /**
105
+ * Delete several rows from one partition, one request each, at most
106
+ * `WRITE_CONCURRENCY` in flight at a time. Pass `uniqueCollection` when the
107
+ * rows belong to a collection whose unique guards must be released with them.
108
+ *
109
+ * `BatchWriteItem` could cut the request count (25 deletes per call) but has
110
+ * partial-failure semantics that need retry logic — out of scope until we
111
+ * wire up retries. Bounded single deletes are simple and correct.
112
+ */
113
+ export declare function deleteItems(adapter: DynamoAdapter, partition: string, rows: Record<string, unknown>[], uniqueCollection?: string): Promise<void>;
114
+ /**
115
+ * Insert `doc` as a version row and, in the same `TransactWriteItems` call,
116
+ * clear `latest` on the row that held it before. If a crash occurs mid-flow
117
+ * neither mutation lands, so a parent never ends up with two `latest=true`
118
+ * rows or zero.
119
+ *
120
+ * `previousLatest` is the row the caller found holding `latest=true`, or
121
+ * `null` when this is the first version.
122
+ */
123
+ export declare function putVersionAsLatest(adapter: DynamoAdapter, partition: string, doc: Record<string, unknown>, previousLatest: null | Record<string, unknown>): Promise<void>;
124
+ //# sourceMappingURL=writeItems.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeItems.d.ts","sourceRoot":"","sources":["../../src/utilities/writeItems.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKhD,OAAO,EAAa,KAAK,WAAW,EAAmB,MAAM,mBAAmB,CAAA;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAEnC;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED,mEAAmE;AACnE,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;gBAET,IAAI,EAAE,MAAM;CAKzB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAK9D;AA+ED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAkBxB;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,8DAA8D;IAC9D,MAAM,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;CAChC,CAAA;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,IAAI,CAAC,CA8Ff;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CAC9D,CAAA;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,OAAO,EACX,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAwCzC;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,cAAc,EAAE,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7C,OAAO,CAAC,IAAI,CAAC,CAqCf"}
@@ -0,0 +1,383 @@
1
+ import { DeleteCommand, GetCommand, PutCommand, TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
2
+ import { APIError } from 'payload';
3
+ import { mapWithConcurrency } from './mapWithConcurrency.js';
4
+ import { normalizeForDynamo } from './normalizeForDynamo.js';
5
+ import { revisionOf, stripInternalKeys } from './stripInternalKeys.js';
6
+ import { sameGuard, uniqueGuardsFor } from './uniqueGuards.js';
7
+ /**
8
+ * The single place the adapter's methods mutate the table. Every `create*`,
9
+ * `update*`, `upsert` and `delete*` method hands its finished document (or
10
+ * the id to remove) to one of the helpers below instead of building its own
11
+ * DynamoDB command.
12
+ *
13
+ * Keeping writes behind one module means the row's physical shape — the
14
+ * `pk`/`sk` composite key, the `_rev` revision counter, value normalization,
15
+ * and any future index attributes — is derived in exactly one spot
16
+ * (`toItem`), and anything that needs to see every mutation (e.g. buffering
17
+ * writes for a Payload-level transaction) has one seam to hook into.
18
+ *
19
+ * Three guarantees are enforced here, each by a condition DynamoDB checks
20
+ * atomically with the write:
21
+ *
22
+ * - **Insert-only** (`mustNotExist`): a new document can't replace one that
23
+ * already has its id.
24
+ * - **No lost updates** (`replaces`): a read-merge-write only lands if the
25
+ * row is still at the revision that was read. See `stripInternalKeys`.
26
+ * - **Unique fields** (`unique`): the document and a guard row per unique
27
+ * value are written in one transaction. See `uniqueGuards`.
28
+ *
29
+ * Callers stay responsible for everything Payload-shaped: locating the
30
+ * target, merging the patch, defaulting timestamps, and projecting against
31
+ * the collection/global schema. These helpers only persist what they're
32
+ * given.
33
+ *
34
+ * Not routed through here: `scrubUnknownFields`, a one-shot maintenance pass
35
+ * that re-puts raw rows at their original keys without normalizing them.
36
+ */ /**
37
+ * How many single-row writes a bulk operation keeps in flight at once.
38
+ * Matches `BatchWriteItem`'s page size and stays well under the SDK's default
39
+ * socket pool (50), so a large `deleteMany` / `updateMany` doesn't starve
40
+ * every other request the process is making.
41
+ */ export const WRITE_CONCURRENCY = 25;
42
+ /**
43
+ * The write's own precondition didn't hold: the id was already taken
44
+ * (`mustNotExist`), or the row changed since it was read (`replaces`).
45
+ */ export class ConditionFailedError extends Error {
46
+ constructor(){
47
+ super('payload-ddb: write condition failed');
48
+ this.name = 'ConditionFailedError';
49
+ }
50
+ }
51
+ /** Another document already holds this value of a unique field. */ export class UniqueViolationError extends Error {
52
+ path;
53
+ constructor(path){
54
+ super(`payload-ddb: value of unique field "${path}" is already in use`);
55
+ this.name = 'UniqueViolationError';
56
+ this.path = path;
57
+ }
58
+ }
59
+ /**
60
+ * True when `err` means "this write's precondition didn't hold" — the signal
61
+ * a read-merge-write loop retries on, and that `create` reports as a
62
+ * duplicate id.
63
+ */ export function isConditionalCheckFailed(err) {
64
+ return err instanceof ConditionFailedError || err instanceof Error && err.name === 'ConditionalCheckFailedException';
65
+ }
66
+ const ITEM_TOO_LARGE = /item size.*exceeded/i;
67
+ function cancellationReasons(err) {
68
+ if (!(err instanceof Error) || err.name !== 'TransactionCanceledException') return null;
69
+ const reasons = err.CancellationReasons;
70
+ return Array.isArray(reasons) ? reasons : [];
71
+ }
72
+ /**
73
+ * DynamoDB caps a single item at 400 KB and reports a breach as a bare
74
+ * `ValidationException` (or, inside a transaction, as one of the
75
+ * cancellation reasons). Re-throw it as an error that says which document
76
+ * was too big and why, and that Payload will show to the API caller.
77
+ */ function rethrowIfItemTooLarge(err, partition, id) {
78
+ if (!(err instanceof Error)) return;
79
+ const tooLarge = ITEM_TOO_LARGE.test(err.message) || (cancellationReasons(err) ?? []).some((reason)=>ITEM_TOO_LARGE.test(reason?.Message ?? ''));
80
+ if (!tooLarge) return;
81
+ throw new APIError(`payload-ddb: document "${String(id)}" in "${partition}" is too large to store. ` + 'DynamoDB limits a single item to 400 KB, including every field and (for versions) the full snapshot.', 413, null, true);
82
+ }
83
+ function requireDocClient(adapter) {
84
+ const docClient = adapter.docClient;
85
+ if (!docClient) {
86
+ throw new Error('payload-ddb: docClient is not initialized — call connect() first.');
87
+ }
88
+ return docClient;
89
+ }
90
+ /**
91
+ * Composite key for a row. `sk` is always the stringified `id` — collections
92
+ * with Number ids keep the original type on the `id` attribute while the
93
+ * sort key stays String (see `ensureTable`).
94
+ */ function keyFor(partition, id) {
95
+ return {
96
+ pk: partition,
97
+ sk: String(id)
98
+ };
99
+ }
100
+ /**
101
+ * Turn a Payload-facing document into the row we store: normalized values
102
+ * plus the internal attributes, keyed by the document's own `id`.
103
+ * The inverse of `stripInternalKeys`.
104
+ */ function toItem(partition, doc, revision) {
105
+ return normalizeForDynamo({
106
+ ...doc,
107
+ ...keyFor(partition, doc['id']),
108
+ _rev: revision
109
+ });
110
+ }
111
+ /** "This guard row is free, or it is already mine." */ function ownedBy(owner) {
112
+ return {
113
+ ConditionExpression: 'attribute_not_exists(pk) OR #owner = :owner',
114
+ ExpressionAttributeNames: {
115
+ '#owner': 'owner'
116
+ },
117
+ ExpressionAttributeValues: {
118
+ ':owner': owner
119
+ }
120
+ };
121
+ }
122
+ /**
123
+ * Claim one unique guard for `owner` outside of a document write (used by
124
+ * `backfillUniqueGuards`). Returns `null` when the guard is now the owner's,
125
+ * or the id of the document that already holds it.
126
+ */ export async function claimGuard(adapter, guard, owner) {
127
+ const docClient = requireDocClient(adapter);
128
+ try {
129
+ await docClient.send(new PutCommand({
130
+ TableName: adapter.tableName,
131
+ Item: {
132
+ ...guard.key,
133
+ owner
134
+ },
135
+ ...ownedBy(owner)
136
+ }));
137
+ return null;
138
+ } catch (err) {
139
+ if (!(err instanceof Error) || err.name !== 'ConditionalCheckFailedException') throw err;
140
+ const held = await docClient.send(new GetCommand({
141
+ TableName: adapter.tableName,
142
+ Key: guard.key,
143
+ ConsistentRead: true
144
+ }));
145
+ return String(held.Item?.['owner'] ?? 'unknown');
146
+ }
147
+ }
148
+ /**
149
+ * Write `doc` as the full row at (`partition`, `doc.id`). The adapter's
150
+ * update paths are read-merge-write, so this is the write half of both
151
+ * inserts and updates.
152
+ *
153
+ * Throws `ConditionFailedError` when `mustNotExist` / `replaces` doesn't
154
+ * hold, and `UniqueViolationError` when a unique value is taken.
155
+ */ export async function putItem(adapter, partition, doc, options = {}) {
156
+ const docClient = requireDocClient(adapter);
157
+ const { mustNotExist, replaces, unique } = options;
158
+ const previousRevision = replaces ? revisionOf(replaces) : undefined;
159
+ const item = toItem(partition, doc, (previousRevision ?? 0) + 1);
160
+ let condition = {};
161
+ if (mustNotExist) {
162
+ // Every row carries `pk`, so "no `pk` attribute" means "no row here".
163
+ condition = {
164
+ ConditionExpression: 'attribute_not_exists(pk)'
165
+ };
166
+ } else if (replaces && previousRevision === undefined) {
167
+ // Read from a row written before revisions existed: it must still be
168
+ // there, and nobody may have given it a revision in the meantime.
169
+ condition = {
170
+ ConditionExpression: 'attribute_exists(pk) AND attribute_not_exists(#rev)',
171
+ ExpressionAttributeNames: {
172
+ '#rev': '_rev'
173
+ }
174
+ };
175
+ } else if (replaces) {
176
+ condition = {
177
+ ConditionExpression: '#rev = :rev',
178
+ ExpressionAttributeNames: {
179
+ '#rev': '_rev'
180
+ },
181
+ ExpressionAttributeValues: {
182
+ ':rev': previousRevision
183
+ }
184
+ };
185
+ }
186
+ const guards = unique ? uniqueGuardsFor(adapter, unique.collection, partition, doc) : [];
187
+ // Guards for values this document no longer has. Those are released, but
188
+ // only if they're ours: a row written before guards existed may share a
189
+ // value with the document that later claimed it.
190
+ let released = unique && replaces ? uniqueGuardsFor(adapter, unique.collection, partition, replaces).filter((old)=>!guards.some((current)=>sameGuard(current, old))) : [];
191
+ try {
192
+ if (guards.length === 0 && released.length === 0) {
193
+ await docClient.send(new PutCommand({
194
+ TableName: adapter.tableName,
195
+ Item: item,
196
+ ...condition
197
+ }));
198
+ return;
199
+ }
200
+ const owner = String(doc['id']);
201
+ const mine = ownedBy(owner);
202
+ // At most two passes: if a released guard turns out to belong to someone
203
+ // else, leave it alone and write the rest.
204
+ for(;;){
205
+ const transactItems = [
206
+ {
207
+ Put: {
208
+ TableName: adapter.tableName,
209
+ Item: item,
210
+ ...condition
211
+ }
212
+ },
213
+ // Re-asserted on every write, not only when a value changes, so
214
+ // documents that predate guards pick theirs up as they're edited.
215
+ ...guards.map((guard)=>({
216
+ Put: {
217
+ TableName: adapter.tableName,
218
+ Item: {
219
+ ...guard.key,
220
+ owner
221
+ },
222
+ ...mine
223
+ }
224
+ })),
225
+ ...released.map((guard)=>({
226
+ Delete: {
227
+ TableName: adapter.tableName,
228
+ Key: guard.key,
229
+ ...mine
230
+ }
231
+ }))
232
+ ];
233
+ try {
234
+ await docClient.send(new TransactWriteCommand({
235
+ TransactItems: transactItems
236
+ }));
237
+ return;
238
+ } catch (err) {
239
+ const reasons = cancellationReasons(err);
240
+ if (!reasons) throw err;
241
+ const failed = (index)=>{
242
+ const code = reasons[index]?.Code;
243
+ return code !== undefined && code !== 'None';
244
+ };
245
+ if (failed(0)) throw new ConditionFailedError();
246
+ const taken = guards.findIndex((_, index)=>failed(1 + index));
247
+ if (taken !== -1) {
248
+ // A clash with a concurrent transaction on the same guard isn't
249
+ // proof the value is taken — let the caller retry and find out.
250
+ if (reasons[1 + taken]?.Code === 'TransactionConflict') throw new ConditionFailedError();
251
+ throw new UniqueViolationError(guards[taken].path);
252
+ }
253
+ const kept = released.filter((_, index)=>!failed(1 + guards.length + index));
254
+ if (kept.length === released.length) throw err;
255
+ released = kept;
256
+ }
257
+ }
258
+ } catch (err) {
259
+ if (err instanceof Error && err.name === 'ConditionalCheckFailedException') {
260
+ throw new ConditionFailedError();
261
+ }
262
+ rethrowIfItemTooLarge(err, partition, doc['id']);
263
+ throw err;
264
+ }
265
+ }
266
+ /**
267
+ * Delete the row at (`partition`, `id`). With `returnOld`, the deleted row
268
+ * comes back (internal keys stripped) so callers can delete and capture the
269
+ * document in one round trip; `null` if no row existed. Without it the
270
+ * result is always `null`.
271
+ */ export async function deleteItem(adapter, partition, id, options = {}) {
272
+ const docClient = requireDocClient(adapter);
273
+ const key = keyFor(partition, id);
274
+ const guards = options.unique ? uniqueGuardsFor(adapter, options.unique.collection, partition, options.unique.doc) : [];
275
+ if (guards.length > 0) {
276
+ const mine = ownedBy(String(id));
277
+ try {
278
+ await docClient.send(new TransactWriteCommand({
279
+ TransactItems: [
280
+ {
281
+ Delete: {
282
+ TableName: adapter.tableName,
283
+ Key: key
284
+ }
285
+ },
286
+ ...guards.map((guard)=>({
287
+ Delete: {
288
+ TableName: adapter.tableName,
289
+ Key: guard.key,
290
+ ...mine
291
+ }
292
+ }))
293
+ ]
294
+ }));
295
+ } catch (err) {
296
+ // A guard that belongs to another document (possible for rows that
297
+ // predate guards) cancels the transaction. The document must still go,
298
+ // so delete it alone and leave that guard with its owner.
299
+ if (!cancellationReasons(err)) throw err;
300
+ await docClient.send(new DeleteCommand({
301
+ TableName: adapter.tableName,
302
+ Key: key
303
+ }));
304
+ }
305
+ return options.returnOld ? options.unique.doc : null;
306
+ }
307
+ const result = await docClient.send(new DeleteCommand({
308
+ TableName: adapter.tableName,
309
+ Key: key,
310
+ ...options.returnOld ? {
311
+ ReturnValues: 'ALL_OLD'
312
+ } : {}
313
+ }));
314
+ return options.returnOld && result.Attributes ? stripInternalKeys(result.Attributes) : null;
315
+ }
316
+ /**
317
+ * Delete several rows from one partition, one request each, at most
318
+ * `WRITE_CONCURRENCY` in flight at a time. Pass `uniqueCollection` when the
319
+ * rows belong to a collection whose unique guards must be released with them.
320
+ *
321
+ * `BatchWriteItem` could cut the request count (25 deletes per call) but has
322
+ * partial-failure semantics that need retry logic — out of scope until we
323
+ * wire up retries. Bounded single deletes are simple and correct.
324
+ */ export async function deleteItems(adapter, partition, rows, uniqueCollection) {
325
+ await mapWithConcurrency(rows, WRITE_CONCURRENCY, (row)=>deleteItem(adapter, partition, row['id'], uniqueCollection ? {
326
+ unique: {
327
+ collection: uniqueCollection,
328
+ doc: row
329
+ }
330
+ } : {}));
331
+ }
332
+ /**
333
+ * Insert `doc` as a version row and, in the same `TransactWriteItems` call,
334
+ * clear `latest` on the row that held it before. If a crash occurs mid-flow
335
+ * neither mutation lands, so a parent never ends up with two `latest=true`
336
+ * rows or zero.
337
+ *
338
+ * `previousLatest` is the row the caller found holding `latest=true`, or
339
+ * `null` when this is the first version.
340
+ */ export async function putVersionAsLatest(adapter, partition, doc, previousLatest) {
341
+ const docClient = requireDocClient(adapter);
342
+ const transactItems = [];
343
+ if (previousLatest) {
344
+ transactItems.push({
345
+ Update: {
346
+ TableName: adapter.tableName,
347
+ Key: keyFor(partition, previousLatest['id']),
348
+ // Bumping `_rev` along with the flip means a read-merge-write that
349
+ // read this row while it was still latest can't write `latest` back.
350
+ UpdateExpression: 'SET #latest = :false ADD #rev :one',
351
+ ExpressionAttributeNames: {
352
+ '#latest': 'latest',
353
+ '#rev': '_rev'
354
+ },
355
+ ExpressionAttributeValues: {
356
+ ':false': false,
357
+ ':one': 1
358
+ },
359
+ // Fail the whole transaction if the previous row was deleted between
360
+ // the caller's read and this transaction. Better to error and let the
361
+ // caller retry than to silently leave the new version with a phantom
362
+ // flip.
363
+ ConditionExpression: 'attribute_exists(pk)'
364
+ }
365
+ });
366
+ }
367
+ transactItems.push({
368
+ Put: {
369
+ TableName: adapter.tableName,
370
+ Item: toItem(partition, doc, 1)
371
+ }
372
+ });
373
+ try {
374
+ await docClient.send(new TransactWriteCommand({
375
+ TransactItems: transactItems
376
+ }));
377
+ } catch (err) {
378
+ rethrowIfItemTooLarge(err, partition, doc['id']);
379
+ throw err;
380
+ }
381
+ }
382
+
383
+ //# sourceMappingURL=writeItems.js.map