@open-aih/payload-ddb 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/dist/connect.d.ts +3 -0
  4. package/dist/connect.d.ts.map +1 -0
  5. package/dist/connect.js +6 -0
  6. package/dist/connect.js.map +1 -0
  7. package/dist/count.d.ts +3 -0
  8. package/dist/count.d.ts.map +1 -0
  9. package/dist/count.js +9 -0
  10. package/dist/count.js.map +1 -0
  11. package/dist/countGlobalVersions.d.ts +3 -0
  12. package/dist/countGlobalVersions.d.ts.map +1 -0
  13. package/dist/countGlobalVersions.js +9 -0
  14. package/dist/countGlobalVersions.js.map +1 -0
  15. package/dist/countVersions.d.ts +3 -0
  16. package/dist/countVersions.d.ts.map +1 -0
  17. package/dist/countVersions.js +9 -0
  18. package/dist/countVersions.js.map +1 -0
  19. package/dist/create.d.ts +3 -0
  20. package/dist/create.d.ts.map +1 -0
  21. package/dist/create.js +38 -0
  22. package/dist/create.js.map +1 -0
  23. package/dist/createGlobal.d.ts +8 -0
  24. package/dist/createGlobal.d.ts.map +1 -0
  25. package/dist/createGlobal.js +29 -0
  26. package/dist/createGlobal.js.map +1 -0
  27. package/dist/createGlobalVersion.d.ts +8 -0
  28. package/dist/createGlobalVersion.d.ts.map +1 -0
  29. package/dist/createGlobalVersion.js +80 -0
  30. package/dist/createGlobalVersion.js.map +1 -0
  31. package/dist/createVersion.d.ts +18 -0
  32. package/dist/createVersion.d.ts.map +1 -0
  33. package/dist/createVersion.js +106 -0
  34. package/dist/createVersion.js.map +1 -0
  35. package/dist/deleteMany.d.ts +12 -0
  36. package/dist/deleteMany.d.ts.map +1 -0
  37. package/dist/deleteMany.js +27 -0
  38. package/dist/deleteMany.js.map +1 -0
  39. package/dist/deleteOne.d.ts +3 -0
  40. package/dist/deleteOne.d.ts.map +1 -0
  41. package/dist/deleteOne.js +48 -0
  42. package/dist/deleteOne.js.map +1 -0
  43. package/dist/deleteVersions.d.ts +9 -0
  44. package/dist/deleteVersions.d.ts.map +1 -0
  45. package/dist/deleteVersions.js +28 -0
  46. package/dist/deleteVersions.js.map +1 -0
  47. package/dist/destroy.d.ts +3 -0
  48. package/dist/destroy.d.ts.map +1 -0
  49. package/dist/destroy.js +10 -0
  50. package/dist/destroy.js.map +1 -0
  51. package/dist/find.d.ts +20 -0
  52. package/dist/find.d.ts.map +1 -0
  53. package/dist/find.js +54 -0
  54. package/dist/find.js.map +1 -0
  55. package/dist/findDistinct.d.ts +13 -0
  56. package/dist/findDistinct.d.ts.map +1 -0
  57. package/dist/findDistinct.js +52 -0
  58. package/dist/findDistinct.js.map +1 -0
  59. package/dist/findGlobal.d.ts +11 -0
  60. package/dist/findGlobal.d.ts.map +1 -0
  61. package/dist/findGlobal.js +34 -0
  62. package/dist/findGlobal.js.map +1 -0
  63. package/dist/findGlobalVersions.d.ts +3 -0
  64. package/dist/findGlobalVersions.d.ts.map +1 -0
  65. package/dist/findGlobalVersions.js +32 -0
  66. package/dist/findGlobalVersions.js.map +1 -0
  67. package/dist/findOne.d.ts +9 -0
  68. package/dist/findOne.d.ts.map +1 -0
  69. package/dist/findOne.js +22 -0
  70. package/dist/findOne.js.map +1 -0
  71. package/dist/findVersions.d.ts +10 -0
  72. package/dist/findVersions.d.ts.map +1 -0
  73. package/dist/findVersions.js +42 -0
  74. package/dist/findVersions.js.map +1 -0
  75. package/dist/index.d.ts +7 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +98 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/init.d.ts +16 -0
  80. package/dist/init.d.ts.map +1 -0
  81. package/dist/init.js +24 -0
  82. package/dist/init.js.map +1 -0
  83. package/dist/queryDrafts.d.ts +3 -0
  84. package/dist/queryDrafts.d.ts.map +1 -0
  85. package/dist/queryDrafts.js +103 -0
  86. package/dist/queryDrafts.js.map +1 -0
  87. package/dist/types.d.ts +119 -0
  88. package/dist/types.d.ts.map +1 -0
  89. package/dist/types.js +3 -0
  90. package/dist/types.js.map +1 -0
  91. package/dist/updateGlobal.d.ts +9 -0
  92. package/dist/updateGlobal.d.ts.map +1 -0
  93. package/dist/updateGlobal.js +45 -0
  94. package/dist/updateGlobal.js.map +1 -0
  95. package/dist/updateGlobalVersion.d.ts +8 -0
  96. package/dist/updateGlobalVersion.d.ts.map +1 -0
  97. package/dist/updateGlobalVersion.js +59 -0
  98. package/dist/updateGlobalVersion.js.map +1 -0
  99. package/dist/updateMany.d.ts +13 -0
  100. package/dist/updateMany.d.ts.map +1 -0
  101. package/dist/updateMany.js +48 -0
  102. package/dist/updateMany.js.map +1 -0
  103. package/dist/updateOne.d.ts +13 -0
  104. package/dist/updateOne.d.ts.map +1 -0
  105. package/dist/updateOne.js +68 -0
  106. package/dist/updateOne.js.map +1 -0
  107. package/dist/updateVersion.d.ts +15 -0
  108. package/dist/updateVersion.d.ts.map +1 -0
  109. package/dist/updateVersion.js +69 -0
  110. package/dist/updateVersion.js.map +1 -0
  111. package/dist/upsert.d.ts +10 -0
  112. package/dist/upsert.d.ts.map +1 -0
  113. package/dist/upsert.js +55 -0
  114. package/dist/upsert.js.map +1 -0
  115. package/dist/utilities/applySorts.d.ts +11 -0
  116. package/dist/utilities/applySorts.d.ts.map +1 -0
  117. package/dist/utilities/applySorts.js +47 -0
  118. package/dist/utilities/applySorts.js.map +1 -0
  119. package/dist/utilities/buildFilterExpression.d.ts +42 -0
  120. package/dist/utilities/buildFilterExpression.d.ts.map +1 -0
  121. package/dist/utilities/buildFilterExpression.js +137 -0
  122. package/dist/utilities/buildFilterExpression.js.map +1 -0
  123. package/dist/utilities/draftsFallback.d.ts +28 -0
  124. package/dist/utilities/draftsFallback.d.ts.map +1 -0
  125. package/dist/utilities/draftsFallback.js +52 -0
  126. package/dist/utilities/draftsFallback.js.map +1 -0
  127. package/dist/utilities/ensureConnected.d.ts +12 -0
  128. package/dist/utilities/ensureConnected.d.ts.map +1 -0
  129. package/dist/utilities/ensureConnected.js +20 -0
  130. package/dist/utilities/ensureConnected.js.map +1 -0
  131. package/dist/utilities/ensureTable.d.ts +16 -0
  132. package/dist/utilities/ensureTable.d.ts.map +1 -0
  133. package/dist/utilities/ensureTable.js +62 -0
  134. package/dist/utilities/ensureTable.js.map +1 -0
  135. package/dist/utilities/findFirst.d.ts +16 -0
  136. package/dist/utilities/findFirst.d.ts.map +1 -0
  137. package/dist/utilities/findFirst.js +79 -0
  138. package/dist/utilities/findFirst.js.map +1 -0
  139. package/dist/utilities/getByPath.d.ts +9 -0
  140. package/dist/utilities/getByPath.d.ts.map +1 -0
  141. package/dist/utilities/getByPath.js +22 -0
  142. package/dist/utilities/getByPath.js.map +1 -0
  143. package/dist/utilities/matchesWhere.d.ts +12 -0
  144. package/dist/utilities/matchesWhere.d.ts.map +1 -0
  145. package/dist/utilities/matchesWhere.js +98 -0
  146. package/dist/utilities/matchesWhere.js.map +1 -0
  147. package/dist/utilities/normalizeForDynamo.d.ts +15 -0
  148. package/dist/utilities/normalizeForDynamo.d.ts.map +1 -0
  149. package/dist/utilities/normalizeForDynamo.js +27 -0
  150. package/dist/utilities/normalizeForDynamo.js.map +1 -0
  151. package/dist/utilities/operators.d.ts +26 -0
  152. package/dist/utilities/operators.d.ts.map +1 -0
  153. package/dist/utilities/operators.js +58 -0
  154. package/dist/utilities/operators.js.map +1 -0
  155. package/dist/utilities/pickConfiguredFields.d.ts +10 -0
  156. package/dist/utilities/pickConfiguredFields.d.ts.map +1 -0
  157. package/dist/utilities/pickConfiguredFields.js +113 -0
  158. package/dist/utilities/pickConfiguredFields.js.map +1 -0
  159. package/dist/utilities/queryCount.d.ts +9 -0
  160. package/dist/utilities/queryCount.d.ts.map +1 -0
  161. package/dist/utilities/queryCount.js +56 -0
  162. package/dist/utilities/queryCount.js.map +1 -0
  163. package/dist/utilities/queryMatching.d.ts +18 -0
  164. package/dist/utilities/queryMatching.d.ts.map +1 -0
  165. package/dist/utilities/queryMatching.js +71 -0
  166. package/dist/utilities/queryMatching.js.map +1 -0
  167. package/dist/utilities/resolveSchema.d.ts +56 -0
  168. package/dist/utilities/resolveSchema.d.ts.map +1 -0
  169. package/dist/utilities/resolveSchema.js +84 -0
  170. package/dist/utilities/resolveSchema.js.map +1 -0
  171. package/dist/utilities/scrubUnknownFields.d.ts +44 -0
  172. package/dist/utilities/scrubUnknownFields.d.ts.map +1 -0
  173. package/dist/utilities/scrubUnknownFields.js +88 -0
  174. package/dist/utilities/scrubUnknownFields.js.map +1 -0
  175. package/dist/utilities/stripInternalKeys.d.ts +7 -0
  176. package/dist/utilities/stripInternalKeys.d.ts.map +1 -0
  177. package/dist/utilities/stripInternalKeys.js +10 -0
  178. package/dist/utilities/stripInternalKeys.js.map +1 -0
  179. package/dist/utilities/whereToId.d.ts +11 -0
  180. package/dist/utilities/whereToId.d.ts.map +1 -0
  181. package/dist/utilities/whereToId.js +38 -0
  182. package/dist/utilities/whereToId.js.map +1 -0
  183. package/package.json +65 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The where-operator coverage shared by `matchesWhere` (in-JS evaluator) and
