@open-aih/payload-ddb 4.1.0 → 4.2.0-beta.20261113193102

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 (102) hide show
  1. package/README.md +133 -26
  2. package/dist/create.js +4 -6
  3. package/dist/create.js.map +1 -1
  4. package/dist/createGlobalVersion.d.ts.map +1 -1
  5. package/dist/createGlobalVersion.js +15 -11
  6. package/dist/createGlobalVersion.js.map +1 -1
  7. package/dist/createVersion.d.ts +11 -8
  8. package/dist/createVersion.d.ts.map +1 -1
  9. package/dist/createVersion.js +25 -28
  10. package/dist/createVersion.js.map +1 -1
  11. package/dist/deleteMany.d.ts.map +1 -1
  12. package/dist/deleteMany.js +6 -3
  13. package/dist/deleteMany.js.map +1 -1
  14. package/dist/deleteOne.js +5 -5
  15. package/dist/deleteOne.js.map +1 -1
  16. package/dist/deleteVersions.d.ts.map +1 -1
  17. package/dist/deleteVersions.js +5 -1
  18. package/dist/deleteVersions.js.map +1 -1
  19. package/dist/find.d.ts +16 -13
  20. package/dist/find.d.ts.map +1 -1
  21. package/dist/find.js +85 -24
  22. package/dist/find.js.map +1 -1
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/init.d.ts.map +1 -1
  28. package/dist/init.js +5 -0
  29. package/dist/init.js.map +1 -1
  30. package/dist/queryDrafts.d.ts.map +1 -1
  31. package/dist/queryDrafts.js +5 -6
  32. package/dist/queryDrafts.js.map +1 -1
  33. package/dist/types.d.ts +14 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/types.js.map +1 -1
  36. package/dist/updateMany.js +2 -4
  37. package/dist/updateMany.js.map +1 -1
  38. package/dist/updateOne.js +2 -4
  39. package/dist/updateOne.js.map +1 -1
  40. package/dist/upsert.js +1 -3
  41. package/dist/upsert.js.map +1 -1
  42. package/dist/utilities/applySorts.d.ts +5 -2
  43. package/dist/utilities/applySorts.d.ts.map +1 -1
  44. package/dist/utilities/applySorts.js +11 -6
  45. package/dist/utilities/applySorts.js.map +1 -1
  46. package/dist/utilities/backfillIndexes.d.ts +71 -0
  47. package/dist/utilities/backfillIndexes.d.ts.map +1 -0
  48. package/dist/utilities/backfillIndexes.js +149 -0
  49. package/dist/utilities/backfillIndexes.js.map +1 -0
  50. package/dist/utilities/batchGet.d.ts +15 -0
  51. package/dist/utilities/batchGet.d.ts.map +1 -0
  52. package/dist/utilities/batchGet.js +56 -0
  53. package/dist/utilities/batchGet.js.map +1 -0
  54. package/dist/utilities/draftsFallback.d.ts.map +1 -1
  55. package/dist/utilities/draftsFallback.js +3 -6
  56. package/dist/utilities/draftsFallback.js.map +1 -1
  57. package/dist/utilities/fieldIndexes.d.ts +130 -0
  58. package/dist/utilities/fieldIndexes.d.ts.map +1 -0
  59. package/dist/utilities/fieldIndexes.js +286 -0
  60. package/dist/utilities/fieldIndexes.js.map +1 -0
  61. package/dist/utilities/findFirst.d.ts +2 -0
  62. package/dist/utilities/findFirst.d.ts.map +1 -1
  63. package/dist/utilities/findFirst.js +5 -0
  64. package/dist/utilities/findFirst.js.map +1 -1
  65. package/dist/utilities/indexLookup.d.ts +73 -0
  66. package/dist/utilities/indexLookup.d.ts.map +1 -0
  67. package/dist/utilities/indexLookup.js +361 -0
  68. package/dist/utilities/indexLookup.js.map +1 -0
  69. package/dist/utilities/indexState.d.ts +13 -0
  70. package/dist/utilities/indexState.d.ts.map +1 -0
  71. package/dist/utilities/indexState.js +199 -0
  72. package/dist/utilities/indexState.js.map +1 -0
  73. package/dist/utilities/queryCount.d.ts +8 -1
  74. package/dist/utilities/queryCount.d.ts.map +1 -1
  75. package/dist/utilities/queryCount.js +8 -2
  76. package/dist/utilities/queryCount.js.map +1 -1
  77. package/dist/utilities/queryMatching.d.ts +12 -5
  78. package/dist/utilities/queryMatching.d.ts.map +1 -1
  79. package/dist/utilities/queryMatching.js +11 -5
  80. package/dist/utilities/queryMatching.js.map +1 -1
  81. package/dist/utilities/queryPartition.d.ts +2 -2
  82. package/dist/utilities/queryPartition.js +2 -2
  83. package/dist/utilities/queryPartition.js.map +1 -1
  84. package/dist/utilities/uniqueGuards.d.ts +34 -0
  85. package/dist/utilities/uniqueGuards.d.ts.map +1 -1
  86. package/dist/utilities/uniqueGuards.js +50 -24
  87. package/dist/utilities/uniqueGuards.js.map +1 -1
  88. package/dist/utilities/updateVersionRow.js +2 -1
  89. package/dist/utilities/updateVersionRow.js.map +1 -1
  90. package/dist/utilities/versionIndex.d.ts +45 -0
  91. package/dist/utilities/versionIndex.d.ts.map +1 -0
  92. package/dist/utilities/versionIndex.js +103 -0
  93. package/dist/utilities/versionIndex.js.map +1 -0
  94. package/dist/utilities/writeItems.d.ts +63 -20
  95. package/dist/utilities/writeItems.d.ts.map +1 -1
  96. package/dist/utilities/writeItems.js +360 -68
  97. package/dist/utilities/writeItems.js.map +1 -1
  98. package/package.json +1 -1
  99. package/dist/utilities/backfillUniqueGuards.d.ts +0 -42
  100. package/dist/utilities/backfillUniqueGuards.d.ts.map +0 -1
  101. package/dist/utilities/backfillUniqueGuards.js +0 -39
  102. package/dist/utilities/backfillUniqueGuards.js.map +0 -1
