@open-aih/payload-ddb 3.1.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +141 -18
  2. package/dist/count.d.ts.map +1 -1
  3. package/dist/count.js +4 -2
  4. package/dist/count.js.map +1 -1
  5. package/dist/countGlobalVersions.d.ts.map +1 -1
  6. package/dist/countGlobalVersions.js +6 -1
  7. package/dist/countGlobalVersions.js.map +1 -1
  8. package/dist/countVersions.d.ts.map +1 -1
  9. package/dist/countVersions.js +6 -1
  10. package/dist/countVersions.js.map +1 -1
  11. package/dist/create.d.ts.map +1 -1
  12. package/dist/create.js +31 -15
  13. package/dist/create.js.map +1 -1
  14. package/dist/createGlobal.d.ts.map +1 -1
  15. package/dist/createGlobal.js +2 -14
  16. package/dist/createGlobal.js.map +1 -1
  17. package/dist/createGlobalVersion.d.ts +1 -1
  18. package/dist/createGlobalVersion.d.ts.map +1 -1
  19. package/dist/createGlobalVersion.js +3 -41
  20. package/dist/createGlobalVersion.js.map +1 -1
  21. package/dist/createVersion.d.ts +6 -5
  22. package/dist/createVersion.d.ts.map +1 -1
  23. package/dist/createVersion.js +8 -48
  24. package/dist/createVersion.js.map +1 -1
  25. package/dist/deleteMany.d.ts +1 -6
  26. package/dist/deleteMany.d.ts.map +1 -1
  27. package/dist/deleteMany.js +8 -19
  28. package/dist/deleteMany.js.map +1 -1
  29. package/dist/deleteOne.d.ts.map +1 -1
  30. package/dist/deleteOne.js +20 -24
  31. package/dist/deleteOne.js.map +1 -1
  32. package/dist/deleteVersions.d.ts.map +1 -1
  33. package/dist/deleteVersions.js +8 -14
  34. package/dist/deleteVersions.js.map +1 -1
  35. package/dist/find.d.ts.map +1 -1
  36. package/dist/find.js +15 -2
  37. package/dist/find.js.map +1 -1
  38. package/dist/findDistinct.d.ts.map +1 -1
  39. package/dist/findDistinct.js +3 -1
  40. package/dist/findDistinct.js.map +1 -1
  41. package/dist/findGlobal.d.ts.map +1 -1
  42. package/dist/findGlobal.js +3 -2
  43. package/dist/findGlobal.js.map +1 -1
  44. package/dist/findGlobalVersions.d.ts.map +1 -1
  45. package/dist/findGlobalVersions.js +10 -2
  46. package/dist/findGlobalVersions.js.map +1 -1
  47. package/dist/findOne.d.ts.map +1 -1
  48. package/dist/findOne.js +17 -6
  49. package/dist/findOne.js.map +1 -1
  50. package/dist/findVersions.d.ts.map +1 -1
  51. package/dist/findVersions.js +10 -2
  52. package/dist/findVersions.js.map +1 -1
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +7 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/queryDrafts.d.ts.map +1 -1
  58. package/dist/queryDrafts.js +24 -4
  59. package/dist/queryDrafts.js.map +1 -1
  60. package/dist/updateGlobal.d.ts +5 -4
  61. package/dist/updateGlobal.d.ts.map +1 -1
  62. package/dist/updateGlobal.js +22 -36
  63. package/dist/updateGlobal.js.map +1 -1
  64. package/dist/updateGlobalVersion.d.ts.map +1 -1
  65. package/dist/updateGlobalVersion.js +3 -49
  66. package/dist/updateGlobalVersion.js.map +1 -1
  67. package/dist/updateJobs.d.ts +21 -0
  68. package/dist/updateJobs.d.ts.map +1 -0
  69. package/dist/updateJobs.js +83 -0
  70. package/dist/updateJobs.js.map +1 -0
  71. package/dist/updateMany.d.ts +9 -3
  72. package/dist/updateMany.d.ts.map +1 -1
  73. package/dist/updateMany.js +47 -30
  74. package/dist/updateMany.js.map +1 -1
  75. package/dist/updateOne.d.ts +6 -1
  76. package/dist/updateOne.d.ts.map +1 -1
  77. package/dist/updateOne.js +49 -49
  78. package/dist/updateOne.js.map +1 -1
  79. package/dist/updateVersion.d.ts.map +1 -1
  80. package/dist/updateVersion.js +3 -52
  81. package/dist/updateVersion.js.map +1 -1
  82. package/dist/upsert.d.ts +6 -0
  83. package/dist/upsert.d.ts.map +1 -1
  84. package/dist/upsert.js +57 -41
  85. package/dist/upsert.js.map +1 -1
  86. package/dist/utilities/backfillUniqueGuards.d.ts +42 -0
  87. package/dist/utilities/backfillUniqueGuards.d.ts.map +1 -0
  88. package/dist/utilities/backfillUniqueGuards.js +39 -0
  89. package/dist/utilities/backfillUniqueGuards.js.map +1 -0
  90. package/dist/utilities/buildFilterExpression.d.ts +29 -14
  91. package/dist/utilities/buildFilterExpression.d.ts.map +1 -1
  92. package/dist/utilities/buildFilterExpression.js +55 -14
  93. package/dist/utilities/buildFilterExpression.js.map +1 -1
  94. package/dist/utilities/castWhere.d.ts +84 -0
  95. package/dist/utilities/castWhere.d.ts.map +1 -0
  96. package/dist/utilities/castWhere.js +254 -0
  97. package/dist/utilities/castWhere.js.map +1 -0
  98. package/dist/utilities/findFirst.d.ts +3 -2
  99. package/dist/utilities/findFirst.d.ts.map +1 -1
  100. package/dist/utilities/findFirst.js +14 -46
  101. package/dist/utilities/findFirst.js.map +1 -1
  102. package/dist/utilities/geo.d.ts +36 -0
  103. package/dist/utilities/geo.d.ts.map +1 -0
  104. package/dist/utilities/geo.js +108 -0
  105. package/dist/utilities/geo.js.map +1 -0
  106. package/dist/utilities/mapWithConcurrency.d.ts +12 -0
  107. package/dist/utilities/mapWithConcurrency.d.ts.map +1 -0
  108. package/dist/utilities/mapWithConcurrency.js +32 -0
  109. package/dist/utilities/mapWithConcurrency.js.map +1 -0
  110. package/dist/utilities/matchesWhere.d.ts +25 -5
  111. package/dist/utilities/matchesWhere.d.ts.map +1 -1
  112. package/dist/utilities/matchesWhere.js +150 -38
  113. package/dist/utilities/matchesWhere.js.map +1 -1
  114. package/dist/utilities/operators.d.ts +22 -12
  115. package/dist/utilities/operators.d.ts.map +1 -1
  116. package/dist/utilities/operators.js +36 -31
  117. package/dist/utilities/operators.js.map +1 -1
  118. package/dist/utilities/optimistic.d.ts +29 -0
  119. package/dist/utilities/optimistic.d.ts.map +1 -0
  120. package/dist/utilities/optimistic.js +58 -0
  121. package/dist/utilities/optimistic.js.map +1 -0
  122. package/dist/utilities/prepareWhere.d.ts +63 -0
  123. package/dist/utilities/prepareWhere.d.ts.map +1 -0
  124. package/dist/utilities/prepareWhere.js +198 -0
  125. package/dist/utilities/prepareWhere.js.map +1 -0
  126. package/dist/utilities/queryCount.d.ts +6 -3
  127. package/dist/utilities/queryCount.d.ts.map +1 -1
  128. package/dist/utilities/queryCount.js +16 -45
  129. package/dist/utilities/queryCount.js.map +1 -1
  130. package/dist/utilities/queryMatching.d.ts +8 -7
  131. package/dist/utilities/queryMatching.d.ts.map +1 -1
  132. package/dist/utilities/queryMatching.js +17 -54
  133. package/dist/utilities/queryMatching.js.map +1 -1
  134. package/dist/utilities/queryPartition.d.ts +26 -0
  135. package/dist/utilities/queryPartition.d.ts.map +1 -0
  136. package/dist/utilities/queryPartition.js +55 -0
  137. package/dist/utilities/queryPartition.js.map +1 -0
  138. package/dist/utilities/resolveJoins.d.ts +37 -0
  139. package/dist/utilities/resolveJoins.d.ts.map +1 -0
  140. package/dist/utilities/resolveJoins.js +195 -0
  141. package/dist/utilities/resolveJoins.js.map +1 -0
  142. package/dist/utilities/stripInternalKeys.d.ts +17 -3
  143. package/dist/utilities/stripInternalKeys.d.ts.map +1 -1
  144. package/dist/utilities/stripInternalKeys.js +25 -4
  145. package/dist/utilities/stripInternalKeys.js.map +1 -1
  146. package/dist/utilities/uniqueGuards.d.ts +39 -0
  147. package/dist/utilities/uniqueGuards.d.ts.map +1 -0
  148. package/dist/utilities/uniqueGuards.js +106 -0
  149. package/dist/utilities/uniqueGuards.js.map +1 -0
  150. package/dist/utilities/updateVersionRow.d.ts +19 -0
  151. package/dist/utilities/updateVersionRow.d.ts.map +1 -0
  152. package/dist/utilities/updateVersionRow.js +40 -0
  153. package/dist/utilities/updateVersionRow.js.map +1 -0
  154. package/dist/utilities/writeErrors.d.ts +11 -0
  155. package/dist/utilities/writeErrors.d.ts.map +1 -0
  156. package/dist/utilities/writeErrors.js +27 -0
  157. package/dist/utilities/writeErrors.js.map +1 -0
  158. package/dist/utilities/writeItems.d.ts +124 -0
  159. package/dist/utilities/writeItems.d.ts.map +1 -0
  160. package/dist/utilities/writeItems.js +383 -0
  161. package/dist/utilities/writeItems.js.map +1 -0
  162. package/package.json +21 -14
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `Promise.all(items.map(fn))`, but with at most `limit` calls in flight.
3
+ * Results come back in input order.
4
+ *
5
+ * Bulk writes need this: firing one request per row all at once means a
6
+ * `deleteMany` over a few thousand rows opens a few thousand concurrent
7
+ * requests, which exhausts the SDK's socket pool and invites throttling.
8
+ * A rejection stops new work from starting and rejects the whole call, same
9
+ * as `Promise.all`.
10
+ */ export async function mapWithConcurrency(items, limit, fn) {
11
+ const results = new Array(items.length);
12
+ let next = 0;
13
+ let failed = false;
14
+ const worker = async ()=>{
15
+ while(!failed && next < items.length){
16
+ const index = next++;
17
+ try {
18
+ results[index] = await fn(items[index], index);
19
+ } catch (err) {
20
+ failed = true;
21
+ throw err;
22
+ }
23
+ }
24
+ };
25
+ const workers = Math.max(1, Math.min(limit, items.length));
26
+ await Promise.all(Array.from({
27
+ length: workers
28
+ }, worker));
29
+ return results;
30
+ }
31
+
32
+ //# sourceMappingURL=mapWithConcurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/mapWithConcurrency.ts"],"sourcesContent":["/**\n * `Promise.all(items.map(fn))`, but with at most `limit` calls in flight.\n * Results come back in input order.\n *\n * Bulk writes need this: firing one request per row all at once means a\n * `deleteMany` over a few thousand rows opens a few thousand concurrent\n * requests, which exhausts the SDK's socket pool and invites throttling.\n * A rejection stops new work from starting and rejects the whole call, same\n * as `Promise.all`.\n */\nexport async function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length)\n let next = 0\n let failed = false\n\n const worker = async (): Promise<void> => {\n while (!failed && next < items.length) {\n const index = next++\n try {\n results[index] = await fn(items[index] as T, index)\n } catch (err) {\n failed = true\n throw err\n }\n }\n }\n\n const workers = Math.max(1, Math.min(limit, items.length))\n await Promise.all(Array.from({ length: workers }, worker))\n return results\n}\n"],"names":["mapWithConcurrency","items","limit","fn","results","Array","length","next","failed","worker","index","err","workers","Math","max","min","Promise","all","from"],"mappings":"AAAA;;;;;;;;;CASC,GACD,OAAO,eAAeA,mBACpBC,KAAmB,EACnBC,KAAa,EACbC,EAA0C;IAE1C,MAAMC,UAAU,IAAIC,MAASJ,MAAMK,MAAM;IACzC,IAAIC,OAAO;IACX,IAAIC,SAAS;IAEb,MAAMC,SAAS;QACb,MAAO,CAACD,UAAUD,OAAON,MAAMK,MAAM,CAAE;YACrC,MAAMI,QAAQH;YACd,IAAI;gBACFH,OAAO,CAACM,MAAM,GAAG,MAAMP,GAAGF,KAAK,CAACS,MAAM,EAAOA;YAC/C,EAAE,OAAOC,KAAK;gBACZH,SAAS;gBACT,MAAMG;YACR;QACF;IACF;IAEA,MAAMC,UAAUC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAACb,OAAOD,MAAMK,MAAM;IACxD,MAAMU,QAAQC,GAAG,CAACZ,MAAMa,IAAI,CAAC;QAAEZ,QAAQM;IAAQ,GAAGH;IAClD,OAAOL;AACT"}
@@ -1,12 +1,32 @@
1
1
  import type { Where } from 'payload';
