@open-aih/payload-ddb 4.1.0 → 4.2.0-beta.20261113195548
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.
- package/README.md +136 -26
- package/dist/create.js +4 -6
- package/dist/create.js.map +1 -1
- package/dist/createGlobalVersion.d.ts.map +1 -1
- package/dist/createGlobalVersion.js +15 -11
- package/dist/createGlobalVersion.js.map +1 -1
- package/dist/createVersion.d.ts +11 -8
- package/dist/createVersion.d.ts.map +1 -1
- package/dist/createVersion.js +25 -28
- package/dist/createVersion.js.map +1 -1
- package/dist/deleteMany.d.ts.map +1 -1
- package/dist/deleteMany.js +6 -3
- package/dist/deleteMany.js.map +1 -1
- package/dist/deleteOne.js +5 -5
- package/dist/deleteOne.js.map +1 -1
- package/dist/deleteVersions.d.ts.map +1 -1
- package/dist/deleteVersions.js +5 -1
- package/dist/deleteVersions.js.map +1 -1
- package/dist/find.d.ts +16 -13
- package/dist/find.d.ts.map +1 -1
- package/dist/find.js +85 -24
- package/dist/find.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +5 -0
- package/dist/init.js.map +1 -1
- package/dist/queryDrafts.d.ts.map +1 -1
- package/dist/queryDrafts.js +5 -6
- package/dist/queryDrafts.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/updateMany.js +2 -4
- package/dist/updateMany.js.map +1 -1
- package/dist/updateOne.js +2 -4
- package/dist/updateOne.js.map +1 -1
- package/dist/upsert.js +1 -3
- package/dist/upsert.js.map +1 -1
- package/dist/utilities/applySorts.d.ts +5 -2
- package/dist/utilities/applySorts.d.ts.map +1 -1
- package/dist/utilities/applySorts.js +11 -6
- package/dist/utilities/applySorts.js.map +1 -1
- package/dist/utilities/backfillIndexes.d.ts +71 -0
- package/dist/utilities/backfillIndexes.d.ts.map +1 -0
- package/dist/utilities/backfillIndexes.js +149 -0
- package/dist/utilities/backfillIndexes.js.map +1 -0
- package/dist/utilities/batchGet.d.ts +15 -0
- package/dist/utilities/batchGet.d.ts.map +1 -0
- package/dist/utilities/batchGet.js +56 -0
- package/dist/utilities/batchGet.js.map +1 -0
- package/dist/utilities/draftsFallback.d.ts.map +1 -1
- package/dist/utilities/draftsFallback.js +3 -6
- package/dist/utilities/draftsFallback.js.map +1 -1
- package/dist/utilities/fieldIndexes.d.ts +130 -0
- package/dist/utilities/fieldIndexes.d.ts.map +1 -0
- package/dist/utilities/fieldIndexes.js +286 -0
- package/dist/utilities/fieldIndexes.js.map +1 -0
- package/dist/utilities/findFirst.d.ts +2 -0
- package/dist/utilities/findFirst.d.ts.map +1 -1
- package/dist/utilities/findFirst.js +5 -0
- package/dist/utilities/findFirst.js.map +1 -1
- package/dist/utilities/indexLookup.d.ts +76 -0
- package/dist/utilities/indexLookup.d.ts.map +1 -0
- package/dist/utilities/indexLookup.js +409 -0
- package/dist/utilities/indexLookup.js.map +1 -0
- package/dist/utilities/indexState.d.ts +13 -0
- package/dist/utilities/indexState.d.ts.map +1 -0
- package/dist/utilities/indexState.js +199 -0
- package/dist/utilities/indexState.js.map +1 -0
- package/dist/utilities/queryCount.d.ts +8 -1
- package/dist/utilities/queryCount.d.ts.map +1 -1
- package/dist/utilities/queryCount.js +8 -2
- package/dist/utilities/queryCount.js.map +1 -1
- package/dist/utilities/queryMatching.d.ts +12 -5
- package/dist/utilities/queryMatching.d.ts.map +1 -1
- package/dist/utilities/queryMatching.js +11 -5
- package/dist/utilities/queryMatching.js.map +1 -1
- package/dist/utilities/queryPartition.d.ts +2 -2
- package/dist/utilities/queryPartition.js +2 -2
- package/dist/utilities/queryPartition.js.map +1 -1
- package/dist/utilities/uniqueGuards.d.ts +34 -0
- package/dist/utilities/uniqueGuards.d.ts.map +1 -1
- package/dist/utilities/uniqueGuards.js +50 -24
- package/dist/utilities/uniqueGuards.js.map +1 -1
- package/dist/utilities/updateVersionRow.js +2 -1
- package/dist/utilities/updateVersionRow.js.map +1 -1
- package/dist/utilities/versionIndex.d.ts +45 -0
- package/dist/utilities/versionIndex.d.ts.map +1 -0
- package/dist/utilities/versionIndex.js +103 -0
- package/dist/utilities/versionIndex.js.map +1 -0
- package/dist/utilities/writeItems.d.ts +63 -20
- package/dist/utilities/writeItems.d.ts.map +1 -1
- package/dist/utilities/writeItems.js +360 -68
- package/dist/utilities/writeItems.js.map +1 -1
- package/package.json +1 -1
- package/dist/utilities/backfillUniqueGuards.d.ts +0 -42
- package/dist/utilities/backfillUniqueGuards.d.ts.map +0 -1
- package/dist/utilities/backfillUniqueGuards.js +0 -39
- 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
|
|
10
|
-
>
|
|
11
|
-
>
|
|
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
|
-
- **
|
|
111
|
-
|
|
112
|
-
|
|
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,92 @@ 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
|
+
An `or` qualifies as a whole when every one of its branches has such a
|
|
183
|
+
clause — the branches are looked up separately and combined — but not when
|
|
184
|
+
one branch would need the partition anyway. The rest of the `where` is then
|
|
185
|
+
evaluated on the documents selected. This covers login by `email`, pages by
|
|
186
|
+
`slug`, relationship population (`id in [...]`), filters on a relationship,
|
|
187
|
+
join fields, date or number ranges, and Payload's lookup of an upload by any
|
|
188
|
+
of its filenames.
|
|
189
|
+
|
|
190
|
+
- **Which fields.** `index: true` on a `text`, `email`, `number`, `date`,
|
|
191
|
+
`relationship` or `upload` field — including `hasMany` fields (one row per
|
|
192
|
+
element), fields in groups and named tabs, and localized fields (per
|
|
193
|
+
locale). Payload sets the flag itself on the field a join points at.
|
|
194
|
+
Not indexed: fields in arrays or blocks, polymorphic relationships, types
|
|
195
|
+
where equality rarely narrows a read (`checkbox`, `select`), and the
|
|
196
|
+
timestamps Payload manages (`createdAt`, `updatedAt`, `deletedAt`), where
|
|
197
|
+
an index would turn every insert or update into a transaction.
|
|
198
|
+
- **Sorted pages.** A `find` sorted by one single-valued indexed field walks
|
|
199
|
+
that field's index in order and stops when the page is full, instead of
|
|
200
|
+
reading and sorting the collection. With `pagination: false` nothing else
|
|
201
|
+
is read. With pagination, the total comes from a `COUNT` pass over the
|
|
202
|
+
partition, which returns no documents but is still billed for the read.
|
|
203
|
+
Not used on drafts-enabled collections, for sorts on several fields, or
|
|
204
|
+
for pages more than 2,000 documents deep.
|
|
205
|
+
- **Consistency.** Index rows are written in the same transaction as their
|
|
206
|
+
document and read with strong consistency, so a lookup sees a document as
|
|
207
|
+
soon as it is written — which a DynamoDB GSI can't promise.
|
|
208
|
+
- **Write cost.** A write that adds or removes index rows is a transaction:
|
|
209
|
+
twice the write units for the document, plus the rows. An update that
|
|
210
|
+
leaves the indexed fields alone costs what it did before. A single-valued
|
|
211
|
+
indexed field has a row for every document, blank ones included, so
|
|
212
|
+
inserts into a collection with one are always transactions.
|
|
213
|
+
- **Large matches.** A lookup that finds more than 256 candidate documents
|
|
214
|
+
falls back to reading the partition, which is the cheaper read by then.
|
|
215
|
+
- **Opting out.** `indexes: false` stops the index rows, the pointers and
|
|
216
|
+
the routed lookups. Lookups by `id` and unique enforcement are unaffected.
|
|
217
|
+
Every instance sharing a table must use the same setting.
|
|
218
|
+
|
|
219
|
+
**Versions.** Version rows are indexed by their `parent`, so listing or
|
|
220
|
+
pruning one document's versions reads only those. Each versioned document
|
|
221
|
+
(and each versioned global) also has a pointer to its latest version: saving
|
|
222
|
+
finds the version to demote in two key reads, and listing drafts fetches one
|
|
223
|
+
version per document rather than every version of every document.
|
|
224
|
+
|
|
225
|
+
**Existing data.** An index is only used once it is known to cover every
|
|
226
|
+
row. A partition that is empty when the adapter starts qualifies straight
|
|
227
|
+
away, which covers new tables and new collections. For one that already has
|
|
228
|
+
rows — after upgrading, or after adding `index: true` to a field — run the
|
|
229
|
+
backfill once; until then its lookups keep reading the partition:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { backfillIndexes } from '@aih-software/payload-ddb'
|
|
233
|
+
|
|
234
|
+
const report = await backfillIndexes(payload)
|
|
235
|
+
// report.collections[slug].ready lists the indexes now in use
|
|
236
|
+
// report.versions[slug] covers that collection's or global's versions
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The backfill is safe to run while the application is serving, and safe to
|
|
240
|
+
re-run. It also removes stray index rows, which an interrupted write can
|
|
241
|
+
leave behind (lookups ignore them, so they cost a little space, not
|
|
242
|
+
correctness). When upgrading from a version before 4.2, finish the rollout
|
|
243
|
+
first: older instances write rows without their index rows, so run the
|
|
244
|
+
backfill once after the last one is gone.
|
|
245
|
+
|
|
156
246
|
### Join fields
|
|
157
247
|
|
|
158
|
-
`join` fields are resolved for `find`, `findByID` and draft queries: one
|
|
159
|
-
per target collection for all returned documents, grouped in memory,
|
|
160
|
-
`limit`, `page`, `sort`, `where` and `count` applied per parent document.
|
|
161
|
-
|
|
162
|
-
|
|
248
|
+
`join` fields are resolved for `find`, `findByID` and draft queries: one
|
|
249
|
+
lookup per target collection for all returned documents, grouped in memory,
|
|
250
|
+
with `limit`, `page`, `sort`, `where` and `count` applied per parent document.
|
|
251
|
+
The lookup uses the index on the field the join points at, so it doesn't read
|
|
252
|
+
the target collection's partition. Polymorphic joins (`collection: [...]`)
|
|
253
|
+
return `{ relationTo, value }` entries. Joined documents are read from the
|
|
254
|
+
target's published rows.
|
|
163
255
|
|
|
164
256
|
### Write guarantees
|
|
165
257
|
|
|
@@ -171,6 +263,10 @@ Joined documents are read from the target's published rows.
|
|
|
171
263
|
per adapter call: Payload's own `update` operation fills the fields missing
|
|
172
264
|
from a patch with the values it read beforehand, so two concurrent
|
|
173
265
|
`payload.update` calls on one document can still overwrite each other.
|
|
266
|
+
- **One latest version.** Creating a version demotes the previous latest and
|
|
267
|
+
moves the document's pointer in one transaction that checks nobody else got
|
|
268
|
+
there first, so saving a document several times at once still leaves
|
|
269
|
+
exactly one version marked latest.
|
|
174
270
|
- **Jobs are claimed once.** `updateJobs` uses the same check, so two workers
|
|
175
271
|
polling the queue can't both take the same job.
|
|
176
272
|
- **Bulk writes are bounded.** `updateMany`, `deleteMany` and `deleteVersions`
|
|
@@ -190,34 +286,42 @@ values (`null`, `''`) are not guarded. Unique fields inside arrays or blocks,
|
|
|
190
286
|
and `hasMany` fields, are not enforced.
|
|
191
287
|
|
|
192
288
|
Writes to a collection with unique fields are transactional and cost twice the
|
|
193
|
-
write units
|
|
289
|
+
write units.
|
|
194
290
|
|
|
195
291
|
Documents written before this was enforced have no guards until they are next
|
|
196
|
-
saved.
|
|
292
|
+
saved. `backfillIndexes` (see *Indexes*) covers them in the same pass:
|
|
197
293
|
|
|
198
294
|
```ts
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const report = await backfillUniqueGuards(payload)
|
|
295
|
+
const report = await backfillIndexes(payload)
|
|
202
296
|
// report.collections[slug].conflicts lists pre-existing duplicates
|
|
203
297
|
```
|
|
204
298
|
|
|
299
|
+
A unique field with unresolved duplicates stays out of use for lookups until
|
|
300
|
+
they are fixed and the pass is run again. `backfillUniqueGuards` is kept as an
|
|
301
|
+
alias.
|
|
302
|
+
|
|
205
303
|
## Known limits / future work
|
|
206
304
|
|
|
207
|
-
- **
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
305
|
+
- **Unindexed filters and sorts read the partition.** That includes the
|
|
306
|
+
default sort on `createdAt`, sorts on several fields, and any filter with
|
|
307
|
+
no indexed clause. The matches are sorted and paged in memory.
|
|
308
|
+
- **Totals are counted.** A paginated `find` that reads its page through an
|
|
309
|
+
index still counts the matches with a pass over the partition.
|
|
310
|
+
- **Drafts are listed in full.** A drafts listing fetches the latest version
|
|
311
|
+
of every document — one read each, not the whole versions partition — and
|
|
312
|
+
then filters, sorts and pages in memory. `find` on a drafts-enabled
|
|
313
|
+
collection does the same for documents it hasn't already matched.
|
|
314
|
+
- **A document's versions are read together.** Payload looks up a document's
|
|
315
|
+
(or global's) newest version by sort before each save, which reads all of
|
|
316
|
+
that document's versions — bounded by `versions.maxPerDoc`.
|
|
213
317
|
- **Payload-level transactions are no-op.** `beginTransaction` returns `null`;
|
|
214
318
|
commit/rollback do nothing. Per-method atomicity (e.g. `createVersion`'s
|
|
215
319
|
flip+put) uses DynamoDB `TransactWriteItems` already; full Payload-context
|
|
216
320
|
transaction buffering across multiple adapter calls is its own milestone.
|
|
217
|
-
- **
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
321
|
+
- **Relationship paths read the related collection.** `author.name equals
|
|
322
|
+
'Ada'` first finds the matching authors — a partition read unless `name` is
|
|
323
|
+
indexed — and then looks the documents up through the index on `author`,
|
|
324
|
+
when it has one.
|
|
221
325
|
- **Joins don't see unpublished drafts** of the joined collection.
|
|
222
326
|
- **`locale: 'all'`** in a filter or sort falls back to the default locale.
|
|
223
327
|
- **`like` matches its operand as one substring.** Payload's Mongo adapter
|
|
@@ -264,6 +368,12 @@ The public publish uses npm
|
|
|
264
368
|
no npm token to rotate. The trusted publisher configured on npmjs.com must
|
|
265
369
|
name this repository and the workflow file `release.yml`.
|
|
266
370
|
|
|
371
|
+
**Prereleases.** To try a change in a real project before releasing it, push
|
|
372
|
+
the commit to the `beta` branch (`git push origin HEAD:beta --force`). The
|
|
373
|
+
same workflow publishes it to both registries as `X.Y.Z-beta.<timestamp>`
|
|
374
|
+
under the `beta` dist-tag — install it with `@open-aih/payload-ddb@beta` —
|
|
375
|
+
and commits, tags and merges nothing. `latest` is untouched.
|
|
376
|
+
|
|
267
377
|
## License
|
|
268
378
|
|
|
269
379
|
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. `
|
|
30
|
-
// unique
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
collection
|
|
35
|
-
}
|
|
32
|
+
collection,
|
|
33
|
+
mustNotExist: true
|
|
36
34
|
});
|
|
37
35
|
break;
|
|
38
36
|
} catch (err) {
|
package/dist/create.js.map
CHANGED
|
@@ -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. `
|
|
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;
|
|
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 {
|
|
2
|
+
import { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js';
|
|
3
3
|
import { projectVersionSnapshot } from './utilities/resolveSchema.js';
|
|
4
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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"}
|
package/dist/createVersion.d.ts
CHANGED
|
@@ -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
|
|
7
|
-
* 1.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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;
|
|
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"}
|
package/dist/createVersion.js
CHANGED
|
@@ -1,41 +1,28 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { backoff, conflictError, MAX_WRITE_ATTEMPTS } from './utilities/optimistic.js';
|
|
3
3
|
import { projectVersionSnapshot } from './utilities/resolveSchema.js';
|
|
4
|
-
import {
|
|
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
|
|
10
|
-
* 1.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
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 {
|
|
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"}
|
package/dist/deleteMany.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/deleteMany.js
CHANGED
|
@@ -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
|
|
13
|
-
|
|
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
|
package/dist/deleteMany.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/deleteMany.ts"],"sourcesContent":["import type { DeleteMany } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport {
|
|
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
|
-
//
|
|
11
|
-
// collections always read the document first.
|
|
12
|
-
const guarded =
|
|
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
|
-
|
|
33
|
+
companions: {
|
|
34
34
|
collection,
|
|
35
35
|
doc: found
|
|
36
36
|
}
|
package/dist/deleteOne.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/deleteOne.ts"],"sourcesContent":["import type { DeleteOne } from 'payload'\n\nimport type { DynamoAdapter } from './types.js'\n\nimport {
|
|
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,
|
|
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"}
|
package/dist/deleteVersions.js
CHANGED
|
@@ -16,7 +16,11 @@ import { deleteItems } from './utilities/writeItems.js';
|
|
|
16
16
|
kind: collection ? 'collection' : 'global',
|
|
17
17
|
slug
|
|
18
18
|
}, where, locale));
|
|
19
|
-
|
|
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;
|
|
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"}
|