3
+ * `buildFilterExpression` (DynamoDB pushdown). Keeping the set in one place
4
+ * keeps the two paths honest — if an operator is supported in one, it should
5
+ * be supported in the other, since callers can't know which path runs.
6
+ */ export const SUPPORTED_OPERATORS = new Set([
7
+ 'equals',
8
+ 'exists',
9
+ 'greater_than',
10
+ 'greater_than_equal',
11
+ 'in',
12
+ 'less_than',
13
+ 'less_than_equal',
14
+ 'like',
15
+ 'not_equals',
16
+ 'not_in'
17
+ ]);
18
+ /**
19
+ * Operators we can't faithfully express in a DynamoDB `FilterExpression` and
20
+ * therefore evaluate in JS after fetching candidate rows. `like` is here
21
+ * because Payload's contract is case-insensitive substring matching while
22
+ * DynamoDB's `contains()` is case-sensitive — pushing it down would silently
23
+ * change semantics. When `where` contains any of these, the read helpers
24
+ * `Query` the partition without server-side filtering and apply
25
+ * `matchesWhere` in memory.
26
+ */ export const JS_ONLY_OPERATORS = new Set([
27
+ 'like'
28
+ ]);
29
+ export const SUPPORTED_OPERATORS_DESCRIPTION = `${[
30
+ ...SUPPORTED_OPERATORS
31
+ ].join(', ')}, and, or`;
32
+ export function unsupportedOperatorError(operator, field) {
33
+ return new Error(`payload-ddb: operator \`${operator}\` is not supported yet on field \`${field}\`. ` + `Supported: ${SUPPORTED_OPERATORS_DESCRIPTION}.`);
34
+ }
35
+ /**
36
+ * Walk a `Where` and return true if any leaf clause uses an operator that
37
+ * forces JS-side evaluation. Recurses into `and`/`or` groups.
38
+ */ export function whereHasJsOnlyOperator(where) {
39
+ if (!where) return false;
40
+ for (const [key, raw] of Object.entries(where)){
41
+ if (key === 'and' || key === 'or') {
42
+ if (!Array.isArray(raw)) continue;
43
+ for (const sub of raw){
44
+ if (sub && typeof sub === 'object' && whereHasJsOnlyOperator(sub)) {
45
+ return true;
46
+ }
47
+ }
48
+ continue;
49
+ }
50
+ if (!raw || typeof raw !== 'object') continue;
51
+ for (const op of Object.keys(raw)){
52
+ if (JS_ONLY_OPERATORS.has(op)) return true;
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+
58
+ //# sourceMappingURL=operators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/operators.ts"],"sourcesContent":["import type { Where } from 'payload'\n\n/**\n * The where-operator coverage shared by `matchesWhere` (in-JS evaluator) and\n * `buildFilterExpression` (DynamoDB pushdown). Keeping the set in one place\n * keeps the two paths honest — if an operator is supported in one, it should\n * be supported in the other, since callers can't know which path runs.\n */\nexport const SUPPORTED_OPERATORS = new Set([\n 'equals',\n 'exists',\n 'greater_than',\n 'greater_than_equal',\n 'in',\n 'less_than',\n 'less_than_equal',\n 'like',\n 'not_equals',\n 'not_in',\n])\n\n/**\n * Operators we can't faithfully express in a DynamoDB `FilterExpression` and\n * therefore evaluate in JS after fetching candidate rows. `like` is here\n * because Payload's contract is case-insensitive substring matching while\n * DynamoDB's `contains()` is case-sensitive — pushing it down would silently\n * change semantics. When `where` contains any of these, the read helpers\n * `Query` the partition without server-side filtering and apply\n * `matchesWhere` in memory.\n */\nexport const JS_ONLY_OPERATORS = new Set(['like'])\n\nexport const SUPPORTED_OPERATORS_DESCRIPTION = `${[...SUPPORTED_OPERATORS].join(', ')}, and, or`\n\nexport function unsupportedOperatorError(operator: string, field: string): Error {\n return new Error(\n `payload-ddb: operator \\`${operator}\\` is not supported yet on field \\`${field}\\`. ` +\n `Supported: ${SUPPORTED_OPERATORS_DESCRIPTION}.`,\n )\n}\n\n/**\n * Walk a `Where` and return true if any leaf clause uses an operator that\n * forces JS-side evaluation. Recurses into `and`/`or` groups.\n */\nexport function whereHasJsOnlyOperator(where: undefined | Where): boolean {\n if (!where) return false\n for (const [key, raw] of Object.entries(where)) {\n if (key === 'and' || key === 'or') {\n if (!Array.isArray(raw)) continue\n for (const sub of raw) {\n if (sub && typeof sub === 'object' && whereHasJsOnlyOperator(sub as Where)) {\n return true\n }\n }\n continue\n }\n if (!raw || typeof raw !== 'object') continue\n for (const op of Object.keys(raw)) {\n if (JS_ONLY_OPERATORS.has(op)) return true\n }\n }\n return false\n}\n"],"names":["SUPPORTED_OPERATORS","Set","JS_ONLY_OPERATORS","SUPPORTED_OPERATORS_DESCRIPTION","join","unsupportedOperatorError","operator","field","Error","whereHasJsOnlyOperator","where","key","raw","Object","entries","Array","isArray","sub","op","keys","has"],"mappings":"AAEA;;;;;CAKC,GACD,OAAO,MAAMA,sBAAsB,IAAIC,IAAI;IACzC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,EAAC;AAEF;;;;;;;;CAQC,GACD,OAAO,MAAMC,oBAAoB,IAAID,IAAI;IAAC;CAAO,EAAC;AAElD,OAAO,MAAME,kCAAkC,GAAG;OAAIH;CAAoB,CAACI,IAAI,CAAC,MAAM,SAAS,CAAC,CAAA;AAEhG,OAAO,SAASC,yBAAyBC,QAAgB,EAAEC,KAAa;IACtE,OAAO,IAAIC,MACT,CAAC,wBAAwB,EAAEF,SAAS,mCAAmC,EAAEC,MAAM,IAAI,CAAC,GAClF,CAAC,WAAW,EAAEJ,gCAAgC,CAAC,CAAC;AAEtD;AAEA;;;CAGC,GACD,OAAO,SAASM,uBAAuBC,KAAwB;IAC7D,IAAI,CAACA,OAAO,OAAO;IACnB,KAAK,MAAM,CAACC,KAAKC,IAAI,IAAIC,OAAOC,OAAO,CAACJ,OAAQ;QAC9C,IAAIC,QAAQ,SAASA,QAAQ,MAAM;YACjC,IAAI,CAACI,MAAMC,OAAO,CAACJ,MAAM;YACzB,KAAK,MAAMK,OAAOL,IAAK;gBACrB,IAAIK,OAAO,OAAOA,QAAQ,YAAYR,uBAAuBQ,MAAe;oBAC1E,OAAO;gBACT;YACF;YACA;QACF;QACA,IAAI,CAACL,OAAO,OAAOA,QAAQ,UAAU;QACrC,KAAK,MAAMM,MAAML,OAAOM,IAAI,CAACP,KAAM;YACjC,IAAIV,kBAAkBkB,GAAG,CAACF,KAAK,OAAO;QACxC;IACF;IACA,OAAO;AACT"}
@@ -0,0 +1,10 @@
1
+ import type { Field } from 'payload';
2
+ export declare function pickConfiguredFields(data: Record<string, unknown>, fields: Field[], extraAllowed?: readonly string[]): Record<string, unknown>;
3
+ /**
4
+ * Strip a version row to its declared shape. Allow-lists Payload's
5
+ * version-row metadata (`parent`, `latest`, `autosave`, etc.) and recurses
6
+ * into the inner `version` snapshot against the parent collection/global's
7
+ * own fields.
8
+ */
9
+ export declare function pickConfiguredVersionRow(row: Record<string, unknown>, parentFields: Field[]): Record<string, unknown>;
10
+ //# sourceMappingURL=pickConfiguredFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pickConfiguredFields.d.ts","sourceRoot":"","sources":["../../src/utilities/pickConfiguredFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAkB,MAAM,SAAS,CAAA;AAkCpD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,KAAK,EAAE,EACf,YAAY,GAAE,SAAS,MAAM,EAAO,GACnC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzB;AA2ED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,YAAY,EAAE,KAAK,EAAE,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB"}
@@ -0,0 +1,113 @@
1
+ import { flattenTopLevelFields } from 'payload';
2
+ import { ROW_RESERVED_KEYS, VERSION_ROW_RESERVED_KEYS } from './resolveSchema.js';
3
+ /**
4
+ * Project an arbitrary `data` object onto the keys declared by a Payload
5
+ * collection or global's `fields` config, recursively. Mongoose's `strict`
6
+ * mode and Drizzle's column allow-list give the other adapters this behavior
7
+ * for free; DynamoDB has no schema layer, so without this projection any
8
+ * stray request-body field rides into the row verbatim. Most visibly,
9
+ * registration-form `confirm-password` would persist alongside the auth
10
+ * collection's actual fields and surface back through `/api/users/me` —
11
+ * leaking a plaintext credential.
12
+ *
13
+ * Allowed top-level keys at each level are derived from
14
+ * `flattenTopLevelFields`, which already inlines unnamed containers (row,
15
+ * collapsible, unnamed tabs) and drops `ui` fields. We additionally skip
16
+ * `join` (read-side virtual relation, never persisted) and recurse into
17
+ * named containers (`group`, `tab`, `array`, `blocks`).
18
+ *
19
+ * `localized: true` short-circuits recursion: persisted localized values are
20
+ * `{ [locale]: value }` maps and we don't introspect locale codes here.
21
+ * Top-level allow-listing still drops the unknown sibling fields the bug
22
+ * actually surfaces.
23
+ *
24
+ * Reserved keys passed via `extraAllowed` (e.g. `id`, `createdAt`,
25
+ * `updatedAt`, `_status`) are passed through unchanged. They aren't always
26
+ * declared in `fields` but are owned by Payload's framework layer.
27
+ */ const PRESENTATIONAL_OR_VIRTUAL = new Set([
28
+ 'ui',
29
+ 'join'
30
+ ]);
31
+ export function pickConfiguredFields(data, fields, extraAllowed = []) {
32
+ return pickAtLevel(data, fields, extraAllowed);
33
+ }
34
+ function pickAtLevel(data, fields, extraAllowed = []) {
35
+ if (!isPlainRecord(data)) return data;
36
+ const out = {};
37
+ for (const key of extraAllowed){
38
+ if (key in data) out[key] = data[key];
39
+ }
40
+ for (const field of flattenTopLevelFields(fields)){
41
+ if (PRESENTATIONAL_OR_VIRTUAL.has(field.type)) continue;
42
+ const name = field.name;
43
+ if (!name || !(name in data)) continue;
44
+ const value = data[name];
45
+ // Localized values are `{ locale: value }` maps; we don't recurse into
46
+ // locale codes. Top-level field name is allow-listed, which is what
47
+ // closes the credential-leak class.
48
+ if (field.localized) {
49
+ out[name] = value;
50
+ continue;
51
+ }
52
+ if (field.type === 'group' || field.type === 'tab') {
53
+ out[name] = pickAtLevel(value, field.fields ?? []);
54
+ } else if (field.type === 'array') {
55
+ out[name] = pickArrayItems(value, field.fields ?? []);
56
+ } else if (field.type === 'blocks') {
57
+ out[name] = pickBlockItems(value, field.blocks ?? []);
58
+ } else {
59
+ out[name] = value;
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+ function pickArrayItems(value, fields) {
65
+ if (!Array.isArray(value)) return value;
66
+ return value.map((item)=>{
67
+ if (!isPlainRecord(item)) return item;
68
+ // Array items get an auto-generated `id` from Payload that isn't in `fields`.
69
+ return pickAtLevel(item, fields, [
70
+ 'id'
71
+ ]);
72
+ });
73
+ }
74
+ function pickBlockItems(value, blocks) {
75
+ if (!Array.isArray(value)) return value;
76
+ const bySlug = new Map(blocks.map((b)=>[
77
+ b.slug,
78
+ b
79
+ ]));
80
+ return value.map((item)=>{
81
+ if (!isPlainRecord(item)) return item;
82
+ const blockType = item['blockType'];
83
+ const block = typeof blockType === 'string' ? bySlug.get(blockType) : undefined;
84
+ // Unknown blockType: pass through. Mirrors Mongo's behavior — a stale
85
+ // blockType lingering in data after a config change shouldn't be
86
+ // silently nuked by the adapter.
87
+ if (!block) return item;
88
+ return pickAtLevel(item, block.fields ?? [], [
89
+ 'id',
90
+ 'blockType'
91
+ ]);
92
+ });
93
+ }
94
+ function isPlainRecord(value) {
95
+ return value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date);
96
+ }
97
+ /**
98
+ * Strip a version row to its declared shape. Allow-lists Payload's
99
+ * version-row metadata (`parent`, `latest`, `autosave`, etc.) and recurses
100
+ * into the inner `version` snapshot against the parent collection/global's
101
+ * own fields.
102
+ */ export function pickConfiguredVersionRow(row, parentFields) {
103
+ const out = {};
104
+ for (const key of VERSION_ROW_RESERVED_KEYS){
105
+ if (key in row) out[key] = row[key];
106
+ }
107
+ if (isPlainRecord(out['version'])) {
108
+ out['version'] = pickConfiguredFields(out['version'], parentFields, ROW_RESERVED_KEYS);
109
+ }
110
+ return out;
111
+ }
112
+
113
+ //# sourceMappingURL=pickConfiguredFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/pickConfiguredFields.ts"],"sourcesContent":["import type { Field, FlattenedField } from 'payload'\n\nimport { flattenTopLevelFields } from 'payload'\n\nimport { ROW_RESERVED_KEYS, VERSION_ROW_RESERVED_KEYS } from './resolveSchema.js'\n\n/**\n * Project an arbitrary `data` object onto the keys declared by a Payload\n * collection or global's `fields` config, recursively. Mongoose's `strict`\n * mode and Drizzle's column allow-list give the other adapters this behavior\n * for free; DynamoDB has no schema layer, so without this projection any\n * stray request-body field rides into the row verbatim. Most visibly,\n * registration-form `confirm-password` would persist alongside the auth\n * collection's actual fields and surface back through `/api/users/me` —\n * leaking a plaintext credential.\n *\n * Allowed top-level keys at each level are derived from\n * `flattenTopLevelFields`, which already inlines unnamed containers (row,\n * collapsible, unnamed tabs) and drops `ui` fields. We additionally skip\n * `join` (read-side virtual relation, never persisted) and recurse into\n * named containers (`group`, `tab`, `array`, `blocks`).\n *\n * `localized: true` short-circuits recursion: persisted localized values are\n * `{ [locale]: value }` maps and we don't introspect locale codes here.\n * Top-level allow-listing still drops the unknown sibling fields the bug\n * actually surfaces.\n *\n * Reserved keys passed via `extraAllowed` (e.g. `id`, `createdAt`,\n * `updatedAt`, `_status`) are passed through unchanged. They aren't always\n * declared in `fields` but are owned by Payload's framework layer.\n */\n\nconst PRESENTATIONAL_OR_VIRTUAL = new Set(['ui', 'join'])\n\nexport function pickConfiguredFields(\n data: Record<string, unknown>,\n fields: Field[],\n extraAllowed: readonly string[] = [],\n): Record<string, unknown> {\n return pickAtLevel(data, fields, extraAllowed) as Record<string, unknown>\n}\n\nfunction pickAtLevel(\n data: unknown,\n fields: Field[],\n extraAllowed: readonly string[] = [],\n): unknown {\n if (!isPlainRecord(data)) return data\n\n const out: Record<string, unknown> = {}\n\n for (const key of extraAllowed) {\n if (key in data) out[key] = data[key]\n }\n\n for (const field of flattenTopLevelFields(fields) as FlattenedField[]) {\n if (PRESENTATIONAL_OR_VIRTUAL.has(field.type)) continue\n const name = (field as { name?: string }).name\n if (!name || !(name in data)) continue\n\n const value = data[name]\n\n // Localized values are `{ locale: value }` maps; we don't recurse into\n // locale codes. Top-level field name is allow-listed, which is what\n // closes the credential-leak class.\n if ((field as { localized?: boolean }).localized) {\n out[name] = value\n continue\n }\n\n if (field.type === 'group' || field.type === 'tab') {\n out[name] = pickAtLevel(value, field.fields ?? [])\n } else if (field.type === 'array') {\n out[name] = pickArrayItems(value, field.fields ?? [])\n } else if (field.type === 'blocks') {\n out[name] = pickBlockItems(value, field.blocks ?? [])\n } else {\n out[name] = value\n }\n }\n\n return out\n}\n\nfunction pickArrayItems(value: unknown, fields: Field[]): unknown {\n if (!Array.isArray(value)) return value\n return value.map((item) => {\n if (!isPlainRecord(item)) return item\n // Array items get an auto-generated `id` from Payload that isn't in `fields`.\n return pickAtLevel(item, fields, ['id']) as Record<string, unknown>\n })\n}\n\nfunction pickBlockItems(\n value: unknown,\n blocks: { slug: string; fields: Field[] }[],\n): unknown {\n if (!Array.isArray(value)) return value\n const bySlug = new Map(blocks.map((b) => [b.slug, b]))\n return value.map((item) => {\n if (!isPlainRecord(item)) return item\n const blockType = item['blockType']\n const block = typeof blockType === 'string' ? bySlug.get(blockType) : undefined\n // Unknown blockType: pass through. Mirrors Mongo's behavior — a stale\n // blockType lingering in data after a config change shouldn't be\n // silently nuked by the adapter.\n if (!block) return item\n return pickAtLevel(item, block.fields ?? [], ['id', 'blockType']) as Record<string, unknown>\n })\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)\n}\n\n/**\n * Strip a version row to its declared shape. Allow-lists Payload's\n * version-row metadata (`parent`, `latest`, `autosave`, etc.) and recurses\n * into the inner `version` snapshot against the parent collection/global's\n * own fields.\n */\nexport function pickConfiguredVersionRow(\n row: Record<string, unknown>,\n parentFields: Field[],\n): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const key of VERSION_ROW_RESERVED_KEYS) {\n if (key in row) out[key] = row[key]\n }\n if (isPlainRecord(out['version'])) {\n out['version'] = pickConfiguredFields(out['version'], parentFields, ROW_RESERVED_KEYS)\n }\n return out\n}\n"],"names":["flattenTopLevelFields","ROW_RESERVED_KEYS","VERSION_ROW_RESERVED_KEYS","PRESENTATIONAL_OR_VIRTUAL","Set","pickConfiguredFields","data","fields","extraAllowed","pickAtLevel","isPlainRecord","out","key","field","has","type","name","value","localized","pickArrayItems","pickBlockItems","blocks","Array","isArray","map","item","bySlug","Map","b","slug","blockType","block","get","undefined","Date","pickConfiguredVersionRow","row","parentFields"],"mappings":"AAEA,SAASA,qBAAqB,QAAQ,UAAS;AAE/C,SAASC,iBAAiB,EAAEC,yBAAyB,QAAQ,qBAAoB;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GAED,MAAMC,4BAA4B,IAAIC,IAAI;IAAC;IAAM;CAAO;AAExD,OAAO,SAASC,qBACdC,IAA6B,EAC7BC,MAAe,EACfC,eAAkC,EAAE;IAEpC,OAAOC,YAAYH,MAAMC,QAAQC;AACnC;AAEA,SAASC,YACPH,IAAa,EACbC,MAAe,EACfC,eAAkC,EAAE;IAEpC,IAAI,CAACE,cAAcJ,OAAO,OAAOA;IAEjC,MAAMK,MAA+B,CAAC;IAEtC,KAAK,MAAMC,OAAOJ,aAAc;QAC9B,IAAII,OAAON,MAAMK,GAAG,CAACC,IAAI,GAAGN,IAAI,CAACM,IAAI;IACvC;IAEA,KAAK,MAAMC,SAASb,sBAAsBO,QAA6B;QACrE,IAAIJ,0BAA0BW,GAAG,CAACD,MAAME,IAAI,GAAG;QAC/C,MAAMC,OAAO,AAACH,MAA4BG,IAAI;QAC9C,IAAI,CAACA,QAAQ,CAAEA,CAAAA,QAAQV,IAAG,GAAI;QAE9B,MAAMW,QAAQX,IAAI,CAACU,KAAK;QAExB,uEAAuE;QACvE,oEAAoE;QACpE,oCAAoC;QACpC,IAAI,AAACH,MAAkCK,SAAS,EAAE;YAChDP,GAAG,CAACK,KAAK,GAAGC;YACZ;QACF;QAEA,IAAIJ,MAAME,IAAI,KAAK,WAAWF,MAAME,IAAI,KAAK,OAAO;YAClDJ,GAAG,CAACK,KAAK,GAAGP,YAAYQ,OAAOJ,MAAMN,MAAM,IAAI,EAAE;QACnD,OAAO,IAAIM,MAAME,IAAI,KAAK,SAAS;YACjCJ,GAAG,CAACK,KAAK,GAAGG,eAAeF,OAAOJ,MAAMN,MAAM,IAAI,EAAE;QACtD,OAAO,IAAIM,MAAME,IAAI,KAAK,UAAU;YAClCJ,GAAG,CAACK,KAAK,GAAGI,eAAeH,OAAOJ,MAAMQ,MAAM,IAAI,EAAE;QACtD,OAAO;YACLV,GAAG,CAACK,KAAK,GAAGC;QACd;IACF;IAEA,OAAON;AACT;AAEA,SAASQ,eAAeF,KAAc,EAAEV,MAAe;IACrD,IAAI,CAACe,MAAMC,OAAO,CAACN,QAAQ,OAAOA;IAClC,OAAOA,MAAMO,GAAG,CAAC,CAACC;QAChB,IAAI,CAACf,cAAce,OAAO,OAAOA;QACjC,8EAA8E;QAC9E,OAAOhB,YAAYgB,MAAMlB,QAAQ;YAAC;SAAK;IACzC;AACF;AAEA,SAASa,eACPH,KAAc,EACdI,MAA2C;IAE3C,IAAI,CAACC,MAAMC,OAAO,CAACN,QAAQ,OAAOA;IAClC,MAAMS,SAAS,IAAIC,IAAIN,OAAOG,GAAG,CAAC,CAACI,IAAM;YAACA,EAAEC,IAAI;YAAED;SAAE;IACpD,OAAOX,MAAMO,GAAG,CAAC,CAACC;QAChB,IAAI,CAACf,cAAce,OAAO,OAAOA;QACjC,MAAMK,YAAYL,IAAI,CAAC,YAAY;QACnC,MAAMM,QAAQ,OAAOD,cAAc,WAAWJ,OAAOM,GAAG,CAACF,aAAaG;QACtE,sEAAsE;QACtE,iEAAiE;QACjE,iCAAiC;QACjC,IAAI,CAACF,OAAO,OAAON;QACnB,OAAOhB,YAAYgB,MAAMM,MAAMxB,MAAM,IAAI,EAAE,EAAE;YAAC;YAAM;SAAY;IAClE;AACF;AAEA,SAASG,cAAcO,KAAc;IACnC,OAAOA,UAAU,QAAQ,OAAOA,UAAU,YAAY,CAACK,MAAMC,OAAO,CAACN,UAAU,CAAEA,CAAAA,iBAAiBiB,IAAG;AACvG;AAEA;;;;;CAKC,GACD,OAAO,SAASC,yBACdC,GAA4B,EAC5BC,YAAqB;IAErB,MAAM1B,MAA+B,CAAC;IACtC,KAAK,MAAMC,OAAOV,0BAA2B;QAC3C,IAAIU,OAAOwB,KAAKzB,GAAG,CAACC,IAAI,GAAGwB,GAAG,CAACxB,IAAI;IACrC;IACA,IAAIF,cAAcC,GAAG,CAAC,UAAU,GAAG;QACjCA,GAAG,CAAC,UAAU,GAAGN,qBAAqBM,GAAG,CAAC,UAAU,EAAE0B,cAAcpC;IACtE;IACA,OAAOU;AACT"}
@@ -0,0 +1,9 @@
1
+ import type { Where } from 'payload';
2
+ import type { DynamoAdapter } from '../types.js';
3
+ /**
4
+ * Paginated count over a single partition with optional `FilterExpression`
5
+ * pushdown. Uses `Select: 'COUNT'` so DynamoDB doesn't ship item bytes —
6
+ * only the post-filter `Count` per page comes back over the wire.
7
+ */
8
+ export declare function queryCount(adapter: DynamoAdapter, partition: string, where: undefined | Where): Promise<number>;
9
+ //# sourceMappingURL=queryCount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryCount.d.ts","sourceRoot":"","sources":["../../src/utilities/queryCount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAIpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAMhD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,SAAS,GAAG,KAAK,GACvB,OAAO,CAAC,MAAM,CAAC,CAiDjB"}
@@ -0,0 +1,56 @@
1
+ import { QueryCommand } from '@aws-sdk/lib-dynamodb';
2
+ import { buildFilterExpression } from './buildFilterExpression.js';
3
+ import { whereHasJsOnlyOperator } from './operators.js';
4
+ import { queryMatching } from './queryMatching.js';
5
+ /**
6
+ * Paginated count over a single partition with optional `FilterExpression`
7
+ * pushdown. Uses `Select: 'COUNT'` so DynamoDB doesn't ship item bytes —
8
+ * only the post-filter `Count` per page comes back over the wire.
9
+ */ export async function queryCount(adapter, partition, where) {
10
+ const docClient = adapter.docClient;
11
+ if (!docClient) {
12
+ throw new Error('payload-ddb: docClient is not initialized — call connect() first.');
13
+ }
14
+ // JS-only operators (e.g. `like`) can't be expressed in `FilterExpression`,
15
+ // so the only correct count is "fetch and count the matches in memory".
16
+ // Loses the `Select: 'COUNT'` byte-savings but keeps semantics consistent
17
+ // with `queryMatching`.
18
+ if (whereHasJsOnlyOperator(where)) {
19
+ const matched = await queryMatching(adapter, partition, where);
20
+ return matched.length;
21
+ }
22
+ const filter = buildFilterExpression(where);
23
+ // Always-false predicate (e.g. `in: []`) → zero rows without a round-trip.
24
+ if (filter === null) return 0;
25
+ let totalDocs = 0;
26
+ let exclusiveStartKey;
27
+ while(true){
28
+ const result = await docClient.send(new QueryCommand({
29
+ TableName: adapter.tableName,
30
+ KeyConditionExpression: '#pk = :pk',
31
+ ExpressionAttributeNames: {
32
+ '#pk': 'pk',
33
+ ...filter?.names ?? {}
34
+ },
35
+ ExpressionAttributeValues: {
36
+ ':pk': partition,
37
+ ...filter?.values ?? {}
38
+ },
39
+ // Strong consistency — see `queryMatching` for rationale.
40
+ ConsistentRead: true,
41
+ Select: 'COUNT',
42
+ ...filter ? {
43
+ FilterExpression: filter.expression
44
+ } : {},
45
+ ...exclusiveStartKey ? {
46
+ ExclusiveStartKey: exclusiveStartKey
47
+ } : {}
48
+ }));
49
+ totalDocs += result.Count ?? 0;
50
+ if (!result.LastEvaluatedKey) break;
51
+ exclusiveStartKey = result.LastEvaluatedKey;
52
+ }
53
+ return totalDocs;
54
+ }
55
+
56
+ //# sourceMappingURL=queryCount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/queryCount.ts"],"sourcesContent":["import type { Where } from 'payload'\n\nimport { QueryCommand } from '@aws-sdk/lib-dynamodb'\n\nimport type { DynamoAdapter } from '../types.js'\n\nimport { buildFilterExpression } from './buildFilterExpression.js'\nimport { whereHasJsOnlyOperator } from './operators.js'\nimport { queryMatching } from './queryMatching.js'\n\n/**\n * Paginated count over a single partition with optional `FilterExpression`\n * pushdown. Uses `Select: 'COUNT'` so DynamoDB doesn't ship item bytes —\n * only the post-filter `Count` per page comes back over the wire.\n */\nexport async function queryCount(\n adapter: DynamoAdapter,\n partition: string,\n where: undefined | Where,\n): Promise<number> {\n const docClient = adapter.docClient\n if (!docClient) {\n throw new Error('payload-ddb: docClient is not initialized — call connect() first.')\n }\n\n // JS-only operators (e.g. `like`) can't be expressed in `FilterExpression`,\n // so the only correct count is \"fetch and count the matches in memory\".\n // Loses the `Select: 'COUNT'` byte-savings but keeps semantics consistent\n // with `queryMatching`.\n if (whereHasJsOnlyOperator(where)) {\n const matched = await queryMatching(adapter, partition, where)\n return matched.length\n }\n\n const filter = buildFilterExpression(where)\n // Always-false predicate (e.g. `in: []`) → zero rows without a round-trip.\n if (filter === null) return 0\n let totalDocs = 0\n let exclusiveStartKey: Record<string, unknown> | undefined\n\n while (true) {\n const result = await docClient.send(\n new QueryCommand({\n TableName: adapter.tableName,\n KeyConditionExpression: '#pk = :pk',\n ExpressionAttributeNames: {\n '#pk': 'pk',\n ...(filter?.names ?? {}),\n },\n ExpressionAttributeValues: {\n ':pk': partition,\n ...(filter?.values ?? {}),\n },\n // Strong consistency — see `queryMatching` for rationale.\n ConsistentRead: true,\n Select: 'COUNT',\n ...(filter ? { FilterExpression: filter.expression } : {}),\n ...(exclusiveStartKey ? { ExclusiveStartKey: exclusiveStartKey } : {}),\n }),\n )\n\n totalDocs += result.Count ?? 0\n\n if (!result.LastEvaluatedKey) break\n exclusiveStartKey = result.LastEvaluatedKey\n }\n\n return totalDocs\n}\n"],"names":["QueryCommand","buildFilterExpression","whereHasJsOnlyOperator","queryMatching","queryCount","adapter","partition","where","docClient","Error","matched","length","filter","totalDocs","exclusiveStartKey","result","send","TableName","tableName","KeyConditionExpression","ExpressionAttributeNames","names","ExpressionAttributeValues","values","ConsistentRead","Select","FilterExpression","expression","ExclusiveStartKey","Count","LastEvaluatedKey"],"mappings":"AAEA,SAASA,YAAY,QAAQ,wBAAuB;AAIpD,SAASC,qBAAqB,QAAQ,6BAA4B;AAClE,SAASC,sBAAsB,QAAQ,iBAAgB;AACvD,SAASC,aAAa,QAAQ,qBAAoB;AAElD;;;;CAIC,GACD,OAAO,eAAeC,WACpBC,OAAsB,EACtBC,SAAiB,EACjBC,KAAwB;IAExB,MAAMC,YAAYH,QAAQG,SAAS;IACnC,IAAI,CAACA,WAAW;QACd,MAAM,IAAIC,MAAM;IAClB;IAEA,4EAA4E;IAC5E,wEAAwE;IACxE,0EAA0E;IAC1E,wBAAwB;IACxB,IAAIP,uBAAuBK,QAAQ;QACjC,MAAMG,UAAU,MAAMP,cAAcE,SAASC,WAAWC;QACxD,OAAOG,QAAQC,MAAM;IACvB;IAEA,MAAMC,SAASX,sBAAsBM;IACrC,2EAA2E;IAC3E,IAAIK,WAAW,MAAM,OAAO;IAC5B,IAAIC,YAAY;IAChB,IAAIC;IAEJ,MAAO,KAAM;QACX,MAAMC,SAAS,MAAMP,UAAUQ,IAAI,CACjC,IAAIhB,aAAa;YACfiB,WAAWZ,QAAQa,SAAS;YAC5BC,wBAAwB;YACxBC,0BAA0B;gBACxB,OAAO;gBACP,GAAIR,QAAQS,SAAS,CAAC,CAAC;YACzB;YACAC,2BAA2B;gBACzB,OAAOhB;gBACP,GAAIM,QAAQW,UAAU,CAAC,CAAC;YAC1B;YACA,0DAA0D;YAC1DC,gBAAgB;YAChBC,QAAQ;YACR,GAAIb,SAAS;gBAAEc,kBAAkBd,OAAOe,UAAU;YAAC,IAAI,CAAC,CAAC;YACzD,GAAIb,oBAAoB;gBAAEc,mBAAmBd;YAAkB,IAAI,CAAC,CAAC;QACvE;QAGFD,aAAaE,OAAOc,KAAK,IAAI;QAE7B,IAAI,CAACd,OAAOe,gBAAgB,EAAE;QAC9BhB,oBAAoBC,OAAOe,gBAAgB;IAC7C;IAEA,OAAOjB;AACT"}
@@ -0,0 +1,18 @@
1
+ import type { Where } from 'payload';
2
+ import type { DynamoAdapter } from '../types.js';
3
+ /**
4
+ * Walk a partition via paginated `Query` (`pk = :partition`), pushing the
5
+ * caller's `where` into `FilterExpression` so non-matching rows are skipped
6
+ * server-side. Returns every matching item materialized in memory — suitable
7
+ * for callers that need the full set (sort, paginate, bulk-mutate).
8
+ *
9
+ * Use `findFirst` instead when you only need the first match: that helper
10
+ * stops on the first hit.
11
+ *
12
+ * Cost note: `FilterExpression` reduces network bytes but not RCU
13
+ * consumption. DynamoDB still reads every row in the partition before
14
+ * filtering. The next meaningful optimization is GSIs that mirror specific
15
+ * access patterns (e.g. `email` for auth, `slug` for public collections).
16
+ */
17
+ export declare function queryMatching(adapter: DynamoAdapter, partition: string, where: undefined | Where): Promise<Record<string, unknown>[]>;
18
+ //# sourceMappingURL=queryMatching.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryMatching.d.ts","sourceRoot":"","sources":["../../src/utilities/queryMatching.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAIpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAOhD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,SAAS,GAAG,KAAK,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAoDpC"}
@@ -0,0 +1,71 @@
1
+ import { QueryCommand } from '@aws-sdk/lib-dynamodb';
2
+ import { buildFilterExpression } from './buildFilterExpression.js';
3
+ import { matchesWhere } from './matchesWhere.js';
4
+ import { whereHasJsOnlyOperator } from './operators.js';
5
+ import { stripInternalKeys } from './stripInternalKeys.js';
6
+ /**
7
+ * Walk a partition via paginated `Query` (`pk = :partition`), pushing the
8
+ * caller's `where` into `FilterExpression` so non-matching rows are skipped
9
+ * server-side. Returns every matching item materialized in memory — suitable
10
+ * for callers that need the full set (sort, paginate, bulk-mutate).
11
+ *
12
+ * Use `findFirst` instead when you only need the first match: that helper
13
+ * stops on the first hit.
14
+ *
15
+ * Cost note: `FilterExpression` reduces network bytes but not RCU
16
+ * consumption. DynamoDB still reads every row in the partition before
17
+ * filtering. The next meaningful optimization is GSIs that mirror specific
18
+ * access patterns (e.g. `email` for auth, `slug` for public collections).
19
+ */ export async function queryMatching(adapter, partition, where) {
20
+ const docClient = adapter.docClient;
21
+ if (!docClient) {
22
+ throw new Error('payload-ddb: docClient is not initialized — call connect() first.');
23
+ }
24
+ // When `where` uses an operator we can't faithfully push down (today: `like`,
25
+ // because Payload's contract is case-insensitive while DynamoDB's
26
+ // `contains()` isn't), we fetch the whole partition and evaluate in JS.
27
+ // Recurse with `where = undefined` to reuse the standard pagination loop.
28
+ if (whereHasJsOnlyOperator(where)) {
29
+ const all = await queryMatching(adapter, partition, undefined);
30
+ return all.filter((row)=>matchesWhere(row, where));
31
+ }
32
+ const filter = buildFilterExpression(where);
33
+ // `null` signals an always-false predicate (e.g. `id: { in: [] }` from
34
+ // access control). Short-circuit so we don't pay for a Query just to
35
+ // return nothing.
36
+ if (filter === null) return [];
37
+ const matched = [];
38
+ let exclusiveStartKey;
39
+ while(true){
40
+ const result = await docClient.send(new QueryCommand({
41
+ TableName: adapter.tableName,
42
+ KeyConditionExpression: '#pk = :pk',
43
+ ExpressionAttributeNames: {
44
+ '#pk': 'pk',
45
+ ...filter?.names ?? {}
46
+ },
47
+ ExpressionAttributeValues: {
48
+ ':pk': partition,
49
+ ...filter?.values ?? {}
50
+ },
51
+ // Strong consistency by default — Payload assumes read-after-write
52
+ // visibility (Mongo/Postgres do), and DynamoDB's default eventual
53
+ // consistency breaks "create-then-list" flows. 2x RCU but correct.
54
+ ConsistentRead: true,
55
+ ...filter ? {
56
+ FilterExpression: filter.expression
57
+ } : {},
58
+ ...exclusiveStartKey ? {
59
+ ExclusiveStartKey: exclusiveStartKey
60
+ } : {}
61
+ }));
62
+ for (const item of result.Items ?? []){
63
+ matched.push(stripInternalKeys(item));
64
+ }
65
+ if (!result.LastEvaluatedKey) break;
66
+ exclusiveStartKey = result.LastEvaluatedKey;
67
+ }
68
+ return matched;
69
+ }
70
+
71
+ //# sourceMappingURL=queryMatching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/queryMatching.ts"],"sourcesContent":["import type { Where } from 'payload'\n\nimport { QueryCommand } from '@aws-sdk/lib-dynamodb'\n\nimport type { DynamoAdapter } from '../types.js'\n\nimport { buildFilterExpression } from './buildFilterExpression.js'\nimport { matchesWhere } from './matchesWhere.js'\nimport { whereHasJsOnlyOperator } from './operators.js'\nimport { stripInternalKeys } from './stripInternalKeys.js'\n\n/**\n * Walk a partition via paginated `Query` (`pk = :partition`), pushing the\n * caller's `where` into `FilterExpression` so non-matching rows are skipped\n * server-side. Returns every matching item materialized in memory — suitable\n * for callers that need the full set (sort, paginate, bulk-mutate).\n *\n * Use `findFirst` instead when you only need the first match: that helper\n * stops on the first hit.\n *\n * Cost note: `FilterExpression` reduces network bytes but not RCU\n * consumption. DynamoDB still reads every row in the partition before\n * filtering. The next meaningful optimization is GSIs that mirror specific\n * access patterns (e.g. `email` for auth, `slug` for public collections).\n */\nexport async function queryMatching(\n adapter: DynamoAdapter,\n partition: string,\n where: undefined | Where,\n): Promise<Record<string, unknown>[]> {\n const docClient = adapter.docClient\n if (!docClient) {\n throw new Error('payload-ddb: docClient is not initialized — call connect() first.')\n }\n\n // When `where` uses an operator we can't faithfully push down (today: `like`,\n // because Payload's contract is case-insensitive while DynamoDB's\n // `contains()` isn't), we fetch the whole partition and evaluate in JS.\n // Recurse with `where = undefined` to reuse the standard pagination loop.\n if (whereHasJsOnlyOperator(where)) {\n const all = await queryMatching(adapter, partition, undefined)\n return all.filter((row) => matchesWhere(row, where))\n }\n\n const filter = buildFilterExpression(where)\n // `null` signals an always-false predicate (e.g. `id: { in: [] }` from\n // access control). Short-circuit so we don't pay for a Query just to\n // return nothing.\n if (filter === null) return []\n const matched: Record<string, unknown>[] = []\n let exclusiveStartKey: Record<string, unknown> | undefined\n\n while (true) {\n const result = await docClient.send(\n new QueryCommand({\n TableName: adapter.tableName,\n KeyConditionExpression: '#pk = :pk',\n ExpressionAttributeNames: {\n '#pk': 'pk',\n ...(filter?.names ?? {}),\n },\n ExpressionAttributeValues: {\n ':pk': partition,\n ...(filter?.values ?? {}),\n },\n // Strong consistency by default — Payload assumes read-after-write\n // visibility (Mongo/Postgres do), and DynamoDB's default eventual\n // consistency breaks \"create-then-list\" flows. 2x RCU but correct.\n ConsistentRead: true,\n ...(filter ? { FilterExpression: filter.expression } : {}),\n ...(exclusiveStartKey ? { ExclusiveStartKey: exclusiveStartKey } : {}),\n }),\n )\n for (const item of result.Items ?? []) {\n matched.push(stripInternalKeys(item))\n }\n if (!result.LastEvaluatedKey) break\n exclusiveStartKey = result.LastEvaluatedKey\n }\n\n return matched\n}\n"],"names":["QueryCommand","buildFilterExpression","matchesWhere","whereHasJsOnlyOperator","stripInternalKeys","queryMatching","adapter","partition","where","docClient","Error","all","undefined","filter","row","matched","exclusiveStartKey","result","send","TableName","tableName","KeyConditionExpression","ExpressionAttributeNames","names","ExpressionAttributeValues","values","ConsistentRead","FilterExpression","expression","ExclusiveStartKey","item","Items","push","LastEvaluatedKey"],"mappings":"AAEA,SAASA,YAAY,QAAQ,wBAAuB;AAIpD,SAASC,qBAAqB,QAAQ,6BAA4B;AAClE,SAASC,YAAY,QAAQ,oBAAmB;AAChD,SAASC,sBAAsB,QAAQ,iBAAgB;AACvD,SAASC,iBAAiB,QAAQ,yBAAwB;AAE1D;;;;;;;;;;;;;CAaC,GACD,OAAO,eAAeC,cACpBC,OAAsB,EACtBC,SAAiB,EACjBC,KAAwB;IAExB,MAAMC,YAAYH,QAAQG,SAAS;IACnC,IAAI,CAACA,WAAW;QACd,MAAM,IAAIC,MAAM;IAClB;IAEA,8EAA8E;IAC9E,kEAAkE;IAClE,wEAAwE;IACxE,0EAA0E;IAC1E,IAAIP,uBAAuBK,QAAQ;QACjC,MAAMG,MAAM,MAAMN,cAAcC,SAASC,WAAWK;QACpD,OAAOD,IAAIE,MAAM,CAAC,CAACC,MAAQZ,aAAaY,KAAKN;IAC/C;IAEA,MAAMK,SAASZ,sBAAsBO;IACrC,uEAAuE;IACvE,qEAAqE;IACrE,kBAAkB;IAClB,IAAIK,WAAW,MAAM,OAAO,EAAE;IAC9B,MAAME,UAAqC,EAAE;IAC7C,IAAIC;IAEJ,MAAO,KAAM;QACX,MAAMC,SAAS,MAAMR,UAAUS,IAAI,CACjC,IAAIlB,aAAa;YACfmB,WAAWb,QAAQc,SAAS;YAC5BC,wBAAwB;YACxBC,0BAA0B;gBACxB,OAAO;gBACP,GAAIT,QAAQU,SAAS,CAAC,CAAC;YACzB;YACAC,2BAA2B;gBACzB,OAAOjB;gBACP,GAAIM,QAAQY,UAAU,CAAC,CAAC;YAC1B;YACA,mEAAmE;YACnE,kEAAkE;YAClE,mEAAmE;YACnEC,gBAAgB;YAChB,GAAIb,SAAS;gBAAEc,kBAAkBd,OAAOe,UAAU;YAAC,IAAI,CAAC,CAAC;YACzD,GAAIZ,oBAAoB;gBAAEa,mBAAmBb;YAAkB,IAAI,CAAC,CAAC;QACvE;QAEF,KAAK,MAAMc,QAAQb,OAAOc,KAAK,IAAI,EAAE,CAAE;YACrChB,QAAQiB,IAAI,CAAC5B,kBAAkB0B;QACjC;QACA,IAAI,CAACb,OAAOgB,gBAAgB,EAAE;QAC9BjB,oBAAoBC,OAAOgB,gBAAgB;IAC7C;IAEA,OAAOlB;AACT"}
@@ -0,0 +1,56 @@
1
+ import type { Field } from 'payload';
2
+ import type { DynamoAdapter } from '../types.js';
3
+ /**
4
+ * Look up the sanitized `fields` array for a collection or global. Lives in
5
+ * one place so the write-path projection (see `pickConfiguredFields`) doesn't
6
+ * have to learn Payload's collection/global storage layout per call site.
7
+ *
8
+ * Returns `null` (rather than throwing) when a slug is unrecognized — a
9
+ * caller using an out-of-config slug is a programmer error elsewhere, but
10
+ * the right blast radius for that here is "skip projection," not "kill the
11
+ * write."
12
+ */
13
+ export declare function getCollectionFields(adapter: DynamoAdapter, slug: string): Field[] | null;
14
+ export declare function getGlobalFields(adapter: DynamoAdapter, slug: string): Field[] | null;
15
+ /**
16
+ * Reserved keys Payload's framework layer owns rather than declaring through
17
+ * `fields`. Allowed at the top of every collection/global row.
18
+ *
19
+ * - `id` — primary key
20
+ * - `createdAt`/`updatedAt` — timestamp pair (default-on per collection config)
21
+ * - `_status` — drafts state (only present when drafts enabled,
22
+ * but allow-listing it unconditionally is harmless)
23
+ * - `globalType` — Payload tags global rows with this internally
24
+ */
25
+ export declare const ROW_RESERVED_KEYS: readonly ["id", "createdAt", "updatedAt", "_status", "globalType"];
26
+ /**
27
+ * Reserved keys at the top of a *version* row. The inner `version` field
28
+ * holds the actual doc snapshot and is recursed into separately against the
29
+ * parent collection/global's own fields.
30
+ */
31
+ export declare const VERSION_ROW_RESERVED_KEYS: readonly ["id", "parent", "version", "createdAt", "updatedAt", "latest", "autosave", "snapshot", "publishedLocale"];
32
+ /**
33
+ * Strip a row against its collection's `fields`, falling back to a no-op
34
+ * if the slug isn't recognized (defensive — see `getCollectionFields`).
35
+ */
36
+ export declare function projectForCollection(adapter: DynamoAdapter, slug: string, data: Record<string, unknown>): Record<string, unknown>;
37
+ export declare function projectForGlobal(adapter: DynamoAdapter, slug: string, data: Record<string, unknown>): Record<string, unknown>;
38
+ /**
39
+ * Strip the inner doc snapshot of a version's `versionData` argument against
40
+ * the parent collection or global's fields. The createVersion adapters wrap
41
+ * the result into a version row whose other keys (parent, latest, ...) are
42
+ * set explicitly by the adapter, so we only need to project the snapshot.
43
+ */
44
+ export declare function projectVersionSnapshot(adapter: DynamoAdapter, parent: {
45
+ kind: 'collection' | 'global';
46
+ slug: string;
47
+ }, data: Record<string, unknown>): Record<string, unknown>;
48
+ /**
49
+ * Strip a fully-merged version row (top-level metadata + nested `version`
50
+ * snapshot) against the parent collection or global's fields.
51
+ */
52
+ export declare function projectVersionRow(adapter: DynamoAdapter, parent: {
53
+ kind: 'collection' | 'global';
54
+ slug: string;
55
+ }, row: Record<string, unknown>): Record<string, unknown>;
56
+ //# sourceMappingURL=resolveSchema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveSchema.d.ts","sourceRoot":"","sources":["../../src/utilities/resolveSchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIhD;;;;;;;;;GASG;AAEH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,IAAI,CAGxF;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,IAAI,CAKpF;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,oEAAqE,CAAA;AAEnG;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,qHAU5B,CAAA;AAEV;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIzB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIzB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE;IAAE,IAAI,EAAE,YAAY,GAAG,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACvD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE;IAAE,IAAI,EAAE,YAAY,GAAG,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACvD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB"}
@@ -0,0 +1,84 @@
1
+ import { pickConfiguredFields, pickConfiguredVersionRow } from './pickConfiguredFields.js';
2
+ /**
3
+ * Look up the sanitized `fields` array for a collection or global. Lives in
4
+ * one place so the write-path projection (see `pickConfiguredFields`) doesn't
5
+ * have to learn Payload's collection/global storage layout per call site.
6
+ *
7
+ * Returns `null` (rather than throwing) when a slug is unrecognized — a
8
+ * caller using an out-of-config slug is a programmer error elsewhere, but
9
+ * the right blast radius for that here is "skip projection," not "kill the
10
+ * write."
11
+ */ export function getCollectionFields(adapter, slug) {
12
+ const config = adapter.payload?.collections?.[slug]?.config;
13
+ return config?.fields ?? null;
14
+ }
15
+ export function getGlobalFields(adapter, slug) {
16
+ const globals = adapter.payload?.config?.globals;
17
+ if (!Array.isArray(globals)) return null;
18
+ const config = globals.find((g)=>g.slug === slug);
19
+ return config?.fields ?? null;
20
+ }
21
+ /**
22
+ * Reserved keys Payload's framework layer owns rather than declaring through
23
+ * `fields`. Allowed at the top of every collection/global row.
24
+ *
25
+ * - `id` — primary key
26
+ * - `createdAt`/`updatedAt` — timestamp pair (default-on per collection config)
27
+ * - `_status` — drafts state (only present when drafts enabled,
28
+ * but allow-listing it unconditionally is harmless)
29
+ * - `globalType` — Payload tags global rows with this internally
30
+ */ export const ROW_RESERVED_KEYS = [
31
+ 'id',
32
+ 'createdAt',
33
+ 'updatedAt',
34
+ '_status',
35
+ 'globalType'
36
+ ];
37
+ /**
38
+ * Reserved keys at the top of a *version* row. The inner `version` field
39
+ * holds the actual doc snapshot and is recursed into separately against the
40
+ * parent collection/global's own fields.
41
+ */ export const VERSION_ROW_RESERVED_KEYS = [
42
+ 'id',
43
+ 'parent',
44
+ 'version',
45
+ 'createdAt',
46
+ 'updatedAt',
47
+ 'latest',
48
+ 'autosave',
49
+ 'snapshot',
50
+ 'publishedLocale'
51
+ ];
52
+ /**
53
+ * Strip a row against its collection's `fields`, falling back to a no-op
54
+ * if the slug isn't recognized (defensive — see `getCollectionFields`).
55
+ */ export function projectForCollection(adapter, slug, data) {
56
+ const fields = getCollectionFields(adapter, slug);
57
+ if (!fields) return data;
58
+ return pickConfiguredFields(data, fields, ROW_RESERVED_KEYS);
59
+ }
60
+ export function projectForGlobal(adapter, slug, data) {
61
+ const fields = getGlobalFields(adapter, slug);
62
+ if (!fields) return data;
63
+ return pickConfiguredFields(data, fields, ROW_RESERVED_KEYS);
64
+ }
65
+ /**
66
+ * Strip the inner doc snapshot of a version's `versionData` argument against
67
+ * the parent collection or global's fields. The createVersion adapters wrap
68
+ * the result into a version row whose other keys (parent, latest, ...) are
69
+ * set explicitly by the adapter, so we only need to project the snapshot.
70
+ */ export function projectVersionSnapshot(adapter, parent, data) {
71
+ const fields = parent.kind === 'collection' ? getCollectionFields(adapter, parent.slug) : getGlobalFields(adapter, parent.slug);
72
+ if (!fields) return data;
73
+ return pickConfiguredFields(data, fields, ROW_RESERVED_KEYS);
74
+ }
75
+ /**
76
+ * Strip a fully-merged version row (top-level metadata + nested `version`
77
+ * snapshot) against the parent collection or global's fields.
78
+ */ export function projectVersionRow(adapter, parent, row) {
79
+ const fields = parent.kind === 'collection' ? getCollectionFields(adapter, parent.slug) : getGlobalFields(adapter, parent.slug);
80
+ if (!fields) return row;
81
+ return pickConfiguredVersionRow(row, fields);
82
+ }
83
+
84
+ //# sourceMappingURL=resolveSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/resolveSchema.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { DynamoAdapter } from '../types.js'\n\nimport { pickConfiguredFields, pickConfiguredVersionRow } from './pickConfiguredFields.js'\n\n/**\n * Look up the sanitized `fields` array for a collection or global. Lives in\n * one place so the write-path projection (see `pickConfiguredFields`) doesn't\n * have to learn Payload's collection/global storage layout per call site.\n *\n * Returns `null` (rather than throwing) when a slug is unrecognized — a\n * caller using an out-of-config slug is a programmer error elsewhere, but\n * the right blast radius for that here is \"skip projection,\" not \"kill the\n * write.\"\n */\n\nexport function getCollectionFields(adapter: DynamoAdapter, slug: string): Field[] | null {\n const config = adapter.payload?.collections?.[slug]?.config\n return config?.fields ?? null\n}\n\nexport function getGlobalFields(adapter: DynamoAdapter, slug: string): Field[] | null {\n const globals = adapter.payload?.config?.globals\n if (!Array.isArray(globals)) return null\n const config = globals.find((g: { slug: string }) => g.slug === slug)\n return config?.fields ?? null\n}\n\n/**\n * Reserved keys Payload's framework layer owns rather than declaring through\n * `fields`. Allowed at the top of every collection/global row.\n *\n * - `id` — primary key\n * - `createdAt`/`updatedAt` — timestamp pair (default-on per collection config)\n * - `_status` — drafts state (only present when drafts enabled,\n * but allow-listing it unconditionally is harmless)\n * - `globalType` — Payload tags global rows with this internally\n */\nexport const ROW_RESERVED_KEYS = ['id', 'createdAt', 'updatedAt', '_status', 'globalType'] as const\n\n/**\n * Reserved keys at the top of a *version* row. The inner `version` field\n * holds the actual doc snapshot and is recursed into separately against the\n * parent collection/global's own fields.\n */\nexport const VERSION_ROW_RESERVED_KEYS = [\n 'id',\n 'parent',\n 'version',\n 'createdAt',\n 'updatedAt',\n 'latest',\n 'autosave',\n 'snapshot',\n 'publishedLocale',\n] as const\n\n/**\n * Strip a row against its collection's `fields`, falling back to a no-op\n * if the slug isn't recognized (defensive — see `getCollectionFields`).\n */\nexport function projectForCollection(\n adapter: DynamoAdapter,\n slug: string,\n data: Record<string, unknown>,\n): Record<string, unknown> {\n const fields = getCollectionFields(adapter, slug)\n if (!fields) return data\n return pickConfiguredFields(data, fields, ROW_RESERVED_KEYS)\n}\n\nexport function projectForGlobal(\n adapter: DynamoAdapter,\n slug: string,\n data: Record<string, unknown>,\n): Record<string, unknown> {\n const fields = getGlobalFields(adapter, slug)\n if (!fields) return data\n return pickConfiguredFields(data, fields, ROW_RESERVED_KEYS)\n}\n\n/**\n * Strip the inner doc snapshot of a version's `versionData` argument against\n * the parent collection or global's fields. The createVersion adapters wrap\n * the result into a version row whose other keys (parent, latest, ...) are\n * set explicitly by the adapter, so we only need to project the snapshot.\n */\nexport function projectVersionSnapshot(\n adapter: DynamoAdapter,\n parent: { kind: 'collection' | 'global'; slug: string },\n data: Record<string, unknown>,\n): Record<string, unknown> {\n const fields =\n parent.kind === 'collection'\n ? getCollectionFields(adapter, parent.slug)\n : getGlobalFields(adapter, parent.slug)\n if (!fields) return data\n return pickConfiguredFields(data, fields, ROW_RESERVED_KEYS)\n}\n\n/**\n * Strip a fully-merged version row (top-level metadata + nested `version`\n * snapshot) against the parent collection or global's fields.\n */\nexport function projectVersionRow(\n adapter: DynamoAdapter,\n parent: { kind: 'collection' | 'global'; slug: string },\n row: Record<string, unknown>,\n): Record<string, unknown> {\n const fields =\n parent.kind === 'collection'\n ? getCollectionFields(adapter, parent.slug)\n : getGlobalFields(adapter, parent.slug)\n if (!fields) return row\n return pickConfiguredVersionRow(row, fields)\n}\n"],"names":["pickConfiguredFields","pickConfiguredVersionRow","getCollectionFields","adapter","slug","config","payload","collections","fields","getGlobalFields","globals","Array","isArray","find","g","ROW_RESERVED_KEYS","VERSION_ROW_RESERVED_KEYS","projectForCollection","data","projectForGlobal","projectVersionSnapshot","parent","kind","projectVersionRow","row"],"mappings":"AAIA,SAASA,oBAAoB,EAAEC,wBAAwB,QAAQ,4BAA2B;AAE1F;;;;;;;;;CASC,GAED,OAAO,SAASC,oBAAoBC,OAAsB,EAAEC,IAAY;IACtE,MAAMC,SAASF,QAAQG,OAAO,EAAEC,aAAa,CAACH,KAAK,EAAEC;IACrD,OAAOA,QAAQG,UAAU;AAC3B;AAEA,OAAO,SAASC,gBAAgBN,OAAsB,EAAEC,IAAY;IAClE,MAAMM,UAAUP,QAAQG,OAAO,EAAED,QAAQK;IACzC,IAAI,CAACC,MAAMC,OAAO,CAACF,UAAU,OAAO;IACpC,MAAML,SAASK,QAAQG,IAAI,CAAC,CAACC,IAAwBA,EAAEV,IAAI,KAAKA;IAChE,OAAOC,QAAQG,UAAU;AAC3B;AAEA;;;;;;;;;CASC,GACD,OAAO,MAAMO,oBAAoB;IAAC;IAAM;IAAa;IAAa;IAAW;CAAa,CAAS;AAEnG;;;;CAIC,GACD,OAAO,MAAMC,4BAA4B;IACvC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAS;AAEV;;;CAGC,GACD,OAAO,SAASC,qBACdd,OAAsB,EACtBC,IAAY,EACZc,IAA6B;IAE7B,MAAMV,SAASN,oBAAoBC,SAASC;IAC5C,IAAI,CAACI,QAAQ,OAAOU;IACpB,OAAOlB,qBAAqBkB,MAAMV,QAAQO;AAC5C;AAEA,OAAO,SAASI,iBACdhB,OAAsB,EACtBC,IAAY,EACZc,IAA6B;IAE7B,MAAMV,SAASC,gBAAgBN,SAASC;IACxC,IAAI,CAACI,QAAQ,OAAOU;IACpB,OAAOlB,qBAAqBkB,MAAMV,QAAQO;AAC5C;AAEA;;;;;CAKC,GACD,OAAO,SAASK,uBACdjB,OAAsB,EACtBkB,MAAuD,EACvDH,IAA6B;IAE7B,MAAMV,SACJa,OAAOC,IAAI,KAAK,eACZpB,oBAAoBC,SAASkB,OAAOjB,IAAI,IACxCK,gBAAgBN,SAASkB,OAAOjB,IAAI;IAC1C,IAAI,CAACI,QAAQ,OAAOU;IACpB,OAAOlB,qBAAqBkB,MAAMV,QAAQO;AAC5C;AAEA;;;CAGC,GACD,OAAO,SAASQ,kBACdpB,OAAsB,EACtBkB,MAAuD,EACvDG,GAA4B;IAE5B,MAAMhB,SACJa,OAAOC,IAAI,KAAK,eACZpB,oBAAoBC,SAASkB,OAAOjB,IAAI,IACxCK,gBAAgBN,SAASkB,OAAOjB,IAAI;IAC1C,IAAI,CAACI,QAAQ,OAAOgB;IACpB,OAAOvB,yBAAyBuB,KAAKhB;AACvC"}
@@ -0,0 +1,44 @@
1
+ import type { Payload } from 'payload';
2
+ /**
3
+ * One-shot cleanup pass for rows that were written before write-time
4
+ * projection landed. Walks every collection, global, and versions partition
5
+ * the adapter knows about, applies the same projection that today's writes
6
+ * use, and re-`Put`s rows that changed. Rows whose contents are already
7
+ * clean stay untouched.
8
+ *
9
+ * Intended to be called once after upgrading; not on every boot. The
10
+ * adapter's regular write paths now project on every mutation, so once a
11
+ * row has been written under the new code it stays clean. This helper
12
+ * exists for the static legacy data that hasn't been touched since the bug
13
+ * was active.
14
+ *
15
+ * @example
16
+ * import { getPayload } from 'payload'
17
+ * import config from './payload.config'
18
+ * import { scrubUnknownFields } from '@aih-software/payload-ddb'
19
+ *
20
+ * const payload = await getPayload({ config })
21
+ * const report = await scrubUnknownFields(payload)
22
+ * console.log(report)
23
+ * await payload.destroy()
24
+ */
25
+ export interface ScrubReport {
26
+ collections: Record<string, {
27
+ scanned: number;
28
+ modified: number;
29
+ }>;
30
+ collectionVersions: Record<string, {
31
+ scanned: number;
32
+ modified: number;
33
+ }>;
34
+ globals: Record<string, {
35
+ scanned: number;
36
+ modified: number;
37
+ }>;
38
+ globalVersions: Record<string, {
39
+ scanned: number;
40
+ modified: number;
41
+ }>;
42
+ }
43
+ export declare function scrubUnknownFields(payload: Payload): Promise<ScrubReport>;
44
+ //# sourceMappingURL=scrubUnknownFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrubUnknownFields.d.ts","sourceRoot":"","sources":["../../src/utilities/scrubUnknownFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAyB,MAAM,SAAS,CAAA;AAa7D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClE,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC9D,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACtE;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAkD/E"}