2
2
  /**
3
3
  * In-memory predicate that evaluates a Payload `Where` against a fetched item.
4
- * Used as a fallback when the query cannot be reduced to a `GetItem` and we
5
- * have to `Scan` the table.
4
+ * This is the adapter's reference evaluator: `buildFilterExpression` pushes
5
+ * down whatever part of a `where` DynamoDB can express, and every row that
6
+ * comes back from an inexact pushdown is re-checked here. It also runs alone
7
+ * for rows that only take their doc shape after projection (drafts
8
+ * supplements, `queryDrafts`).
6
9
  *
7
- * Supports: `equals`, `not_equals`, `exists`, `in`, `not_in`, `and`, `or`.
8
- * Throws on any other operator so we surface coverage gaps instead of
10
+ * Where a clause can be evaluated by both, the two must return the same rows
11
+ * — `test/parity` pins that.
12
+ *
13
+ * Paths follow MongoDB's array semantics, which is what Payload's query
14
+ * language assumes: an array anywhere along a path is transparent, so
15
+ * `tags equals 'x'` matches a `hasMany` value containing `'x'` and
16
+ * `items.label equals 'x'` matches when any item has that label. A negative
17
+ * operator (`not_equals`, `not_in`, `not_like`) holds only when *no* element
18
+ * matches its positive form.
19
+ *
20
+ * Throws on any unsupported operator so we surface coverage gaps instead of
9
21
  * silently returning wrong results.
10
22
  */