package/README.md CHANGED
@@ -6,9 +6,11 @@ stores data in [Amazon DynamoDB](https://aws.amazon.com/dynamodb/).
6
6
  > **Status:** all `BaseDatabaseAdapter` methods are implemented end-to-end:
7
7
  > CRUD, drafts, versions, globals, localization, every `where` operator, join
8
8
  > fields and unique fields. The current strategy is "correct first, optimize
9
- > later" — a single shared table with a `Query`-per-collection access pattern
10
- > and no GSIs. Suitable for development and small-to-medium workloads; large
11
- > collections will want the optimizations listed under *Known limits*.
9
+ > later" — a single shared table where lookups by id, unique field or indexed
10
+ > field go straight to the matching rows, and every other filter reads the
11
+ > collection's partition. Suitable for development and small-to-medium
12
+ > workloads; large collections will want the optimizations listed under
13
+ > *Known limits*.
12
14
 
13
15
  ## Requirements
14
16
 
@@ -89,6 +91,7 @@ value in the same physical table.
89
91
  | `tableName` | `string` | `'payload'` | The single DynamoDB table backing every collection, global, and versions stream. |
90
92
  | `ensureTables` | `boolean` | `false` | When true, auto-creates the table at init if it doesn't exist. Dev-loop convenience; turn off in production. |
91
93
  | `migrationDir` | `string` | `'migrations'` | Directory Payload reads and writes migration files in. |
94
+ | `indexes` | `boolean` | `true` | Maintain index rows for `index: true` fields and answer lookups by an indexed or unique field from them instead of reading the collection. See *Indexes*. |
92
95
 
93
96
  ## Data layout
94
97
 
@@ -107,9 +110,19 @@ value in the same physical table.
107
110
  silently overwrite each other.
108
111
  - **Unique guards:** `pk = "<slug>#unique#<field path>"`, `sk = "<value>"`,
109
112
  one small row per value of a `unique: true` field. See *Unique fields*.
110
- - **No GSIs or LSIs.** Per-collection reads are `Query`s scoped by `pk`;
111
- predicates beyond `pk` get pushed to `FilterExpression` (DynamoDB still
112
- reads every row in the partition before filtering).
113
+ - **Index rows:** `pk = "<slug>#index#<field path>"`,
114
+ `sk = "<value key>#<id>"`, one small row per value of an `index: true`
115
+ field. Value keys sort the way the values do. See *Indexes*.
116
+ - **Latest-version pointers:** `pk = "<slug>_versions#latest"`,
117
+ `sk = "<parent key>"`, one row per versioned document naming its latest
118
+ version.
119
+ - **Index markers:** `pk = "payload-ddb#indexes"`, one row per index that is
120
+ known to cover every document.
121
+ - **No GSIs or LSIs.** Indexes are ordinary rows in the same table, which is
122
+ what lets them be read with strong consistency. A filter no index can
123
+ answer is a `Query` scoped by `pk`, with its predicates pushed to
124
+ `FilterExpression` (DynamoDB still reads every row in the partition before
125
+ filtering).
113
126
 
114
127
  ## What's implemented
115
128
 
@@ -153,13 +166,89 @@ Every Payload `where` operator is supported: `equals`, `not_equals`,
153
166
  - **Geo operators** work on `point` fields. Distances are great-circle
154
167
  metres; polygon tests are planar.
155
168
 
169
+ ### Indexes
170
+
171
+ A filter normally reads the collection's whole partition. These clauses avoid
172
+ that and read only the matching documents:
173
+
174
+ | Clause | Answered from |
175
+ |---|---|
176
+ | `id` `equals` / `in` | the documents' own keys |
177
+ | `equals` / `in` on a `unique: true` field | the field's unique guards |
178
+ | `equals` / `in` on an `index: true` field | index rows |
179
+ | `greater_than(_equal)` / `less_than(_equal)` on an `index: true` field | index rows, which are kept in value order |
180
+
181
+ The clause has to hold for every result: at the top level or inside `and`,
182
+ not on one side of an `or`. The rest of the `where` is then evaluated on the
183
+ documents it selects. This covers login by `email`, pages by `slug`,
184
+ relationship population (`id in [...]`), filters on a relationship, join
185
+ fields, and date or number ranges.
186
+
187
+ - **Which fields.** `index: true` on a `text`, `email`, `number`, `date`,
188
+ `relationship` or `upload` field — including `hasMany` fields (one row per
189
+ element), fields in groups and named tabs, and localized fields (per
190
+ locale). Payload sets the flag itself on the field a join points at.
191
+ Not indexed: fields in arrays or blocks, polymorphic relationships, types
192
+ where equality rarely narrows a read (`checkbox`, `select`), and the
193
+ timestamps Payload manages (`createdAt`, `updatedAt`, `deletedAt`), where
194
+ an index would turn every insert or update into a transaction.
195
+ - **Sorted pages.** A `find` sorted by one single-valued indexed field walks
196
+ that field's index in order and stops when the page is full, instead of
197
+ reading and sorting the collection. With `pagination: false` nothing else
198
+ is read. With pagination, the total comes from a `COUNT` pass over the
199
+ partition, which returns no documents but is still billed for the read.
200
+ Not used on drafts-enabled collections, for sorts on several fields, or
201
+ for pages more than 2,000 documents deep.
202
+ - **Consistency.** Index rows are written in the same transaction as their
203
+ document and read with strong consistency, so a lookup sees a document as
204
+ soon as it is written — which a DynamoDB GSI can't promise.
205
+ - **Write cost.** A write that adds or removes index rows is a transaction:
206
+ twice the write units for the document, plus the rows. An update that
207
+ leaves the indexed fields alone costs what it did before. A single-valued
208
+ indexed field has a row for every document, blank ones included, so
209
+ inserts into a collection with one are always transactions.
210
+ - **Large matches.** A lookup that finds more than 256 candidate documents
211
+ falls back to reading the partition, which is the cheaper read by then.
212
+ - **Opting out.** `indexes: false` stops the index rows, the pointers and
213
+ the routed lookups. Lookups by `id` and unique enforcement are unaffected.
214
+ Every instance sharing a table must use the same setting.
215
+
216
+ **Versions.** Version rows are indexed by their `parent`, so listing or
217
+ pruning one document's versions reads only those. Each versioned document
218
+ (and each versioned global) also has a pointer to its latest version: saving
219
+ finds the version to demote in two key reads, and listing drafts fetches one
220
+ version per document rather than every version of every document.
221
+
222
+ **Existing data.** An index is only used once it is known to cover every
223
+ row. A partition that is empty when the adapter starts qualifies straight
224
+ away, which covers new tables and new collections. For one that already has
225
+ rows — after upgrading, or after adding `index: true` to a field — run the
226
+ backfill once; until then its lookups keep reading the partition:
227
+
228
+ ```ts
229
+ import { backfillIndexes } from '@aih-software/payload-ddb'
230
+
231
+ const report = await backfillIndexes(payload)
232
+ // report.collections[slug].ready lists the indexes now in use
233
+ // report.versions[slug] covers that collection's or global's versions
234
+ ```
235
+
236
+ The backfill is safe to run while the application is serving, and safe to
237
+ re-run. It also removes stray index rows, which an interrupted write can
238
+ leave behind (lookups ignore them, so they cost a little space, not
239
+ correctness). When upgrading from a version before 4.2, finish the rollout
240
+ first: older instances write rows without their index rows, so run the
241
+ backfill once after the last one is gone.
242
+
156
243
  ### Join fields
157
244
 
158
- `join` fields are resolved for `find`, `findByID` and draft queries: one query
159
- per target collection for all returned documents, grouped in memory, with
160
- `limit`, `page`, `sort`, `where` and `count` applied per parent document.
161
- Polymorphic joins (`collection: [...]`) return `{ relationTo, value }` entries.
162
- Joined documents are read from the target's published rows.
245
+ `join` fields are resolved for `find`, `findByID` and draft queries: one
246
+ lookup per target collection for all returned documents, grouped in memory,
247
+ with `limit`, `page`, `sort`, `where` and `count` applied per parent document.
248
+ The lookup uses the index on the field the join points at, so it doesn't read
249
+ the target collection's partition. Polymorphic joins (`collection: [...]`)
250
+ return `{ relationTo, value }` entries. Joined documents are read from the
251
+ target's published rows.
163
252
 
164
253
  ### Write guarantees
165
254
 
@@ -171,6 +260,10 @@ Joined documents are read from the target's published rows.
171
260
  per adapter call: Payload's own `update` operation fills the fields missing
172
261
  from a patch with the values it read beforehand, so two concurrent
173
262
  `payload.update` calls on one document can still overwrite each other.
263
+ - **One latest version.** Creating a version demotes the previous latest and
264
+ moves the document's pointer in one transaction that checks nobody else got
265
+ there first, so saving a document several times at once still leaves
266
+ exactly one version marked latest.
174
267
  - **Jobs are claimed once.** `updateJobs` uses the same check, so two workers
175
268
  polling the queue can't both take the same job.
176
269
  - **Bulk writes are bounded.** `updateMany`, `deleteMany` and `deleteVersions`
@@ -190,34 +283,42 @@ values (`null`, `''`) are not guarded. Unique fields inside arrays or blocks,
190
283
  and `hasMany` fields, are not enforced.
191
284
 
192
285
  Writes to a collection with unique fields are transactional and cost twice the
193
- write units; collections without them are unaffected.
286
+ write units.
194
287
 
195
288
  Documents written before this was enforced have no guards until they are next
196
- saved. To cover them in one pass:
289
+ saved. `backfillIndexes` (see *Indexes*) covers them in the same pass:
197
290
 
198
291
  ```ts
199
- import { backfillUniqueGuards } from '@aih-software/payload-ddb'
200
-
201
- const report = await backfillUniqueGuards(payload)
292
+ const report = await backfillIndexes(payload)
202
293
  // report.collections[slug].conflicts lists pre-existing duplicates
203
294
  ```
204
295
 
296
+ A unique field with unresolved duplicates stays out of use for lookups until
297
+ they are fixed and the pass is run again. `backfillUniqueGuards` is kept as an
298
+ alias.
299
+
205
300
  ## Known limits / future work
206
301
 
207
- - **No GSI routing.** Every non-id read is a `Query` against the collection's
208
- partition (with `FilterExpression` pushdown — DynamoDB still reads every
209
- row in the partition internally, but only matching rows come back over the
210
- wire). Adding GSIs that mirror Payload's common access patterns
211
- (e.g. `email` for auth, `slug` for public-facing collections) is the
212
- highest-impact remaining optimization milestone.
302
+ - **Unindexed filters and sorts read the partition.** That includes the
303
+ default sort on `createdAt`, sorts on several fields, and any filter with
304
+ no indexed clause. The matches are sorted and paged in memory.
305
+ - **Totals are counted.** A paginated `find` that reads its page through an
306
+ index still counts the matches with a pass over the partition.
307
+ - **Drafts are listed in full.** A drafts listing fetches the latest version
308
+ of every document — one read each, not the whole versions partition — and
309
+ then filters, sorts and pages in memory. `find` on a drafts-enabled
310
+ collection does the same for documents it hasn't already matched.
311
+ - **A document's versions are read together.** Payload looks up a document's
312
+ (or global's) newest version by sort before each save, which reads all of
313
+ that document's versions — bounded by `versions.maxPerDoc`.
213
314
  - **Payload-level transactions are no-op.** `beginTransaction` returns `null`;
214
315
  commit/rollback do nothing. Per-method atomicity (e.g. `createVersion`'s
215
316
  flip+put) uses DynamoDB `TransactWriteItems` already; full Payload-context
216
317
  transaction buffering across multiple adapter calls is its own milestone.
217
- - **Filters cost a partition read.** Clauses evaluated in JS, relationship
218
- paths and joins all read the partitions involved in full (a relationship
219
- path or join adds one read of the related collection). Fine at CMS scale;
220
- the GSI milestone is what removes it.
318
+ - **Relationship paths read the related collection.** `author.name equals
319
+ 'Ada'` first finds the matching authors — a partition read unless `name` is
320
+ indexed — and then looks the documents up through the index on `author`,
321
+ when it has one.
221
322
  - **Joins don't see unpublished drafts** of the joined collection.
222
323
  - **`locale: 'all'`** in a filter or sort falls back to the default locale.
223
324
  - **`like` matches its operand as one substring.** Payload's Mongo adapter
@@ -264,6 +365,12 @@ The public publish uses npm
264
365
  no npm token to rotate. The trusted publisher configured on npmjs.com must
265
366
  name this repository and the workflow file `release.yml`.
266
367
 
368
+ **Prereleases.** To try a change in a real project before releasing it, push
369
+ the commit to the `beta` branch (`git push origin HEAD:beta --force`). The
370
+ same workflow publishes it to both registries as `X.Y.Z-beta.<timestamp>`
371
+ under the `beta` dist-tag — install it with `@open-aih/payload-ddb@beta` —
372
+ and commits, tags and merges nothing. `latest` is untouched.
373
+
267
374
  ## License
268
375
 
269
376
  MIT
package/dist/create.js CHANGED
@@ -26,13 +26,11 @@ export const create = async function create({ collection, customID, data, req, r
26
26
  try {
27
27
  // Insert-only: a plain `PutItem` would replace whatever row already
28
28
  // sits at this id, so a caller-supplied id that collides with an
29
- // existing doc would silently destroy it. `unique` adds a guard row per
30
- // unique field in the same transaction.
29
+ // existing doc would silently destroy it. `collection` adds the
30
+ // document's unique guards and index rows to the same transaction.
31
31
  await putItem(this, partition, projected, {
32
- mustNotExist: true,
33
- unique: {
34
- collection
35
- }
32
+ collection,
33
+ mustNotExist: true
36
34
  });
37
35
  break;
38
36
  } catch (err) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/create.ts"],"sourcesContent":["import type { Create } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { backoff, conflictError, getById, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\nimport { mustBeUniqueError, rethrowUniqueViolation } from './utilities/writeErrors.js'\nimport { isConditionalCheckFailed, putItem } from './utilities/writeItems.js'\n\nexport const create: Create = async function create(\n this: DynamoAdapter,\n { collection, customID, data, req, returning },\n) {\n const id = customID ?? data['id'] ?? randomUUID()\n const now = new Date().toISOString()\n // Spread `data` first, then nullish-coalesce timestamps. Explicit values\n // in `data` (versions/restore, migrations backdating) still win, but a\n // payload-passed `data.createdAt = undefined` no longer wipes the default\n // back to undefined — which the marshaller would then drop with\n // `removeUndefinedValues: true`, leaving the row without timestamps.\n const item: Record<string, unknown> = {\n ...data,\n id,\n createdAt: data['createdAt'] ?? now,\n updatedAt: data['updatedAt'] ?? now,\n }\n // Project against the collection's declared fields. DDB has no schema\n // layer, so without this any stray request-body key (notoriously the\n // registration form's `confirm-password`) would persist verbatim.\n const projected = projectForCollection(this, collection, item)\n const partition = this.resolvePartition(collection)\n\n for (let attempt = 1; ; attempt++) {\n try {\n // Insert-only: a plain `PutItem` would replace whatever row already\n // sits at this id, so a caller-supplied id that collides with an\n // existing doc would silently destroy it. `unique` adds a guard row per\n // unique field in the same transaction.\n await putItem(this, partition, projected, { mustNotExist: true, unique: { collection } })\n break\n } catch (err) {\n rethrowUniqueViolation(err, collection, req)\n if (!isConditionalCheckFailed(err)) throw err\n\n // Either the id is taken, or this write collided with a concurrent one\n // over a unique value. Surface the first the way the Mongo and Postgres\n // adapters surface a duplicate primary key — a `ValidationError` on\n // `id` — and retry the second, which resolves to a clean success or a\n // unique violation.\n if (await getById(this, partition, id)) throw mustBeUniqueError('id', collection, req)\n if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError()\n await backoff(attempt)\n }\n }\n\n return returning === false ? (null as never) : projected\n}\n"],"names":["randomUUID","backoff","conflictError","getById","MAX_WRITE_ATTEMPTS","projectForCollection","mustBeUniqueError","rethrowUniqueViolation","isConditionalCheckFailed","putItem","create","collection","customID","data","req","returning","id","now","Date","toISOString","item","createdAt","updatedAt","projected","partition","resolvePartition","attempt","mustNotExist","unique","err"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AAIxC,SAASC,OAAO,EAAEC,aAAa,EAAEC,OAAO,EAAEC,kBAAkB,QAAQ,4BAA2B;AAC/F,SAASC,oBAAoB,QAAQ,+BAA8B;AACnE,SAASC,iBAAiB,EAAEC,sBAAsB,QAAQ,6BAA4B;AACtF,SAASC,wBAAwB,EAAEC,OAAO,QAAQ,4BAA2B;AAE7E,OAAO,MAAMC,SAAiB,eAAeA,OAE3C,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,IAAI,EAAEC,GAAG,EAAEC,SAAS,EAAE;IAE9C,MAAMC,KAAKJ,YAAYC,IAAI,CAAC,KAAK,IAAIb;IACrC,MAAMiB,MAAM,IAAIC,OAAOC,WAAW;IAClC,yEAAyE;IACzE,uEAAuE;IACvE,0EAA0E;IAC1E,gEAAgE;IAChE,qEAAqE;IACrE,MAAMC,OAAgC;QACpC,GAAGP,IAAI;QACPG;QACAK,WAAWR,IAAI,CAAC,YAAY,IAAII;QAChCK,WAAWT,IAAI,CAAC,YAAY,IAAII;IAClC;IACA,sEAAsE;IACtE,qEAAqE;IACrE,kEAAkE;IAClE,MAAMM,YAAYlB,qBAAqB,IAAI,EAAEM,YAAYS;IACzD,MAAMI,YAAY,IAAI,CAACC,gBAAgB,CAACd;IAExC,IAAK,IAAIe,UAAU,IAAKA,UAAW;QACjC,IAAI;YACF,oEAAoE;YACpE,iEAAiE;YACjE,wEAAwE;YACxE,wCAAwC;YACxC,MAAMjB,QAAQ,IAAI,EAAEe,WAAWD,WAAW;gBAAEI,cAAc;gBAAMC,QAAQ;oBAAEjB;gBAAW;YAAE;YACvF;QACF,EAAE,OAAOkB,KAAK;YACZtB,uBAAuBsB,KAAKlB,YAAYG;YACxC,IAAI,CAACN,yBAAyBqB,MAAM,MAAMA;YAE1C,uEAAuE;YACvE,wEAAwE;YACxE,oEAAoE;YACpE,sEAAsE;YACtE,oBAAoB;YACpB,IAAI,MAAM1B,QAAQ,IAAI,EAAEqB,WAAWR,KAAK,MAAMV,kBAAkB,MAAMK,YAAYG;YAClF,IAAIY,WAAWtB,oBAAoB,MAAMF;YACzC,MAAMD,QAAQyB;QAChB;IACF;IAEA,OAAOX,cAAc,QAAS,OAAiBQ;AACjD,EAAC"}
1
+ {"version":3,"sources":["../src/create.ts"],"sourcesContent":["import type { Create } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { backoff, conflictError, getById, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js'\nimport { projectForCollection } from './utilities/resolveSchema.js'\nimport { mustBeUniqueError, rethrowUniqueViolation } from './utilities/writeErrors.js'\nimport { isConditionalCheckFailed, putItem } from './utilities/writeItems.js'\n\nexport const create: Create = async function create(\n this: DynamoAdapter,\n { collection, customID, data, req, returning },\n) {\n const id = customID ?? data['id'] ?? randomUUID()\n const now = new Date().toISOString()\n // Spread `data` first, then nullish-coalesce timestamps. Explicit values\n // in `data` (versions/restore, migrations backdating) still win, but a\n // payload-passed `data.createdAt = undefined` no longer wipes the default\n // back to undefined — which the marshaller would then drop with\n // `removeUndefinedValues: true`, leaving the row without timestamps.\n const item: Record<string, unknown> = {\n ...data,\n id,\n createdAt: data['createdAt'] ?? now,\n updatedAt: data['updatedAt'] ?? now,\n }\n // Project against the collection's declared fields. DDB has no schema\n // layer, so without this any stray request-body key (notoriously the\n // registration form's `confirm-password`) would persist verbatim.\n const projected = projectForCollection(this, collection, item)\n const partition = this.resolvePartition(collection)\n\n for (let attempt = 1; ; attempt++) {\n try {\n // Insert-only: a plain `PutItem` would replace whatever row already\n // sits at this id, so a caller-supplied id that collides with an\n // existing doc would silently destroy it. `collection` adds the\n // document's unique guards and index rows to the same transaction.\n await putItem(this, partition, projected, { collection, mustNotExist: true })\n break\n } catch (err) {\n rethrowUniqueViolation(err, collection, req)\n if (!isConditionalCheckFailed(err)) throw err\n\n // Either the id is taken, or this write collided with a concurrent one\n // over a unique value. Surface the first the way the Mongo and Postgres\n // adapters surface a duplicate primary key — a `ValidationError` on\n // `id` — and retry the second, which resolves to a clean success or a\n // unique violation.\n if (await getById(this, partition, id)) throw mustBeUniqueError('id', collection, req)\n if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError()\n await backoff(attempt)\n }\n }\n\n return returning === false ? (null as never) : projected\n}\n"],"names":["randomUUID","backoff","conflictError","getById","MAX_WRITE_ATTEMPTS","projectForCollection","mustBeUniqueError","rethrowUniqueViolation","isConditionalCheckFailed","putItem","create","collection","customID","data","req","returning","id","now","Date","toISOString","item","createdAt","updatedAt","projected","partition","resolvePartition","attempt","mustNotExist","err"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AAIxC,SAASC,OAAO,EAAEC,aAAa,EAAEC,OAAO,EAAEC,kBAAkB,QAAQ,4BAA2B;AAC/F,SAASC,oBAAoB,QAAQ,+BAA8B;AACnE,SAASC,iBAAiB,EAAEC,sBAAsB,QAAQ,6BAA4B;AACtF,SAASC,wBAAwB,EAAEC,OAAO,QAAQ,4BAA2B;AAE7E,OAAO,MAAMC,SAAiB,eAAeA,OAE3C,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,IAAI,EAAEC,GAAG,EAAEC,SAAS,EAAE;IAE9C,MAAMC,KAAKJ,YAAYC,IAAI,CAAC,KAAK,IAAIb;IACrC,MAAMiB,MAAM,IAAIC,OAAOC,WAAW;IAClC,yEAAyE;IACzE,uEAAuE;IACvE,0EAA0E;IAC1E,gEAAgE;IAChE,qEAAqE;IACrE,MAAMC,OAAgC;QACpC,GAAGP,IAAI;QACPG;QACAK,WAAWR,IAAI,CAAC,YAAY,IAAII;QAChCK,WAAWT,IAAI,CAAC,YAAY,IAAII;IAClC;IACA,sEAAsE;IACtE,qEAAqE;IACrE,kEAAkE;IAClE,MAAMM,YAAYlB,qBAAqB,IAAI,EAAEM,YAAYS;IACzD,MAAMI,YAAY,IAAI,CAACC,gBAAgB,CAACd;IAExC,IAAK,IAAIe,UAAU,IAAKA,UAAW;QACjC,IAAI;YACF,oEAAoE;YACpE,iEAAiE;YACjE,gEAAgE;YAChE,mEAAmE;YACnE,MAAMjB,QAAQ,IAAI,EAAEe,WAAWD,WAAW;gBAAEZ;gBAAYgB,cAAc;YAAK;YAC3E;QACF,EAAE,OAAOC,KAAK;YACZrB,uBAAuBqB,KAAKjB,YAAYG;YACxC,IAAI,CAACN,yBAAyBoB,MAAM,MAAMA;YAE1C,uEAAuE;YACvE,wEAAwE;YACxE,oEAAoE;YACpE,sEAAsE;YACtE,oBAAoB;YACpB,IAAI,MAAMzB,QAAQ,IAAI,EAAEqB,WAAWR,KAAK,MAAMV,kBAAkB,MAAMK,YAAYG;YAClF,IAAIY,WAAWtB,oBAAoB,MAAMF;YACzC,MAAMD,QAAQyB;QAChB;IACF;IAEA,OAAOX,cAAc,QAAS,OAAiBQ;AACjD,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"createGlobalVersion.d.ts","sourceRoot":"","sources":["../src/createGlobalVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAUlD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBAwCjC,CAAA"}
1
+ {"version":3,"file":"createGlobalVersion.d.ts","sourceRoot":"","sources":["../src/createGlobalVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAWlD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBA8CjC,CAAA"}
@@ -1,21 +1,14 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { findFirst } from './utilities/findFirst.js';
2
+ import { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js';
3
3
  import { projectVersionSnapshot } from './utilities/resolveSchema.js';
4
- import { putVersionAsLatest } from './utilities/writeItems.js';
4
+ import { findLatestVersion } from './utilities/versionIndex.js';
5
+ import { isConditionalCheckFailed, putVersionAsLatest } from './utilities/writeItems.js';
5
6
  /**
6
7
  * Like `createVersion` but for global singletons: there's no `parent`, and
7
8
  * the "latest" scope is the entire global's versions partition. Same atomic
8
9
  * flip+put via `putVersionAsLatest`. See `createVersion` for rationale.
9
10
  */ export const createGlobalVersion = async function createGlobalVersion({ autosave, createdAt, globalSlug, publishedLocale, returning, snapshot, updatedAt, versionData }) {
10
11
  const partition = this.resolveVersionsPartition(globalSlug);
11
- const previousLatest = await findFirst(this, {
12
- partition,
13
- where: {
14
- latest: {
15
- equals: true
16
- }
17
- }
18
- });
19
12
  const id = randomUUID();
20
13
  const sanitizedVersionData = projectVersionSnapshot(this, {
21
14
  kind: 'global',
@@ -35,7 +28,18 @@ import { putVersionAsLatest } from './utilities/writeItems.js';
35
28
  publishedLocale
36
29
  } : {}
37
30
  };
38
- await putVersionAsLatest(this, partition, item, previousLatest);
31
+ for(let attempt = 1;; attempt++){
32
+ // A global has one stream of versions, so one pointer and no parent.
33
+ const { pointer, previous } = await findLatestVersion(this, partition, undefined);
34
+ try {
35
+ await putVersionAsLatest(this, partition, item, previous, pointer);
36
+ break;
37
+ } catch (err) {
38
+ if (!isConditionalCheckFailed(err)) throw err;
39
+ if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError();
40
+ await backoff(attempt);
41
+ }
42
+ }
39
43
  return returning === false ? null : item;
40
44
  };
41
45
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/createGlobalVersion.ts"],"sourcesContent":["import type { CreateGlobalVersion } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { projectVersionSnapshot } from './utilities/resolveSchema.js'\nimport { putVersionAsLatest } from './utilities/writeItems.js'\n\n/**\n * Like `createVersion` but for global singletons: there's no `parent`, and\n * the \"latest\" scope is the entire global's versions partition. Same atomic\n * flip+put via `putVersionAsLatest`. See `createVersion` for rationale.\n */\nexport const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(\n this: DynamoAdapter,\n {\n autosave,\n createdAt,\n globalSlug,\n publishedLocale,\n returning,\n snapshot,\n updatedAt,\n versionData,\n },\n) {\n const partition = this.resolveVersionsPartition(globalSlug)\n\n const previousLatest = await findFirst(this, {\n partition,\n where: { latest: { equals: true } },\n })\n\n const id = randomUUID()\n const sanitizedVersionData = projectVersionSnapshot(\n this,\n { kind: 'global', slug: globalSlug },\n versionData as Record<string, unknown>,\n )\n const item: Record<string, unknown> = {\n id,\n version: sanitizedVersionData,\n createdAt,\n updatedAt,\n latest: true,\n autosave,\n ...(snapshot ? { snapshot: true } : {}),\n ...(publishedLocale !== undefined ? { publishedLocale } : {}),\n }\n\n await putVersionAsLatest(this, partition, item, previousLatest)\n\n return returning === false ? (null as never) : (item as never)\n}\n"],"names":["randomUUID","findFirst","projectVersionSnapshot","putVersionAsLatest","createGlobalVersion","autosave","createdAt","globalSlug","publishedLocale","returning","snapshot","updatedAt","versionData","partition","resolveVersionsPartition","previousLatest","where","latest","equals","id","sanitizedVersionData","kind","slug","item","version","undefined"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AAIxC,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,sBAAsB,QAAQ,+BAA8B;AACrE,SAASC,kBAAkB,QAAQ,4BAA2B;AAE9D;;;;CAIC,GACD,OAAO,MAAMC,sBAA2C,eAAeA,oBAErE,EACEC,QAAQ,EACRC,SAAS,EACTC,UAAU,EACVC,eAAe,EACfC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,WAAW,EACZ;IAED,MAAMC,YAAY,IAAI,CAACC,wBAAwB,CAACP;IAEhD,MAAMQ,iBAAiB,MAAMd,UAAU,IAAI,EAAE;QAC3CY;QACAG,OAAO;YAAEC,QAAQ;gBAAEC,QAAQ;YAAK;QAAE;IACpC;IAEA,MAAMC,KAAKnB;IACX,MAAMoB,uBAAuBlB,uBAC3B,IAAI,EACJ;QAAEmB,MAAM;QAAUC,MAAMf;IAAW,GACnCK;IAEF,MAAMW,OAAgC;QACpCJ;QACAK,SAASJ;QACTd;QACAK;QACAM,QAAQ;QACRZ;QACA,GAAIK,WAAW;YAAEA,UAAU;QAAK,IAAI,CAAC,CAAC;QACtC,GAAIF,oBAAoBiB,YAAY;YAAEjB;QAAgB,IAAI,CAAC,CAAC;IAC9D;IAEA,MAAML,mBAAmB,IAAI,EAAEU,WAAWU,MAAMR;IAEhD,OAAON,cAAc,QAAS,OAAkBc;AAClD,EAAC"}
1
+ {"version":3,"sources":["../src/createGlobalVersion.ts"],"sourcesContent":["import type { CreateGlobalVersion } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js'\nimport { projectVersionSnapshot } from './utilities/resolveSchema.js'\nimport { findLatestVersion } from './utilities/versionIndex.js'\nimport { isConditionalCheckFailed, putVersionAsLatest } from './utilities/writeItems.js'\n\n/**\n * Like `createVersion` but for global singletons: there's no `parent`, and\n * the \"latest\" scope is the entire global's versions partition. Same atomic\n * flip+put via `putVersionAsLatest`. See `createVersion` for rationale.\n */\nexport const createGlobalVersion: CreateGlobalVersion = async function createGlobalVersion(\n this: DynamoAdapter,\n {\n autosave,\n createdAt,\n globalSlug,\n publishedLocale,\n returning,\n snapshot,\n updatedAt,\n versionData,\n },\n) {\n const partition = this.resolveVersionsPartition(globalSlug)\n\n const id = randomUUID()\n const sanitizedVersionData = projectVersionSnapshot(\n this,\n { kind: 'global', slug: globalSlug },\n versionData as Record<string, unknown>,\n )\n const item: Record<string, unknown> = {\n id,\n version: sanitizedVersionData,\n createdAt,\n updatedAt,\n latest: true,\n autosave,\n ...(snapshot ? { snapshot: true } : {}),\n ...(publishedLocale !== undefined ? { publishedLocale } : {}),\n }\n\n for (let attempt = 1; ; attempt++) {\n // A global has one stream of versions, so one pointer and no parent.\n const { pointer, previous } = await findLatestVersion(this, partition, undefined)\n try {\n await putVersionAsLatest(this, partition, item, previous, pointer)\n break\n } catch (err) {\n if (!isConditionalCheckFailed(err)) throw err\n if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError()\n await backoff(attempt)\n }\n }\n\n return returning === false ? (null as never) : (item as never)\n}\n"],"names":["randomUUID","backoff","conflictError","MAX_WRITE_ATTEMPTS","projectVersionSnapshot","findLatestVersion","isConditionalCheckFailed","putVersionAsLatest","createGlobalVersion","autosave","createdAt","globalSlug","publishedLocale","returning","snapshot","updatedAt","versionData","partition","resolveVersionsPartition","id","sanitizedVersionData","kind","slug","item","version","latest","undefined","attempt","pointer","previous","err"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AAIxC,SAASC,OAAO,EAAEC,aAAa,EAAEC,kBAAkB,QAAQ,4BAA2B;AACtF,SAASC,sBAAsB,QAAQ,+BAA8B;AACrE,SAASC,iBAAiB,QAAQ,8BAA6B;AAC/D,SAASC,wBAAwB,EAAEC,kBAAkB,QAAQ,4BAA2B;AAExF;;;;CAIC,GACD,OAAO,MAAMC,sBAA2C,eAAeA,oBAErE,EACEC,QAAQ,EACRC,SAAS,EACTC,UAAU,EACVC,eAAe,EACfC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,WAAW,EACZ;IAED,MAAMC,YAAY,IAAI,CAACC,wBAAwB,CAACP;IAEhD,MAAMQ,KAAKnB;IACX,MAAMoB,uBAAuBhB,uBAC3B,IAAI,EACJ;QAAEiB,MAAM;QAAUC,MAAMX;IAAW,GACnCK;IAEF,MAAMO,OAAgC;QACpCJ;QACAK,SAASJ;QACTV;QACAK;QACAU,QAAQ;QACRhB;QACA,GAAIK,WAAW;YAAEA,UAAU;QAAK,IAAI,CAAC,CAAC;QACtC,GAAIF,oBAAoBc,YAAY;YAAEd;QAAgB,IAAI,CAAC,CAAC;IAC9D;IAEA,IAAK,IAAIe,UAAU,IAAKA,UAAW;QACjC,qEAAqE;QACrE,MAAM,EAAEC,OAAO,EAAEC,QAAQ,EAAE,GAAG,MAAMxB,kBAAkB,IAAI,EAAEY,WAAWS;QACvE,IAAI;YACF,MAAMnB,mBAAmB,IAAI,EAAEU,WAAWM,MAAMM,UAAUD;YAC1D;QACF,EAAE,OAAOE,KAAK;YACZ,IAAI,CAACxB,yBAAyBwB,MAAM,MAAMA;YAC1C,IAAIH,WAAWxB,oBAAoB,MAAMD;YACzC,MAAMD,QAAQ0B;QAChB;IACF;IAEA,OAAOd,cAAc,QAAS,OAAkBU;AAClD,EAAC"}
@@ -3,14 +3,17 @@ import type { CreateVersion } from 'payload';
3
3
  * Insert a new version for a collection's parent doc, maintaining the
4
4
  * `latest=true` invariant per parent.
5
5
  *
6
- * Two round trips:
7
- * 1. `Query` the versions partition for the previous `latest=true` row.
8
- * 2. `putVersionAsLatest` flips that row's `latest` to false and puts the
9
- * new row in one atomic `TransactWriteItems` call. If a crash occurs
10
- * mid-flow neither mutation lands, so we never end up with two
11
- * `latest=true` rows or zero. The flip carries `attribute_exists(pk)` so
12
- * the transaction fails cleanly if the previous row was deleted between
13
- * (1) and (2).
6
+ * Two steps:
7
+ * 1. Find the previous `latest=true` row — through the parent's pointer
8
+ * (two key reads), or by reading the versions partition while pointers
9
+ * aren't complete yet (`findLatestVersion`).
10
+ * 2. `putVersionAsLatest` flips that row's `latest` to false, puts the new
11
+ * row and moves the pointer in one atomic `TransactWriteItems` call. If
12
+ * a crash occurs mid-flow none of it lands, so we never end up with two
13
+ * `latest=true` rows or zero.
14
+ *
15
+ * If another save of the same document got in between the two steps, the
16
+ * transaction's conditions fail and both steps are repeated.
14
17
  *
15
18
  * `autosave` is persisted on the row even though it isn't surfaced in
16
19
  * `TypeWithVersion` — `findVersions` filters by it.
@@ -1 +1 @@
1
- {"version":3,"file":"createVersion.d.ts","sourceRoot":"","sources":["../src/createVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAU5C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,EAAE,aA6C3B,CAAA"}
1
+ {"version":3,"file":"createVersion.d.ts","sourceRoot":"","sources":["../src/createVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAW5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,EAAE,aAkD3B,CAAA"}
@@ -1,41 +1,28 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { findFirst } from './utilities/findFirst.js';
2
+ import { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js';
3
3
  import { projectVersionSnapshot } from './utilities/resolveSchema.js';
4
- import { putVersionAsLatest } from './utilities/writeItems.js';
4
+ import { findLatestVersion } from './utilities/versionIndex.js';
5
+ import { isConditionalCheckFailed, putVersionAsLatest } from './utilities/writeItems.js';
5
6
  /**
6
7
  * Insert a new version for a collection's parent doc, maintaining the
7
8
  * `latest=true` invariant per parent.
8
9
  *
9
- * Two round trips:
10
- * 1. `Query` the versions partition for the previous `latest=true` row.
11
- * 2. `putVersionAsLatest` flips that row's `latest` to false and puts the
12
- * new row in one atomic `TransactWriteItems` call. If a crash occurs
13
- * mid-flow neither mutation lands, so we never end up with two
14
- * `latest=true` rows or zero. The flip carries `attribute_exists(pk)` so
15
- * the transaction fails cleanly if the previous row was deleted between
16
- * (1) and (2).
10
+ * Two steps:
11
+ * 1. Find the previous `latest=true` row — through the parent's pointer
12
+ * (two key reads), or by reading the versions partition while pointers
13
+ * aren't complete yet (`findLatestVersion`).
14
+ * 2. `putVersionAsLatest` flips that row's `latest` to false, puts the new
15
+ * row and moves the pointer in one atomic `TransactWriteItems` call. If
16
+ * a crash occurs mid-flow none of it lands, so we never end up with two
17
+ * `latest=true` rows or zero.
18
+ *
19
+ * If another save of the same document got in between the two steps, the
20
+ * transaction's conditions fail and both steps are repeated.
17
21
  *
18
22
  * `autosave` is persisted on the row even though it isn't surfaced in
19
23
  * `TypeWithVersion` — `findVersions` filters by it.
20
24
  */ export const createVersion = async function createVersion({ autosave, collectionSlug, createdAt, parent, publishedLocale, returning, snapshot, updatedAt, versionData }) {
21
25
  const partition = this.resolveVersionsPartition(collectionSlug);
22
- const previousLatest = await findFirst(this, {
23
- partition,
24
- where: {
25
- and: [
26
- {
27
- parent: {
28
- equals: parent
29
- }
30
- },
31
- {
32
- latest: {
33
- equals: true
34
- }
35
- }
36
- ]
37
- }
38
- });
39
26
  const id = randomUUID();
40
27
  // Project the snapshot against the parent collection's schema so that any
41
28
  // unknown keys (e.g. registration-form `confirm-password`) don't ride into
@@ -59,7 +46,17 @@ import { putVersionAsLatest } from './utilities/writeItems.js';
59
46
  publishedLocale
60
47
  } : {}
61
48
  };
62
- await putVersionAsLatest(this, partition, item, previousLatest);
49
+ for(let attempt = 1;; attempt++){
50
+ const { pointer, previous } = await findLatestVersion(this, partition, parent);
51
+ try {
52
+ await putVersionAsLatest(this, partition, item, previous, pointer);
53
+ break;
54
+ } catch (err) {
55
+ if (!isConditionalCheckFailed(err)) throw err;
56
+ if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError();
57
+ await backoff(attempt);
58
+ }
59
+ }
63
60
  return returning === false ? null : item;
64
61
  };
65
62
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/createVersion.ts"],"sourcesContent":["import type { CreateVersion } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { projectVersionSnapshot } from './utilities/resolveSchema.js'\nimport { putVersionAsLatest } from './utilities/writeItems.js'\n\n/**\n * Insert a new version for a collection's parent doc, maintaining the\n * `latest=true` invariant per parent.\n *\n * Two round trips:\n * 1. `Query` the versions partition for the previous `latest=true` row.\n * 2. `putVersionAsLatest` flips that row's `latest` to false and puts the\n * new row in one atomic `TransactWriteItems` call. If a crash occurs\n * mid-flow neither mutation lands, so we never end up with two\n * `latest=true` rows or zero. The flip carries `attribute_exists(pk)` so\n * the transaction fails cleanly if the previous row was deleted between\n * (1) and (2).\n *\n * `autosave` is persisted on the row even though it isn't surfaced in\n * `TypeWithVersion` — `findVersions` filters by it.\n */\nexport const createVersion: CreateVersion = async function createVersion(\n this: DynamoAdapter,\n {\n autosave,\n collectionSlug,\n createdAt,\n parent,\n publishedLocale,\n returning,\n snapshot,\n updatedAt,\n versionData,\n },\n) {\n const partition = this.resolveVersionsPartition(collectionSlug)\n\n const previousLatest = await findFirst(this, {\n partition,\n where: { and: [{ parent: { equals: parent } }, { latest: { equals: true } }] },\n })\n\n const id = randomUUID()\n // Project the snapshot against the parent collection's schema so that any\n // unknown keys (e.g. registration-form `confirm-password`) don't ride into\n // the version row alongside the legitimate doc state.\n const sanitizedVersionData = projectVersionSnapshot(\n this,\n { kind: 'collection', slug: collectionSlug },\n versionData as Record<string, unknown>,\n )\n const item: Record<string, unknown> = {\n id,\n parent,\n version: sanitizedVersionData,\n createdAt,\n updatedAt,\n latest: true,\n autosave,\n ...(snapshot ? { snapshot: true } : {}),\n ...(publishedLocale !== undefined ? { publishedLocale } : {}),\n }\n\n await putVersionAsLatest(this, partition, item, previousLatest)\n\n return returning === false ? (null as never) : (item as never)\n}\n"],"names":["randomUUID","findFirst","projectVersionSnapshot","putVersionAsLatest","createVersion","autosave","collectionSlug","createdAt","parent","publishedLocale","returning","snapshot","updatedAt","versionData","partition","resolveVersionsPartition","previousLatest","where","and","equals","latest","id","sanitizedVersionData","kind","slug","item","version","undefined"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AAIxC,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,sBAAsB,QAAQ,+BAA8B;AACrE,SAASC,kBAAkB,QAAQ,4BAA2B;AAE9D;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,gBAA+B,eAAeA,cAEzD,EACEC,QAAQ,EACRC,cAAc,EACdC,SAAS,EACTC,MAAM,EACNC,eAAe,EACfC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,WAAW,EACZ;IAED,MAAMC,YAAY,IAAI,CAACC,wBAAwB,CAACT;IAEhD,MAAMU,iBAAiB,MAAMf,UAAU,IAAI,EAAE;QAC3Ca;QACAG,OAAO;YAAEC,KAAK;gBAAC;oBAAEV,QAAQ;wBAAEW,QAAQX;oBAAO;gBAAE;gBAAG;oBAAEY,QAAQ;wBAAED,QAAQ;oBAAK;gBAAE;aAAE;QAAC;IAC/E;IAEA,MAAME,KAAKrB;IACX,0EAA0E;IAC1E,2EAA2E;IAC3E,sDAAsD;IACtD,MAAMsB,uBAAuBpB,uBAC3B,IAAI,EACJ;QAAEqB,MAAM;QAAcC,MAAMlB;IAAe,GAC3CO;IAEF,MAAMY,OAAgC;QACpCJ;QACAb;QACAkB,SAASJ;QACTf;QACAK;QACAQ,QAAQ;QACRf;QACA,GAAIM,WAAW;YAAEA,UAAU;QAAK,IAAI,CAAC,CAAC;QACtC,GAAIF,oBAAoBkB,YAAY;YAAElB;QAAgB,IAAI,CAAC,CAAC;IAC9D;IAEA,MAAMN,mBAAmB,IAAI,EAAEW,WAAWW,MAAMT;IAEhD,OAAON,cAAc,QAAS,OAAkBe;AAClD,EAAC"}
1
+ {"version":3,"sources":["../src/createVersion.ts"],"sourcesContent":["import type { CreateVersion } from 'payload'\n\nimport { randomUUID } from 'node:crypto'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js'\nimport { projectVersionSnapshot } from './utilities/resolveSchema.js'\nimport { findLatestVersion } from './utilities/versionIndex.js'\nimport { isConditionalCheckFailed, putVersionAsLatest } from './utilities/writeItems.js'\n\n/**\n * Insert a new version for a collection's parent doc, maintaining the\n * `latest=true` invariant per parent.\n *\n * Two steps:\n * 1. Find the previous `latest=true` row — through the parent's pointer\n * (two key reads), or by reading the versions partition while pointers\n * aren't complete yet (`findLatestVersion`).\n * 2. `putVersionAsLatest` flips that row's `latest` to false, puts the new\n * row and moves the pointer in one atomic `TransactWriteItems` call. If\n * a crash occurs mid-flow none of it lands, so we never end up with two\n * `latest=true` rows or zero.\n *\n * If another save of the same document got in between the two steps, the\n * transaction's conditions fail and both steps are repeated.\n *\n * `autosave` is persisted on the row even though it isn't surfaced in\n * `TypeWithVersion` — `findVersions` filters by it.\n */\nexport const createVersion: CreateVersion = async function createVersion(\n this: DynamoAdapter,\n {\n autosave,\n collectionSlug,\n createdAt,\n parent,\n publishedLocale,\n returning,\n snapshot,\n updatedAt,\n versionData,\n },\n) {\n const partition = this.resolveVersionsPartition(collectionSlug)\n\n const id = randomUUID()\n // Project the snapshot against the parent collection's schema so that any\n // unknown keys (e.g. registration-form `confirm-password`) don't ride into\n // the version row alongside the legitimate doc state.\n const sanitizedVersionData = projectVersionSnapshot(\n this,\n { kind: 'collection', slug: collectionSlug },\n versionData as Record<string, unknown>,\n )\n const item: Record<string, unknown> = {\n id,\n parent,\n version: sanitizedVersionData,\n createdAt,\n updatedAt,\n latest: true,\n autosave,\n ...(snapshot ? { snapshot: true } : {}),\n ...(publishedLocale !== undefined ? { publishedLocale } : {}),\n }\n\n for (let attempt = 1; ; attempt++) {\n const { pointer, previous } = await findLatestVersion(this, partition, parent)\n try {\n await putVersionAsLatest(this, partition, item, previous, pointer)\n break\n } catch (err) {\n if (!isConditionalCheckFailed(err)) throw err\n if (attempt >= MAX_WRITE_ATTEMPTS) throw conflictError()\n await backoff(attempt)\n }\n }\n\n return returning === false ? (null as never) : (item as never)\n}\n"],"names":["randomUUID","backoff","conflictError","MAX_WRITE_ATTEMPTS","projectVersionSnapshot","findLatestVersion","isConditionalCheckFailed","putVersionAsLatest","createVersion","autosave","collectionSlug","createdAt","parent","publishedLocale","returning","snapshot","updatedAt","versionData","partition","resolveVersionsPartition","id","sanitizedVersionData","kind","slug","item","version","latest","undefined","attempt","pointer","previous","err"],"mappings":"AAEA,SAASA,UAAU,QAAQ,cAAa;AAIxC,SAASC,OAAO,EAAEC,aAAa,EAAEC,kBAAkB,QAAQ,4BAA2B;AACtF,SAASC,sBAAsB,QAAQ,+BAA8B;AACrE,SAASC,iBAAiB,QAAQ,8BAA6B;AAC/D,SAASC,wBAAwB,EAAEC,kBAAkB,QAAQ,4BAA2B;AAExF;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,MAAMC,gBAA+B,eAAeA,cAEzD,EACEC,QAAQ,EACRC,cAAc,EACdC,SAAS,EACTC,MAAM,EACNC,eAAe,EACfC,SAAS,EACTC,QAAQ,EACRC,SAAS,EACTC,WAAW,EACZ;IAED,MAAMC,YAAY,IAAI,CAACC,wBAAwB,CAACT;IAEhD,MAAMU,KAAKpB;IACX,0EAA0E;IAC1E,2EAA2E;IAC3E,sDAAsD;IACtD,MAAMqB,uBAAuBjB,uBAC3B,IAAI,EACJ;QAAEkB,MAAM;QAAcC,MAAMb;IAAe,GAC3CO;IAEF,MAAMO,OAAgC;QACpCJ;QACAR;QACAa,SAASJ;QACTV;QACAK;QACAU,QAAQ;QACRjB;QACA,GAAIM,WAAW;YAAEA,UAAU;QAAK,IAAI,CAAC,CAAC;QACtC,GAAIF,oBAAoBc,YAAY;YAAEd;QAAgB,IAAI,CAAC,CAAC;IAC9D;IAEA,IAAK,IAAIe,UAAU,IAAKA,UAAW;QACjC,MAAM,EAAEC,OAAO,EAAEC,QAAQ,EAAE,GAAG,MAAMzB,kBAAkB,IAAI,EAAEa,WAAWN;QACvE,IAAI;YACF,MAAML,mBAAmB,IAAI,EAAEW,WAAWM,MAAMM,UAAUD;YAC1D;QACF,EAAE,OAAOE,KAAK;YACZ,IAAI,CAACzB,yBAAyByB,MAAM,MAAMA;YAC1C,IAAIH,WAAWzB,oBAAoB,MAAMD;YACzC,MAAMD,QAAQ2B;QAChB;IACF;IAEA,OAAOd,cAAc,QAAS,OAAkBU;AAClD,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"deleteMany.d.ts","sourceRoot":"","sources":["../src/deleteMany.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AASzC;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,UAexB,CAAA"}
1
+ {"version":3,"file":"deleteMany.d.ts","sourceRoot":"","sources":["../src/deleteMany.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AASzC;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,UAgBxB,CAAA"}
@@ -1,6 +1,6 @@
1
+ import { hasCompanionRows } from './utilities/fieldIndexes.js';
1
2
  import { prepareCollectionWhere } from './utilities/prepareWhere.js';
2
3
  import { queryMatching } from './utilities/queryMatching.js';
3
- import { hasUniqueFields } from './utilities/uniqueGuards.js';
4
4
  import { deleteItems } from './utilities/writeItems.js';
5
5
  /**
6
6
  * v1 strategy: query-and-collect matches, then delete each by composite key
@@ -9,8 +9,11 @@ import { deleteItems } from './utilities/writeItems.js';
9
9
  const where = await prepareCollectionWhere(this, collection, rawWhere);
10
10
  const partition = this.resolvePartition(collection);
11
11
  const matched = await queryMatching(this, partition, where);
12
- await deleteItems(this, partition, matched, // Unique guards are released together with the documents that hold them.
13
- hasUniqueFields(this, collection) ? collection : undefined);
12
+ await deleteItems(this, partition, matched, // Unique guards and index rows go together with the documents they
13
+ // point at.
14
+ hasCompanionRows(this, collection) ? {
15
+ collection
16
+ } : {});
14
17
  };
15
18
 
16
19
  //# sourceMappingURL=deleteMany.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/deleteMany.ts"],"sourcesContent":["import type { DeleteMany } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { prepareCollectionWhere } from './utilities/prepareWhere.js'\nimport { queryMatching } from './utilities/queryMatching.js'\nimport { hasUniqueFields } from './utilities/uniqueGuards.js'\nimport { deleteItems } from './utilities/writeItems.js'\n\n/**\n * v1 strategy: query-and-collect matches, then delete each by composite key\n * in parallel. See `deleteItems` for why this isn't a `BatchWriteItem`.\n */\nexport const deleteMany: DeleteMany = async function deleteMany(\n this: DynamoAdapter,\n { collection, where: rawWhere },\n) {\n const where = await prepareCollectionWhere(this, collection, rawWhere)\n const partition = this.resolvePartition(collection)\n const matched = await queryMatching(this, partition, where)\n\n await deleteItems(\n this,\n partition,\n matched,\n // Unique guards are released together with the documents that hold them.\n hasUniqueFields(this, collection) ? collection : undefined,\n )\n}\n"],"names":["prepareCollectionWhere","queryMatching","hasUniqueFields","deleteItems","deleteMany","collection","where","rawWhere","partition","resolvePartition","matched","undefined"],"mappings":"AAIA,SAASA,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,aAAa,QAAQ,+BAA8B;AAC5D,SAASC,eAAe,QAAQ,8BAA6B;AAC7D,SAASC,WAAW,QAAQ,4BAA2B;AAEvD;;;CAGC,GACD,OAAO,MAAMC,aAAyB,eAAeA,WAEnD,EAAEC,UAAU,EAAEC,OAAOC,QAAQ,EAAE;IAE/B,MAAMD,QAAQ,MAAMN,uBAAuB,IAAI,EAAEK,YAAYE;IAC7D,MAAMC,YAAY,IAAI,CAACC,gBAAgB,CAACJ;IACxC,MAAMK,UAAU,MAAMT,cAAc,IAAI,EAAEO,WAAWF;IAErD,MAAMH,YACJ,IAAI,EACJK,WACAE,SACA,yEAAyE;IACzER,gBAAgB,IAAI,EAAEG,cAAcA,aAAaM;AAErD,EAAC"}
1
+ {"version":3,"sources":["../src/deleteMany.ts"],"sourcesContent":["import type { DeleteMany } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { hasCompanionRows } from './utilities/fieldIndexes.js'\nimport { prepareCollectionWhere } from './utilities/prepareWhere.js'\nimport { queryMatching } from './utilities/queryMatching.js'\nimport { deleteItems } from './utilities/writeItems.js'\n\n/**\n * v1 strategy: query-and-collect matches, then delete each by composite key\n * in parallel. See `deleteItems` for why this isn't a `BatchWriteItem`.\n */\nexport const deleteMany: DeleteMany = async function deleteMany(\n this: DynamoAdapter,\n { collection, where: rawWhere },\n) {\n const where = await prepareCollectionWhere(this, collection, rawWhere)\n const partition = this.resolvePartition(collection)\n const matched = await queryMatching(this, partition, where)\n\n await deleteItems(\n this,\n partition,\n matched,\n // Unique guards and index rows go together with the documents they\n // point at.\n hasCompanionRows(this, collection) ? { collection } : {},\n )\n}\n"],"names":["hasCompanionRows","prepareCollectionWhere","queryMatching","deleteItems","deleteMany","collection","where","rawWhere","partition","resolvePartition","matched"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,8BAA6B;AAC9D,SAASC,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,aAAa,QAAQ,+BAA8B;AAC5D,SAASC,WAAW,QAAQ,4BAA2B;AAEvD;;;CAGC,GACD,OAAO,MAAMC,aAAyB,eAAeA,WAEnD,EAAEC,UAAU,EAAEC,OAAOC,QAAQ,EAAE;IAE/B,MAAMD,QAAQ,MAAML,uBAAuB,IAAI,EAAEI,YAAYE;IAC7D,MAAMC,YAAY,IAAI,CAACC,gBAAgB,CAACJ;IACxC,MAAMK,UAAU,MAAMR,cAAc,IAAI,EAAEM,WAAWF;IAErD,MAAMH,YACJ,IAAI,EACJK,WACAE,SACA,mEAAmE;IACnE,YAAY;IACZV,iBAAiB,IAAI,EAAEK,cAAc;QAAEA;IAAW,IAAI,CAAC;AAE3D,EAAC"}
package/dist/deleteOne.js CHANGED
@@ -1,15 +1,15 @@
1
+ import { hasCompanionRows } from './utilities/fieldIndexes.js';
1
2
  import { findFirst } from './utilities/findFirst.js';
2
3
  import { prepareCollectionWhere } from './utilities/prepareWhere.js';
3
- import { hasUniqueFields } from './utilities/uniqueGuards.js';
4
4
  import { whereToId } from './utilities/whereToId.js';
5
5
  import { deleteItem } from './utilities/writeItems.js';
6
6
  export const deleteOne = async function deleteOne({ collection, returning, where: rawWhere }) {
7
7
  const where = await prepareCollectionWhere(this, collection, rawWhere);
8
8
  const partition = this.resolvePartition(collection);
9
9
  const idFromWhere = whereToId(where);
10
- // Releasing a document's unique guards needs its values, so those
11
- // collections always read the document first.
12
- const guarded = hasUniqueFields(this, collection);
10
+ // Removing a document's unique guards and index rows needs its values, so
11
+ // collections that have either always read the document first.
12
+ const guarded = hasCompanionRows(this, collection);
13
13
  // Fast path — pure id-equality lets us delete and capture the doc in one call.
14
14
  if (idFromWhere !== null && !guarded) {
15
15
  const deleted = await deleteItem(this, partition, idFromWhere, {
@@ -30,7 +30,7 @@ export const deleteOne = async function deleteOne({ collection, returning, where
30
30
  return null;
31
31
  }
32
32
  await deleteItem(this, partition, found['id'], guarded ? {
33
- unique: {
33
+ companions: {
34
34
  collection,
35
35
  doc: found
36
36
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/deleteOne.ts"],"sourcesContent":["import type { DeleteOne } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { findFirst } from './utilities/findFirst.js'\nimport { prepareCollectionWhere } from './utilities/prepareWhere.js'\nimport { hasUniqueFields } from './utilities/uniqueGuards.js'\nimport { whereToId } from './utilities/whereToId.js'\nimport { deleteItem } from './utilities/writeItems.js'\n\nexport const deleteOne: DeleteOne = async function deleteOne(\n this: DynamoAdapter,\n { collection, returning, where: rawWhere },\n) {\n const where = await prepareCollectionWhere(this, collection, rawWhere)\n const partition = this.resolvePartition(collection)\n const idFromWhere = whereToId(where)\n // Releasing a document's unique guards needs its values, so those\n // collections always read the document first.\n const guarded = hasUniqueFields(this, collection)\n\n // Fast path — pure id-equality lets us delete and capture the doc in one call.\n if (idFromWhere !== null && !guarded) {\n const deleted = await deleteItem(this, partition, idFromWhere, { returnOld: true })\n if (returning === false) {\n return null as never\n }\n return deleted as never\n }\n\n // Slow path — locate (a `GetItem` when `where` is an id), then delete by\n // composite key.\n const found = await findFirst(this, { partition, where })\n if (!found) {\n return null as never\n }\n\n await deleteItem(\n this,\n partition,\n found['id'],\n guarded ? { unique: { collection, doc: found } } : {},\n )\n\n if (returning === false) {\n return null as never\n }\n return found as never\n}\n"],"names":["findFirst","prepareCollectionWhere","hasUniqueFields","whereToId","deleteItem","deleteOne","collection","returning","where","rawWhere","partition","resolvePartition","idFromWhere","guarded","deleted","returnOld","found","unique","doc"],"mappings":"AAIA,SAASA,SAAS,QAAQ,2BAA0B;AACpD,SAASC,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,eAAe,QAAQ,8BAA6B;AAC7D,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,UAAU,QAAQ,4BAA2B;AAEtD,OAAO,MAAMC,YAAuB,eAAeA,UAEjD,EAAEC,UAAU,EAAEC,SAAS,EAAEC,OAAOC,QAAQ,EAAE;IAE1C,MAAMD,QAAQ,MAAMP,uBAAuB,IAAI,EAAEK,YAAYG;IAC7D,MAAMC,YAAY,IAAI,CAACC,gBAAgB,CAACL;IACxC,MAAMM,cAAcT,UAAUK;IAC9B,kEAAkE;IAClE,8CAA8C;IAC9C,MAAMK,UAAUX,gBAAgB,IAAI,EAAEI;IAEtC,+EAA+E;IAC/E,IAAIM,gBAAgB,QAAQ,CAACC,SAAS;QACpC,MAAMC,UAAU,MAAMV,WAAW,IAAI,EAAEM,WAAWE,aAAa;YAAEG,WAAW;QAAK;QACjF,IAAIR,cAAc,OAAO;YACvB,OAAO;QACT;QACA,OAAOO;IACT;IAEA,yEAAyE;IACzE,iBAAiB;IACjB,MAAME,QAAQ,MAAMhB,UAAU,IAAI,EAAE;QAAEU;QAAWF;IAAM;IACvD,IAAI,CAACQ,OAAO;QACV,OAAO;IACT;IAEA,MAAMZ,WACJ,IAAI,EACJM,WACAM,KAAK,CAAC,KAAK,EACXH,UAAU;QAAEI,QAAQ;YAAEX;YAAYY,KAAKF;QAAM;IAAE,IAAI,CAAC;IAGtD,IAAIT,cAAc,OAAO;QACvB,OAAO;IACT;IACA,OAAOS;AACT,EAAC"}
1
+ {"version":3,"sources":["../src/deleteOne.ts"],"sourcesContent":["import type { DeleteOne } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { hasCompanionRows } from './utilities/fieldIndexes.js'\nimport { findFirst } from './utilities/findFirst.js'\nimport { prepareCollectionWhere } from './utilities/prepareWhere.js'\nimport { whereToId } from './utilities/whereToId.js'\nimport { deleteItem } from './utilities/writeItems.js'\n\nexport const deleteOne: DeleteOne = async function deleteOne(\n this: DynamoAdapter,\n { collection, returning, where: rawWhere },\n) {\n const where = await prepareCollectionWhere(this, collection, rawWhere)\n const partition = this.resolvePartition(collection)\n const idFromWhere = whereToId(where)\n // Removing a document's unique guards and index rows needs its values, so\n // collections that have either always read the document first.\n const guarded = hasCompanionRows(this, collection)\n\n // Fast path — pure id-equality lets us delete and capture the doc in one call.\n if (idFromWhere !== null && !guarded) {\n const deleted = await deleteItem(this, partition, idFromWhere, { returnOld: true })\n if (returning === false) {\n return null as never\n }\n return deleted as never\n }\n\n // Slow path — locate (a `GetItem` when `where` is an id), then delete by\n // composite key.\n const found = await findFirst(this, { partition, where })\n if (!found) {\n return null as never\n }\n\n await deleteItem(\n this,\n partition,\n found['id'],\n guarded ? { companions: { collection, doc: found } } : {},\n )\n\n if (returning === false) {\n return null as never\n }\n return found as never\n}\n"],"names":["hasCompanionRows","findFirst","prepareCollectionWhere","whereToId","deleteItem","deleteOne","collection","returning","where","rawWhere","partition","resolvePartition","idFromWhere","guarded","deleted","returnOld","found","companions","doc"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,8BAA6B;AAC9D,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,SAAS,QAAQ,2BAA0B;AACpD,SAASC,UAAU,QAAQ,4BAA2B;AAEtD,OAAO,MAAMC,YAAuB,eAAeA,UAEjD,EAAEC,UAAU,EAAEC,SAAS,EAAEC,OAAOC,QAAQ,EAAE;IAE1C,MAAMD,QAAQ,MAAMN,uBAAuB,IAAI,EAAEI,YAAYG;IAC7D,MAAMC,YAAY,IAAI,CAACC,gBAAgB,CAACL;IACxC,MAAMM,cAAcT,UAAUK;IAC9B,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAMK,UAAUb,iBAAiB,IAAI,EAAEM;IAEvC,+EAA+E;IAC/E,IAAIM,gBAAgB,QAAQ,CAACC,SAAS;QACpC,MAAMC,UAAU,MAAMV,WAAW,IAAI,EAAEM,WAAWE,aAAa;YAAEG,WAAW;QAAK;QACjF,IAAIR,cAAc,OAAO;YACvB,OAAO;QACT;QACA,OAAOO;IACT;IAEA,yEAAyE;IACzE,iBAAiB;IACjB,MAAME,QAAQ,MAAMf,UAAU,IAAI,EAAE;QAAES;QAAWF;IAAM;IACvD,IAAI,CAACQ,OAAO;QACV,OAAO;IACT;IAEA,MAAMZ,WACJ,IAAI,EACJM,WACAM,KAAK,CAAC,KAAK,EACXH,UAAU;QAAEI,YAAY;YAAEX;YAAYY,KAAKF;QAAM;IAAE,IAAI,CAAC;IAG1D,IAAIT,cAAc,OAAO;QACvB,OAAO;IACT;IACA,OAAOS;AACT,EAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"deleteVersions.d.ts","sourceRoot":"","sources":["../src/deleteVersions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAQ7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,cAsB5B,CAAA"}
1
+ {"version":3,"file":"deleteVersions.d.ts","sourceRoot":"","sources":["../src/deleteVersions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAQ7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,cAwB5B,CAAA"}
@@ -16,7 +16,11 @@ import { deleteItems } from './utilities/writeItems.js';
16
16
  kind: collection ? 'collection' : 'global',
17
17
  slug
18
18
  }, where, locale));
19
- await deleteItems(this, partition, matched);
19
+ // Each version's index row, and its parent's pointer if it was the latest,
20
+ // go with it.
21
+ await deleteItems(this, partition, matched, {
22
+ versions: true
23
+ });
20
24
  };
21
25
 
22
26
  //# sourceMappingURL=deleteVersions.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/deleteVersions.ts"],"sourcesContent":["import type { DeleteVersions } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { prepareVersionsWhere } from './utilities/prepareWhere.js'\nimport { queryMatching } from './utilities/queryMatching.js'\nimport { deleteItems } from './utilities/writeItems.js'\n\n/**\n * Same query + parallel delete pattern as `deleteMany`, but routes to the\n * versions partition for either a collection (`collection`) or a global\n * (`globalSlug`). Payload guarantees exactly one of those two slugs is\n * present.\n */\nexport const deleteVersions: DeleteVersions = async function deleteVersions(\n this: DynamoAdapter,\n { collection, globalSlug, locale, where },\n) {\n const slug = collection ?? globalSlug\n if (!slug) {\n throw new Error('payload-ddb: deleteVersions requires either `collection` or `globalSlug`.')\n }\n\n const partition = this.resolveVersionsPartition(slug)\n const matched = await queryMatching(\n this,\n partition,\n await prepareVersionsWhere(\n this,\n { kind: collection ? 'collection' : 'global', slug },\n where,\n locale,\n ),\n )\n\n await deleteItems(this, partition, matched)\n}\n"],"names":["prepareVersionsWhere","queryMatching","deleteItems","deleteVersions","collection","globalSlug","locale","where","slug","Error","partition","resolveVersionsPartition","matched","kind"],"mappings":"AAIA,SAASA,oBAAoB,QAAQ,8BAA6B;AAClE,SAASC,aAAa,QAAQ,+BAA8B;AAC5D,SAASC,WAAW,QAAQ,4BAA2B;AAEvD;;;;;CAKC,GACD,OAAO,MAAMC,iBAAiC,eAAeA,eAE3D,EAAEC,UAAU,EAAEC,UAAU,EAAEC,MAAM,EAAEC,KAAK,EAAE;IAEzC,MAAMC,OAAOJ,cAAcC;IAC3B,IAAI,CAACG,MAAM;QACT,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,YAAY,IAAI,CAACC,wBAAwB,CAACH;IAChD,MAAMI,UAAU,MAAMX,cACpB,IAAI,EACJS,WACA,MAAMV,qBACJ,IAAI,EACJ;QAAEa,MAAMT,aAAa,eAAe;QAAUI;IAAK,GACnDD,OACAD;IAIJ,MAAMJ,YAAY,IAAI,EAAEQ,WAAWE;AACrC,EAAC"}
1
+ {"version":3,"sources":["../src/deleteVersions.ts"],"sourcesContent":["import type { DeleteVersions } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport { prepareVersionsWhere } from './utilities/prepareWhere.js'\nimport { queryMatching } from './utilities/queryMatching.js'\nimport { deleteItems } from './utilities/writeItems.js'\n\n/**\n * Same query + parallel delete pattern as `deleteMany`, but routes to the\n * versions partition for either a collection (`collection`) or a global\n * (`globalSlug`). Payload guarantees exactly one of those two slugs is\n * present.\n */\nexport const deleteVersions: DeleteVersions = async function deleteVersions(\n this: DynamoAdapter,\n { collection, globalSlug, locale, where },\n) {\n const slug = collection ?? globalSlug\n if (!slug) {\n throw new Error('payload-ddb: deleteVersions requires either `collection` or `globalSlug`.')\n }\n\n const partition = this.resolveVersionsPartition(slug)\n const matched = await queryMatching(\n this,\n partition,\n await prepareVersionsWhere(\n this,\n { kind: collection ? 'collection' : 'global', slug },\n where,\n locale,\n ),\n )\n\n // Each version's index row, and its parent's pointer if it was the latest,\n // go with it.\n await deleteItems(this, partition, matched, { versions: true })\n}\n"],"names":["prepareVersionsWhere","queryMatching","deleteItems","deleteVersions","collection","globalSlug","locale","where","slug","Error","partition","resolveVersionsPartition","matched","kind","versions"],"mappings":"AAIA,SAASA,oBAAoB,QAAQ,8BAA6B;AAClE,SAASC,aAAa,QAAQ,+BAA8B;AAC5D,SAASC,WAAW,QAAQ,4BAA2B;AAEvD;;;;;CAKC,GACD,OAAO,MAAMC,iBAAiC,eAAeA,eAE3D,EAAEC,UAAU,EAAEC,UAAU,EAAEC,MAAM,EAAEC,KAAK,EAAE;IAEzC,MAAMC,OAAOJ,cAAcC;IAC3B,IAAI,CAACG,MAAM;QACT,MAAM,IAAIC,MAAM;IAClB;IAEA,MAAMC,YAAY,IAAI,CAACC,wBAAwB,CAACH;IAChD,MAAMI,UAAU,MAAMX,cACpB,IAAI,EACJS,WACA,MAAMV,qBACJ,IAAI,EACJ;QAAEa,MAAMT,aAAa,eAAe;QAAUI;IAAK,GACnDD,OACAD;IAIJ,2EAA2E;IAC3E,cAAc;IACd,MAAMJ,YAAY,IAAI,EAAEQ,WAAWE,SAAS;QAAEE,UAAU;IAAK;AAC/D,EAAC"}