11
- export declare function matchesWhere(item: Record<string, unknown>, where: Where | undefined): boolean;
23
+ export declare function matchesWhere(item: Record<string, unknown>, where: undefined | Where): boolean;
24
+ /**
25
+ * Every value a dotted path can reach in `source`, treating arrays as
26
+ * transparent. A path that ends on an array yields the array itself (so an
27
+ * array operand can match it whole, and `exists` sees an empty list as a
28
+ * value) followed by each of its elements. A path that reaches nothing
29
+ * yields no candidates.
30
+ */
31
+ export declare function candidatesAt(source: unknown, path: string): unknown[];
12
32
  //# sourceMappingURL=matchesWhere.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"matchesWhere.d.ts","sourceRoot":"","sources":["../../src/utilities/matchesWhere.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAKpC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,OAAO,CA8C7F"}
1
+ {"version":3,"file":"matchesWhere.d.ts","sourceRoot":"","sources":["../../src/utilities/matchesWhere.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAOpC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,SAAS,GAAG,KAAK,GAAG,OAAO,CAuD7F;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAIrE"}
@@ -1,12 +1,26 @@
1
+ import { matchesIntersects, matchesNear, matchesWithin } from './geo.js';
1
2
  import { getByPath } from './getByPath.js';
2
- import { SUPPORTED_OPERATORS, unsupportedOperatorError } from './operators.js';
3
+ import { normalizeForDynamo } from './normalizeForDynamo.js';
4
+ import { JS_ONLY_MARKER, SUPPORTED_OPERATORS, unsupportedOperatorError } from './operators.js';
3
5
  /**
4
6
  * In-memory predicate that evaluates a Payload `Where` against a fetched item.
5
- * Used as a fallback when the query cannot be reduced to a `GetItem` and we
6
- * have to `Scan` the table.
7
+ * This is the adapter's reference evaluator: `buildFilterExpression` pushes
8
+ * down whatever part of a `where` DynamoDB can express, and every row that
9
+ * comes back from an inexact pushdown is re-checked here. It also runs alone
10
+ * for rows that only take their doc shape after projection (drafts
11
+ * supplements, `queryDrafts`).
7
12
  *
8
- * Supports: `equals`, `not_equals`, `exists`, `in`, `not_in`, `and`, `or`.
9
- * Throws on any other operator so we surface coverage gaps instead of
13
+ * Where a clause can be evaluated by both, the two must return the same rows
14
+ * — `test/parity` pins that.
15
+ *
16
+ * Paths follow MongoDB's array semantics, which is what Payload's query
17
+ * language assumes: an array anywhere along a path is transparent, so
18
+ * `tags equals 'x'` matches a `hasMany` value containing `'x'` and
19
+ * `items.label equals 'x'` matches when any item has that label. A negative
20
+ * operator (`not_equals`, `not_in`, `not_like`) holds only when *no* element
21
+ * matches its positive form.
22
+ *
23
+ * Throws on any unsupported operator so we surface coverage gaps instead of
10
24
  * silently returning wrong results.
11
25
  */ export function matchesWhere(item, where) {
12
26
  if (!where) {
@@ -35,64 +49,162 @@ import { SUPPORTED_OPERATORS, unsupportedOperatorError } from './operators.js';
35
49
  if (!raw || typeof raw !== 'object') {
36
50
  continue;
37
51
  }
38
- const fieldValue = getByPath(item, key);
39
52
  const operators = raw;
53
+ let candidates;
40
54
  for (const [operator, expected] of Object.entries(operators)){
55
+ if (operator === JS_ONLY_MARKER) continue;
41
56
  if (!SUPPORTED_OPERATORS.has(operator)) {
42
57
  throw unsupportedOperatorError(operator, key);
43
58
  }
44
- if (!evaluate(fieldValue, operator, expected)) {
59
+ // Geometry reads the stored point whole — `[lng, lat]` is a value, not
60
+ // a list of candidates.
61
+ if (operator === 'near' || operator === 'within' || operator === 'intersects') {
62
+ if (!evaluateGeo(getByPath(item, key), operator, expected)) return false;
63
+ continue;
64
+ }
65
+ candidates ??= candidatesAt(item, key);
66
+ if (!evaluate(candidates, operator, expected)) {
45
67
  return false;
46
68
  }
47
69
  }
48
70
  }
49
71
  return true;
50
72
  }
51
- function evaluate(actual, operator, expected) {
73
+ /**
74
+ * Every value a dotted path can reach in `source`, treating arrays as
75
+ * transparent. A path that ends on an array yields the array itself (so an
76
+ * array operand can match it whole, and `exists` sees an empty list as a
77
+ * value) followed by each of its elements. A path that reaches nothing
78
+ * yields no candidates.
79
+ */ export function candidatesAt(source, path) {
80
+ const out = [];
81
+ collect(source, path.split('.'), 0, out);
82
+ return out;
83
+ }
84
+ function collect(value, segments, index, out) {
85
+ if (index === segments.length) {
86
+ if (value === undefined) return;
87
+ out.push(value);
88
+ if (Array.isArray(value)) {
89
+ for (const member of value){
90
+ if (member !== undefined) out.push(member);
91
+ }
92
+ }
93
+ return;
94
+ }
95
+ if (Array.isArray(value)) {
96
+ for (const member of value)collect(member, segments, index, out);
97
+ return;
98
+ }
99
+ if (value === null || typeof value !== 'object') return;
100
+ collect(value[segments[index]], segments, index + 1, out);
101
+ }
102
+ function evaluateGeo(value, operator, expected) {
103
+ switch(operator){
104
+ case 'intersects':
105
+ return matchesIntersects(value, expected);
106
+ case 'near':
107
+ return matchesNear(value, expected);
108
+ default:
109
+ return matchesWithin(value, expected);
110
+ }
111
+ }
112
+ function evaluate(candidates, operator, rawExpected) {
113
+ // Coerce the operand exactly as `buildFilterExpression` coerces its
114
+ // expression values (today: `Date` → ISO string, including inside `in` /
115
+ // `not_in` lists). Rows hold ISO strings, so comparing them against a raw
116
+ // `Date` would never be equal and would order by `Date#toString()` text.
117
+ const expected = normalizeForDynamo(rawExpected);
118
+ const some = (test)=>candidates.some(test);
119
+ const hasValue = (candidate)=>candidate !== undefined && candidate !== null;
52
120
  switch(operator){
121
+ case 'all':
122
+ return Array.isArray(expected) && expected.every((member)=>some((candidate)=>valuesEqual(candidate, member)));
123
+ case 'contains':
124
+ return asList(expected).some((needle)=>some((candidate)=>containsText(candidate, needle)));
53
125
  case 'equals':
54
- return actual === expected;
55
- case 'not_equals':
56
- return actual !== expected;
126
+ return some((candidate)=>valuesEqual(candidate, expected));
127
+ case 'exists':
128
+ return expected ? some(hasValue) : !some(hasValue);
57
129
  case 'greater_than':
58
- return compareLoose(actual, expected) > 0;
130
+ return some((candidate)=>compareLoose(candidate, expected) > 0);
59
131
  case 'greater_than_equal':
60
- return compareLoose(actual, expected) >= 0;
132
+ return some((candidate)=>compareLoose(candidate, expected) >= 0);
133
+ case 'in':
134
+ return Array.isArray(expected) && expected.some((member)=>some((candidate)=>valuesEqual(candidate, member)));
61
135
  case 'less_than':
62
- return compareLoose(actual, expected) < 0;
136
+ return some((candidate)=>compareLoose(candidate, expected) < 0);
63
137
  case 'less_than_equal':
64
- return compareLoose(actual, expected) <= 0;
65
- case 'exists':
66
- return expected ? actual !== undefined && actual !== null : actual === undefined || actual === null;
67
- case 'in':
68
- return Array.isArray(expected) && expected.includes(actual);
69
- case 'not_in':
70
- return Array.isArray(expected) && !expected.includes(actual);
138
+ return some((candidate)=>compareLoose(candidate, expected) <= 0);
71
139
  case 'like':
72
- {
73
- // Empty needle means "no constraint" — mirrors SQL `LIKE '%%'` and what
74
- // Payload's admin search produces when the search input is cleared.
75
- if (typeof expected !== 'string' || expected === '') return true;
76
- if (typeof actual !== 'string') return false;
77
- return actual.toLowerCase().includes(expected.toLowerCase());
78
- }
140
+ return isLike(candidates, expected);
141
+ case 'not_equals':
142
+ return !some((candidate)=>valuesEqual(candidate, expected));
143
+ case 'not_in':
144
+ return Array.isArray(expected) && !expected.some((member)=>some((candidate)=>valuesEqual(candidate, member)));
145
+ case 'not_like':
146
+ return !isLike(candidates, expected);
79
147
  default:
80
148
  return false;
81
149
  }
82
150
  }
151
+ const asList = (value)=>Array.isArray(value) ? value : [
152
+ value
153
+ ];
154
+ function isLike(candidates, expected) {
155
+ // Empty needle means "no constraint" — mirrors SQL `LIKE '%%'` and what
156
+ // Payload's admin search produces when the search input is cleared.
157
+ if (typeof expected !== 'string' || expected === '') return true;
158
+ const needle = expected.toLowerCase();
159
+ return candidates.some((candidate)=>typeof candidate === 'string' && candidate.toLowerCase().includes(needle));
160
+ }
83
161
  /**
84
- * Loose ordered comparison matching DynamoDB's `<`/`>` semantics: numbers and
85
- * strings (including ISO date strings) compare naturally; nullish on either
86
- * side yields a non-match by returning `NaN`. Lets `greater_than`/`less_than`
87
- * mirror the server-side filter evaluator behavior.
162
+ * `contains`: case-insensitive substring, over text and numbers (a `hasMany`
163
+ * number field is searched by its digits, as the Mongo adapter does).
164
+ */ function containsText(candidate, needle) {
165
+ if (typeof needle !== 'string' && typeof needle !== 'number') return false;
166
+ if (typeof candidate !== 'string' && typeof candidate !== 'number') return false;
167
+ return String(candidate).toLowerCase().includes(String(needle).toLowerCase());
168
+ }
169
+ /**
170
+ * Equality as DynamoDB's `=` defines it: scalars by value and type, lists
171
+ * element-by-element in order, maps key-by-key regardless of key order. A
172
+ * plain `===` would compare objects by reference and never match — which
173
+ * breaks object operands such as a polymorphic relationship's
174
+ * `{ relationTo, value }`.
175
+ *
176
+ * Keys holding `undefined` are ignored on both sides, since the marshaller
177
+ * drops them before anything reaches DynamoDB (`removeUndefinedValues`).
178
+ */ function valuesEqual(a, b) {
179
+ if (a === b) return true;
180
+ if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;
181
+ if (Array.isArray(a) || Array.isArray(b)) {
182
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
183
+ return a.every((member, index)=>valuesEqual(member, b[index]));
184
+ }
185
+ const left = a;
186
+ const right = b;
187
+ const definedKeys = (record)=>Object.keys(record).filter((key)=>record[key] !== undefined);
188
+ const leftKeys = definedKeys(left);
189
+ if (leftKeys.length !== definedKeys(right).length) return false;
190
+ return leftKeys.every((key)=>right[key] !== undefined && valuesEqual(left[key], right[key]));
191
+ }
192
+ /**
193
+ * Ordered comparison matching DynamoDB's `<`/`>` semantics: two numbers or
194
+ * two strings (including ISO date strings) compare naturally; anything else
195
+ * — a missing side, or operands of different types — yields a non-match by
196
+ * returning `NaN`. DynamoDB never orders a number against a string, so
197
+ * falling back to string comparison here would match rows the pushdown
198
+ * rejects. `castWhere` is what turns a string operand into the field's real
199
+ * type before it gets this far.
88
200
  */ function compareLoose(a, b) {
89
- if (a === undefined || a === null || b === undefined || b === null) return NaN;
90
201
  if (typeof a === 'number' && typeof b === 'number') return a - b;
91
- const as = String(a);
92
- const bs = String(b);
93
- if (as < bs) return -1;
94
- if (as > bs) return 1;
95
- return 0;
202
+ if (typeof a === 'string' && typeof b === 'string') {
203
+ if (a < b) return -1;
204
+ if (a > b) return 1;
205
+ return 0;
206
+ }
207
+ return NaN;
96
208
  }
97
209
 
98
210
  //# sourceMappingURL=matchesWhere.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/matchesWhere.ts"],"sourcesContent":["import type { Where } from 'payload'\n\nimport { getByPath } from './getByPath.js'\nimport { SUPPORTED_OPERATORS, unsupportedOperatorError } from './operators.js'\n\n/**\n * In-memory predicate that evaluates a Payload `Where` against a fetched item.\n * Used as a fallback when the query cannot be reduced to a `GetItem` and we\n * have to `Scan` the table.\n *\n * Supports: `equals`, `not_equals`, `exists`, `in`, `not_in`, `and`, `or`.\n * Throws on any other operator so we surface coverage gaps instead of\n * silently returning wrong results.\n */\nexport function matchesWhere(item: Record<string, unknown>, where: Where | undefined): boolean {\n if (!where) {\n return true\n }\n\n for (const [key, raw] of Object.entries(where)) {\n if (key === 'and') {\n if (!Array.isArray(raw)) continue\n for (const sub of raw) {\n if (!matchesWhere(item, sub)) return false\n }\n continue\n }\n\n if (key === 'or') {\n if (!Array.isArray(raw)) continue\n let any = false\n for (const sub of raw) {\n if (matchesWhere(item, sub)) {\n any = true\n break\n }\n }\n if (!any) return false\n continue\n }\n\n if (!raw || typeof raw !== 'object') {\n continue\n }\n\n const fieldValue = getByPath(item, key)\n const operators = raw as Record<string, unknown>\n\n for (const [operator, expected] of Object.entries(operators)) {\n if (!SUPPORTED_OPERATORS.has(operator)) {\n throw unsupportedOperatorError(operator, key)\n }\n\n if (!evaluate(fieldValue, operator, expected)) {\n return false\n }\n }\n }\n\n return true\n}\n\nfunction evaluate(actual: unknown, operator: string, expected: unknown): boolean {\n switch (operator) {\n case 'equals':\n return actual === expected\n case 'not_equals':\n return actual !== expected\n case 'greater_than':\n return compareLoose(actual, expected) > 0\n case 'greater_than_equal':\n return compareLoose(actual, expected) >= 0\n case 'less_than':\n return compareLoose(actual, expected) < 0\n case 'less_than_equal':\n return compareLoose(actual, expected) <= 0\n case 'exists':\n return expected ? actual !== undefined && actual !== null : actual === undefined || actual === null\n case 'in':\n return Array.isArray(expected) && expected.includes(actual)\n case 'not_in':\n return Array.isArray(expected) && !expected.includes(actual)\n case 'like': {\n // Empty needle means \"no constraint\" — mirrors SQL `LIKE '%%'` and what\n // Payload's admin search produces when the search input is cleared.\n if (typeof expected !== 'string' || expected === '') return true\n if (typeof actual !== 'string') return false\n return actual.toLowerCase().includes(expected.toLowerCase())\n }\n default:\n return false\n }\n}\n\n/**\n * Loose ordered comparison matching DynamoDB's `<`/`>` semantics: numbers and\n * strings (including ISO date strings) compare naturally; nullish on either\n * side yields a non-match by returning `NaN`. Lets `greater_than`/`less_than`\n * mirror the server-side filter evaluator behavior.\n */\nfunction compareLoose(a: unknown, b: unknown): number {\n if (a === undefined || a === null || b === undefined || b === null) return NaN\n if (typeof a === 'number' && typeof b === 'number') return a - b\n const as = String(a)\n const bs = String(b)\n if (as < bs) return -1\n if (as > bs) return 1\n return 0\n}\n"],"names":["getByPath","SUPPORTED_OPERATORS","unsupportedOperatorError","matchesWhere","item","where","key","raw","Object","entries","Array","isArray","sub","any","fieldValue","operators","operator","expected","has","evaluate","actual","compareLoose","undefined","includes","toLowerCase","a","b","NaN","as","String","bs"],"mappings":"AAEA,SAASA,SAAS,QAAQ,iBAAgB;AAC1C,SAASC,mBAAmB,EAAEC,wBAAwB,QAAQ,iBAAgB;AAE9E;;;;;;;;CAQC,GACD,OAAO,SAASC,aAAaC,IAA6B,EAAEC,KAAwB;IAClF,IAAI,CAACA,OAAO;QACV,OAAO;IACT;IAEA,KAAK,MAAM,CAACC,KAAKC,IAAI,IAAIC,OAAOC,OAAO,CAACJ,OAAQ;QAC9C,IAAIC,QAAQ,OAAO;YACjB,IAAI,CAACI,MAAMC,OAAO,CAACJ,MAAM;YACzB,KAAK,MAAMK,OAAOL,IAAK;gBACrB,IAAI,CAACJ,aAAaC,MAAMQ,MAAM,OAAO;YACvC;YACA;QACF;QAEA,IAAIN,QAAQ,MAAM;YAChB,IAAI,CAACI,MAAMC,OAAO,CAACJ,MAAM;YACzB,IAAIM,MAAM;YACV,KAAK,MAAMD,OAAOL,IAAK;gBACrB,IAAIJ,aAAaC,MAAMQ,MAAM;oBAC3BC,MAAM;oBACN;gBACF;YACF;YACA,IAAI,CAACA,KAAK,OAAO;YACjB;QACF;QAEA,IAAI,CAACN,OAAO,OAAOA,QAAQ,UAAU;YACnC;QACF;QAEA,MAAMO,aAAad,UAAUI,MAAME;QACnC,MAAMS,YAAYR;QAElB,KAAK,MAAM,CAACS,UAAUC,SAAS,IAAIT,OAAOC,OAAO,CAACM,WAAY;YAC5D,IAAI,CAACd,oBAAoBiB,GAAG,CAACF,WAAW;gBACtC,MAAMd,yBAAyBc,UAAUV;YAC3C;YAEA,IAAI,CAACa,SAASL,YAAYE,UAAUC,WAAW;gBAC7C,OAAO;YACT;QACF;IACF;IAEA,OAAO;AACT;AAEA,SAASE,SAASC,MAAe,EAAEJ,QAAgB,EAAEC,QAAiB;IACpE,OAAQD;QACN,KAAK;YACH,OAAOI,WAAWH;QACpB,KAAK;YACH,OAAOG,WAAWH;QACpB,KAAK;YACH,OAAOI,aAAaD,QAAQH,YAAY;QAC1C,KAAK;YACH,OAAOI,aAAaD,QAAQH,aAAa;QAC3C,KAAK;YACH,OAAOI,aAAaD,QAAQH,YAAY;QAC1C,KAAK;YACH,OAAOI,aAAaD,QAAQH,aAAa;QAC3C,KAAK;YACH,OAAOA,WAAWG,WAAWE,aAAaF,WAAW,OAAOA,WAAWE,aAAaF,WAAW;QACjG,KAAK;YACH,OAAOV,MAAMC,OAAO,CAACM,aAAaA,SAASM,QAAQ,CAACH;QACtD,KAAK;YACH,OAAOV,MAAMC,OAAO,CAACM,aAAa,CAACA,SAASM,QAAQ,CAACH;QACvD,KAAK;YAAQ;gBACX,wEAAwE;gBACxE,oEAAoE;gBACpE,IAAI,OAAOH,aAAa,YAAYA,aAAa,IAAI,OAAO;gBAC5D,IAAI,OAAOG,WAAW,UAAU,OAAO;gBACvC,OAAOA,OAAOI,WAAW,GAAGD,QAAQ,CAACN,SAASO,WAAW;YAC3D;QACA;YACE,OAAO;IACX;AACF;AAEA;;;;;CAKC,GACD,SAASH,aAAaI,CAAU,EAAEC,CAAU;IAC1C,IAAID,MAAMH,aAAaG,MAAM,QAAQC,MAAMJ,aAAaI,MAAM,MAAM,OAAOC;IAC3E,IAAI,OAAOF,MAAM,YAAY,OAAOC,MAAM,UAAU,OAAOD,IAAIC;IAC/D,MAAME,KAAKC,OAAOJ;IAClB,MAAMK,KAAKD,OAAOH;IAClB,IAAIE,KAAKE,IAAI,OAAO,CAAC;IACrB,IAAIF,KAAKE,IAAI,OAAO;IACpB,OAAO;AACT"}
1
+ {"version":3,"sources":["../../src/utilities/matchesWhere.ts"],"sourcesContent":["import type { Where } from 'payload'\n\nimport { matchesIntersects, matchesNear, matchesWithin } from './geo.js'\nimport { getByPath } from './getByPath.js'\nimport { normalizeForDynamo } from './normalizeForDynamo.js'\nimport { JS_ONLY_MARKER, SUPPORTED_OPERATORS, unsupportedOperatorError } from './operators.js'\n\n/**\n * In-memory predicate that evaluates a Payload `Where` against a fetched item.\n * This is the adapter's reference evaluator: `buildFilterExpression` pushes\n * down whatever part of a `where` DynamoDB can express, and every row that\n * comes back from an inexact pushdown is re-checked here. It also runs alone\n * for rows that only take their doc shape after projection (drafts\n * supplements, `queryDrafts`).\n *\n * Where a clause can be evaluated by both, the two must return the same rows\n * — `test/parity` pins that.\n *\n * Paths follow MongoDB's array semantics, which is what Payload's query\n * language assumes: an array anywhere along a path is transparent, so\n * `tags equals 'x'` matches a `hasMany` value containing `'x'` and\n * `items.label equals 'x'` matches when any item has that label. A negative\n * operator (`not_equals`, `not_in`, `not_like`) holds only when *no* element\n * matches its positive form.\n *\n * Throws on any unsupported operator so we surface coverage gaps instead of\n * silently returning wrong results.\n */\nexport function matchesWhere(item: Record<string, unknown>, where: undefined | Where): boolean {\n if (!where) {\n return true\n }\n\n for (const [key, raw] of Object.entries(where)) {\n if (key === 'and') {\n if (!Array.isArray(raw)) continue\n for (const sub of raw) {\n if (!matchesWhere(item, sub)) return false\n }\n continue\n }\n\n if (key === 'or') {\n if (!Array.isArray(raw)) continue\n let any = false\n for (const sub of raw) {\n if (matchesWhere(item, sub)) {\n any = true\n break\n }\n }\n if (!any) return false\n continue\n }\n\n if (!raw || typeof raw !== 'object') {\n continue\n }\n\n const operators = raw as Record<string, unknown>\n let candidates: undefined | unknown[]\n\n for (const [operator, expected] of Object.entries(operators)) {\n if (operator === JS_ONLY_MARKER) continue\n if (!SUPPORTED_OPERATORS.has(operator)) {\n throw unsupportedOperatorError(operator, key)\n }\n\n // Geometry reads the stored point whole — `[lng, lat]` is a value, not\n // a list of candidates.\n if (operator === 'near' || operator === 'within' || operator === 'intersects') {\n if (!evaluateGeo(getByPath(item, key), operator, expected)) return false\n continue\n }\n\n candidates ??= candidatesAt(item, key)\n if (!evaluate(candidates, operator, expected)) {\n return false\n }\n }\n }\n\n return true\n}\n\n/**\n * Every value a dotted path can reach in `source`, treating arrays as\n * transparent. A path that ends on an array yields the array itself (so an\n * array operand can match it whole, and `exists` sees an empty list as a\n * value) followed by each of its elements. A path that reaches nothing\n * yields no candidates.\n */\nexport function candidatesAt(source: unknown, path: string): unknown[] {\n const out: unknown[] = []\n collect(source, path.split('.'), 0, out)\n return out\n}\n\nfunction collect(value: unknown, segments: string[], index: number, out: unknown[]): void {\n if (index === segments.length) {\n if (value === undefined) return\n out.push(value)\n if (Array.isArray(value)) {\n for (const member of value) {\n if (member !== undefined) out.push(member)\n }\n }\n return\n }\n if (Array.isArray(value)) {\n for (const member of value) collect(member, segments, index, out)\n return\n }\n if (value === null || typeof value !== 'object') return\n collect((value as Record<string, unknown>)[segments[index]!], segments, index + 1, out)\n}\n\nfunction evaluateGeo(value: unknown, operator: string, expected: unknown): boolean {\n switch (operator) {\n case 'intersects':\n return matchesIntersects(value, expected)\n case 'near':\n return matchesNear(value, expected)\n default:\n return matchesWithin(value, expected)\n }\n}\n\nfunction evaluate(candidates: unknown[], operator: string, rawExpected: unknown): boolean {\n // Coerce the operand exactly as `buildFilterExpression` coerces its\n // expression values (today: `Date` → ISO string, including inside `in` /\n // `not_in` lists). Rows hold ISO strings, so comparing them against a raw\n // `Date` would never be equal and would order by `Date#toString()` text.\n const expected = normalizeForDynamo(rawExpected)\n const some = (test: (candidate: unknown) => boolean) => candidates.some(test)\n const hasValue = (candidate: unknown) => candidate !== undefined && candidate !== null\n\n switch (operator) {\n case 'all':\n return (\n Array.isArray(expected) &&\n expected.every((member) => some((candidate) => valuesEqual(candidate, member)))\n )\n case 'contains':\n return asList(expected).some((needle) => some((candidate) => containsText(candidate, needle)))\n case 'equals':\n return some((candidate) => valuesEqual(candidate, expected))\n case 'exists':\n return expected ? some(hasValue) : !some(hasValue)\n case 'greater_than':\n return some((candidate) => compareLoose(candidate, expected) > 0)\n case 'greater_than_equal':\n return some((candidate) => compareLoose(candidate, expected) >= 0)\n case 'in':\n return (\n Array.isArray(expected) &&\n expected.some((member) => some((candidate) => valuesEqual(candidate, member)))\n )\n case 'less_than':\n return some((candidate) => compareLoose(candidate, expected) < 0)\n case 'less_than_equal':\n return some((candidate) => compareLoose(candidate, expected) <= 0)\n case 'like':\n return isLike(candidates, expected)\n case 'not_equals':\n return !some((candidate) => valuesEqual(candidate, expected))\n case 'not_in':\n return (\n Array.isArray(expected) &&\n !expected.some((member) => some((candidate) => valuesEqual(candidate, member)))\n )\n case 'not_like':\n return !isLike(candidates, expected)\n default:\n return false\n }\n}\n\nconst asList = (value: unknown): unknown[] => (Array.isArray(value) ? value : [value])\n\nfunction isLike(candidates: unknown[], expected: unknown): boolean {\n // Empty needle means \"no constraint\" — mirrors SQL `LIKE '%%'` and what\n // Payload's admin search produces when the search input is cleared.\n if (typeof expected !== 'string' || expected === '') return true\n const needle = expected.toLowerCase()\n return candidates.some(\n (candidate) => typeof candidate === 'string' && candidate.toLowerCase().includes(needle),\n )\n}\n\n/**\n * `contains`: case-insensitive substring, over text and numbers (a `hasMany`\n * number field is searched by its digits, as the Mongo adapter does).\n */\nfunction containsText(candidate: unknown, needle: unknown): boolean {\n if (typeof needle !== 'string' && typeof needle !== 'number') return false\n if (typeof candidate !== 'string' && typeof candidate !== 'number') return false\n return String(candidate).toLowerCase().includes(String(needle).toLowerCase())\n}\n\n/**\n * Equality as DynamoDB's `=` defines it: scalars by value and type, lists\n * element-by-element in order, maps key-by-key regardless of key order. A\n * plain `===` would compare objects by reference and never match — which\n * breaks object operands such as a polymorphic relationship's\n * `{ relationTo, value }`.\n *\n * Keys holding `undefined` are ignored on both sides, since the marshaller\n * drops them before anything reaches DynamoDB (`removeUndefinedValues`).\n */\nfunction valuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false\n\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false\n return a.every((member, index) => valuesEqual(member, b[index]))\n }\n\n const left = a as Record<string, unknown>\n const right = b as Record<string, unknown>\n const definedKeys = (record: Record<string, unknown>) =>\n Object.keys(record).filter((key) => record[key] !== undefined)\n const leftKeys = definedKeys(left)\n if (leftKeys.length !== definedKeys(right).length) return false\n return leftKeys.every((key) => right[key] !== undefined && valuesEqual(left[key], right[key]))\n}\n\n/**\n * Ordered comparison matching DynamoDB's `<`/`>` semantics: two numbers or\n * two strings (including ISO date strings) compare naturally; anything else\n * — a missing side, or operands of different types — yields a non-match by\n * returning `NaN`. DynamoDB never orders a number against a string, so\n * falling back to string comparison here would match rows the pushdown\n * rejects. `castWhere` is what turns a string operand into the field's real\n * type before it gets this far.\n */\nfunction compareLoose(a: unknown, b: unknown): number {\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n }\n return NaN\n}\n"],"names":["matchesIntersects","matchesNear","matchesWithin","getByPath","normalizeForDynamo","JS_ONLY_MARKER","SUPPORTED_OPERATORS","unsupportedOperatorError","matchesWhere","item","where","key","raw","Object","entries","Array","isArray","sub","any","operators","candidates","operator","expected","has","evaluateGeo","candidatesAt","evaluate","source","path","out","collect","split","value","segments","index","length","undefined","push","member","rawExpected","some","test","hasValue","candidate","every","valuesEqual","asList","needle","containsText","compareLoose","isLike","toLowerCase","includes","String","a","b","left","right","definedKeys","record","keys","filter","leftKeys","NaN"],"mappings":"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"}
@@ -1,26 +1,36 @@
1
- import type { Where } from 'payload';
2
1
  /**
3
2
  * The where-operator coverage shared by `matchesWhere` (in-JS evaluator) and
4
3
  * `buildFilterExpression` (DynamoDB pushdown). Keeping the set in one place
5
4
  * keeps the two paths honest — if an operator is supported in one, it should
6
5
  * be supported in the other, since callers can't know which path runs.
6
+ *
7
+ * This is Payload's full operator list (`validOperators` in core).
7
8
  */
8
9
  export declare const SUPPORTED_OPERATORS: Set<string>;
9
10
  /**
10
11
  * Operators we can't faithfully express in a DynamoDB `FilterExpression` and
11
- * therefore evaluate in JS after fetching candidate rows. `like` is here
12
- * because Payload's contract is case-insensitive substring matching while
13
- * DynamoDB's `contains()` is case-sensitive — pushing it down would silently
14
- * change semantics. When `where` contains any of these, the read helpers
15
- * `Query` the partition without server-side filtering and apply
16
- * `matchesWhere` in memory.
12
+ * therefore evaluate in JS after fetching candidate rows:
13
+ *
14
+ * - `like` / `not_like` / `contains`: Payload's contract is case-insensitive
15
+ * matching, while DynamoDB's `contains()` is case-sensitive — pushing it
16
+ * down would silently change semantics.
17
+ * - `all`: needs "every operand is a member" over a list; no equivalent.
18
+ * - `near` / `within` / `intersects`: geometry.
19
+ *
20
+ * A clause using one of these is simply left out of the pushed-down filter
21
+ * (see `buildFilterExpression`); the rest of the `where` still narrows the
22
+ * read server-side, and `matchesWhere` re-checks every returned row.
17
23
  */
18
24
  export declare const JS_ONLY_OPERATORS: Set<string>;
19
- export declare const SUPPORTED_OPERATORS_DESCRIPTION: string;
20
- export declare function unsupportedOperatorError(operator: string, field: string): Error;
21
25
  /**
22
- * Walk a `Where` and return true if any leaf clause uses an operator that
23
- * forces JS-side evaluation. Recurses into `and`/`or` groups.
26
+ * Pseudo-operator `castWhere` adds to a clause whose *path* can't be pushed
27
+ * down, whatever its operators: one that runs through an array or a
28
+ * `hasMany` value (DynamoDB document paths can't mean "any element"), or
29
+ * into a field whose inner shape the schema doesn't describe. The pushdown
30
+ * skips the whole clause; `matchesWhere` ignores the marker and evaluates the
31
+ * real operators with any-element semantics.
24
32
  */
25
- export declare function whereHasJsOnlyOperator(where: undefined | Where): boolean;
33
+ export declare const JS_ONLY_MARKER = "$js";
34
+ export declare const SUPPORTED_OPERATORS_DESCRIPTION: string;
35
+ export declare function unsupportedOperatorError(operator: string, field: string): Error;
26
36
  //# sourceMappingURL=operators.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"operators.d.ts","sourceRoot":"","sources":["../../src/utilities/operators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,aAW9B,CAAA;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,aAAoB,CAAA;AAElD,eAAO,MAAM,+BAA+B,QAAoD,CAAA;AAEhG,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAK/E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,GAAG,OAAO,CAkBxE"}
1
+ {"version":3,"file":"operators.d.ts","sourceRoot":"","sources":["../../src/utilities/operators.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,aAiB9B,CAAA;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,aAQ5B,CAAA;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAAQ,CAAA;AAEnC,eAAO,MAAM,+BAA+B,QAAoD,CAAA;AAEhG,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAK/E"}
@@ -3,56 +3,61 @@
3
3
  * `buildFilterExpression` (DynamoDB pushdown). Keeping the set in one place
4
4
  * keeps the two paths honest — if an operator is supported in one, it should
5
5
  * be supported in the other, since callers can't know which path runs.
6
+ *
7
+ * This is Payload's full operator list (`validOperators` in core).
6
8
  */ export const SUPPORTED_OPERATORS = new Set([
9
+ 'all',
10
+ 'contains',
7
11
  'equals',
8
12
  'exists',
9
13
  'greater_than',
10
14
  'greater_than_equal',
11
15
  'in',
16
+ 'intersects',
12
17
  'less_than',
13
18
  'less_than_equal',
14
19
  'like',
20
+ 'near',
15
21
  'not_equals',
16
- 'not_in'
22
+ 'not_in',
23
+ 'not_like',
24
+ 'within'
17
25
  ]);
18
26
  /**
19
27
  * 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.
28
+ * therefore evaluate in JS after fetching candidate rows:
29
+ *
30
+ * - `like` / `not_like` / `contains`: Payload's contract is case-insensitive
31
+ * matching, while DynamoDB's `contains()` is case-sensitive — pushing it
32
+ * down would silently change semantics.
33
+ * - `all`: needs "every operand is a member" over a list; no equivalent.
34
+ * - `near` / `within` / `intersects`: geometry.
35
+ *
36
+ * A clause using one of these is simply left out of the pushed-down filter
37
+ * (see `buildFilterExpression`); the rest of the `where` still narrows the
38
+ * read server-side, and `matchesWhere` re-checks every returned row.
26
39
  */ export const JS_ONLY_OPERATORS = new Set([
27
- 'like'
40
+ 'all',
41
+ 'contains',
42
+ 'intersects',
43
+ 'like',
44
+ 'near',
45
+ 'not_like',
46
+ 'within'
28
47
  ]);
48
+ /**
49
+ * Pseudo-operator `castWhere` adds to a clause whose *path* can't be pushed
50
+ * down, whatever its operators: one that runs through an array or a
51
+ * `hasMany` value (DynamoDB document paths can't mean "any element"), or
52
+ * into a field whose inner shape the schema doesn't describe. The pushdown
53
+ * skips the whole clause; `matchesWhere` ignores the marker and evaluates the
54
+ * real operators with any-element semantics.
55
+ */ export const JS_ONLY_MARKER = '$js';
29
56
  export const SUPPORTED_OPERATORS_DESCRIPTION = `${[
30
57
  ...SUPPORTED_OPERATORS
31
58
  ].join(', ')}, and, or`;
32
59
  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;
60
+ return new Error(`payload-ddb: operator \`${operator}\` is not supported on field \`${field}\`. ` + `Supported: ${SUPPORTED_OPERATORS_DESCRIPTION}.`);
56
61
  }
57
62
 
58
63
  //# sourceMappingURL=operators.js.map
@@ -1 +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"}
1
+ {"version":3,"sources":["../../src/utilities/operators.ts"],"sourcesContent":["/**\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 *\n * This is Payload's full operator list (`validOperators` in core).\n */\nexport const SUPPORTED_OPERATORS = new Set([\n 'all',\n 'contains',\n 'equals',\n 'exists',\n 'greater_than',\n 'greater_than_equal',\n 'in',\n 'intersects',\n 'less_than',\n 'less_than_equal',\n 'like',\n 'near',\n 'not_equals',\n 'not_in',\n 'not_like',\n 'within',\n])\n\n/**\n * Operators we can't faithfully express in a DynamoDB `FilterExpression` and\n * therefore evaluate in JS after fetching candidate rows:\n *\n * - `like` / `not_like` / `contains`: Payload's contract is case-insensitive\n * matching, while DynamoDB's `contains()` is case-sensitive — pushing it\n * down would silently change semantics.\n * - `all`: needs \"every operand is a member\" over a list; no equivalent.\n * - `near` / `within` / `intersects`: geometry.\n *\n * A clause using one of these is simply left out of the pushed-down filter\n * (see `buildFilterExpression`); the rest of the `where` still narrows the\n * read server-side, and `matchesWhere` re-checks every returned row.\n */\nexport const JS_ONLY_OPERATORS = new Set([\n 'all',\n 'contains',\n 'intersects',\n 'like',\n 'near',\n 'not_like',\n 'within',\n])\n\n/**\n * Pseudo-operator `castWhere` adds to a clause whose *path* can't be pushed\n * down, whatever its operators: one that runs through an array or a\n * `hasMany` value (DynamoDB document paths can't mean \"any element\"), or\n * into a field whose inner shape the schema doesn't describe. The pushdown\n * skips the whole clause; `matchesWhere` ignores the marker and evaluates the\n * real operators with any-element semantics.\n */\nexport const JS_ONLY_MARKER = '$js'\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 on field \\`${field}\\`. ` +\n `Supported: ${SUPPORTED_OPERATORS_DESCRIPTION}.`,\n )\n}\n"],"names":["SUPPORTED_OPERATORS","Set","JS_ONLY_OPERATORS","JS_ONLY_MARKER","SUPPORTED_OPERATORS_DESCRIPTION","join","unsupportedOperatorError","operator","field","Error"],"mappings":"AAAA;;;;;;;CAOC,GACD,OAAO,MAAMA,sBAAsB,IAAIC,IAAI;IACzC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,EAAC;AAEF;;;;;;;;;;;;;CAaC,GACD,OAAO,MAAMC,oBAAoB,IAAID,IAAI;IACvC;IACA;IACA;IACA;IACA;IACA;IACA;CACD,EAAC;AAEF;;;;;;;CAOC,GACD,OAAO,MAAME,iBAAiB,MAAK;AAEnC,OAAO,MAAMC,kCAAkC,GAAG;OAAIJ;CAAoB,CAACK,IAAI,CAAC,MAAM,SAAS,CAAC,CAAA;AAEhG,OAAO,SAASC,yBAAyBC,QAAgB,EAAEC,KAAa;IACtE,OAAO,IAAIC,MACT,CAAC,wBAAwB,EAAEF,SAAS,+BAA+B,EAAEC,MAAM,IAAI,CAAC,GAC9E,CAAC,WAAW,EAAEJ,gCAAgC,CAAC,CAAC;AAEtD"}
@@ -0,0 +1,29 @@
1
+ import { APIError } from 'payload';
2
+ import type { DynamoAdapter } from '../types.js';
3
+ type Row = Record<string, unknown>;
4
+ /**
5
+ * Read one row by id with a strongly consistent `GetItem`. The returned
6
+ * object carries its revision (see `stripInternalKeys`), so it can be handed
7
+ * to `putItem` as `replaces`.
8
+ */
9
+ export declare function getById(adapter: DynamoAdapter, partition: string, id: unknown): Promise<null | Row>;
10
+ /** Enough to ride out a burst of writers on one row; beyond it something is wrong. */
11
+ export declare const MAX_WRITE_ATTEMPTS = 10;
12
+ /**
13
+ * Run a read-merge-write until it lands.
14
+ *
15
+ * `write` merges onto the row it's given and puts the result with
16
+ * `replaces: current`. If another writer got in first, the put's revision
17
+ * check fails; `reread` then fetches the row as it is now and the merge is
18
+ * redone on top of it — so two concurrent patches to different fields both
19
+ * survive, instead of the second silently overwriting the first.
20
+ *
21
+ * `reread` returning `null` means the row is gone or no longer qualifies;
22
+ * the result is then `null` and nothing is written.
23
+ */
24
+ export declare function writeWithRetry<T>(first: null | Row, reread: () => Promise<null | Row>, write: (current: Row) => Promise<T>): Promise<null | T>;
25
+ export declare function conflictError(): APIError;
26
+ /** A short random pause so writers that collided don't collide again in step. */
27
+ export declare function backoff(attempt: number): Promise<void>;
28
+ export {};
29
+ //# sourceMappingURL=optimistic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimistic.d.ts","sourceRoot":"","sources":["../../src/utilities/optimistic.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAElC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKhD,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAElC;;;;GAIG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,IAAI,GAAG,GAAG,CAAC,CAerB;AAED,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAEpC;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,KAAK,EAAE,IAAI,GAAG,GAAG,EACjB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,GAAG,CAAC,EACjC,KAAK,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC,CAenB;AAED,wBAAgB,aAAa,IAAI,QAAQ,CAOxC;AAED,iFAAiF;AACjF,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtD"}