@ftschopp/dynatable-core 1.4.2 → 1.4.4

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 (86) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/builders/query/create-query-builder.d.ts.map +1 -1
  3. package/dist/builders/query/create-query-builder.js +19 -2
  4. package/dist/builders/update/create-update-builder.d.ts.map +1 -1
  5. package/dist/builders/update/create-update-builder.js +25 -0
  6. package/dist/core/types.d.ts +5 -0
  7. package/dist/core/types.d.ts.map +1 -1
  8. package/dist/utils/model-utils.d.ts +9 -3
  9. package/dist/utils/model-utils.d.ts.map +1 -1
  10. package/dist/utils/model-utils.js +15 -7
  11. package/package.json +19 -2
  12. package/eslint.config.mjs +0 -4
  13. package/jest.config.js +0 -11
  14. package/src/builders/README.md +0 -339
  15. package/src/builders/batch-get/README.md +0 -43
  16. package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
  17. package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
  18. package/src/builders/batch-get/index.ts +0 -2
  19. package/src/builders/batch-get/types.ts +0 -28
  20. package/src/builders/batch-write/README.md +0 -204
  21. package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
  22. package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
  23. package/src/builders/batch-write/index.ts +0 -2
  24. package/src/builders/batch-write/types.ts +0 -33
  25. package/src/builders/delete/create-delete-builder.test.ts +0 -294
  26. package/src/builders/delete/create-delete-builder.ts +0 -100
  27. package/src/builders/delete/index.ts +0 -2
  28. package/src/builders/delete/types.ts +0 -17
  29. package/src/builders/get/create-get-builder.test.ts +0 -272
  30. package/src/builders/get/create-get-builder.ts +0 -117
  31. package/src/builders/get/index.ts +0 -2
  32. package/src/builders/get/types.ts +0 -39
  33. package/src/builders/index.ts +0 -14
  34. package/src/builders/put/create-put-builder.test.ts +0 -213
  35. package/src/builders/put/create-put-builder.ts +0 -160
  36. package/src/builders/put/index.ts +0 -2
  37. package/src/builders/put/types.ts +0 -24
  38. package/src/builders/query/create-query-builder.test.ts +0 -770
  39. package/src/builders/query/create-query-builder.ts +0 -455
  40. package/src/builders/query/index.ts +0 -2
  41. package/src/builders/query/types.ts +0 -109
  42. package/src/builders/scan/create-scan-builder.test.ts +0 -416
  43. package/src/builders/scan/create-scan-builder.ts +0 -265
  44. package/src/builders/scan/index.ts +0 -2
  45. package/src/builders/scan/types.ts +0 -88
  46. package/src/builders/shared/conditions.ts +0 -58
  47. package/src/builders/shared/index.ts +0 -4
  48. package/src/builders/shared/operators.test.ts +0 -270
  49. package/src/builders/shared/operators.ts +0 -208
  50. package/src/builders/shared/projection.ts +0 -28
  51. package/src/builders/shared/types.ts +0 -101
  52. package/src/builders/transact-get/README.md +0 -167
  53. package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
  54. package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
  55. package/src/builders/transact-get/index.ts +0 -2
  56. package/src/builders/transact-get/types.ts +0 -31
  57. package/src/builders/transact-write/README.md +0 -202
  58. package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
  59. package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
  60. package/src/builders/transact-write/index.ts +0 -2
  61. package/src/builders/transact-write/types.ts +0 -71
  62. package/src/builders/update/create-update-builder.test.ts +0 -573
  63. package/src/builders/update/create-update-builder.ts +0 -375
  64. package/src/builders/update/index.ts +0 -2
  65. package/src/builders/update/types.ts +0 -63
  66. package/src/core/types.test.ts +0 -641
  67. package/src/core/types.ts +0 -366
  68. package/src/entity/create-entity-api.ts +0 -224
  69. package/src/entity/index.ts +0 -19
  70. package/src/entity/middleware/factories.ts +0 -15
  71. package/src/entity/middleware/types.ts +0 -7
  72. package/src/entity/middleware/with-middleware.ts +0 -37
  73. package/src/entity/types.ts +0 -79
  74. package/src/entity/validation/key-validation.ts +0 -34
  75. package/src/index.ts +0 -23
  76. package/src/table.ts +0 -116
  77. package/src/utils/dynamodb-logger.test.ts +0 -246
  78. package/src/utils/dynamodb-logger.ts +0 -175
  79. package/src/utils/model-utils.test.ts +0 -452
  80. package/src/utils/model-utils.ts +0 -188
  81. package/src/utils/zod-utils.test.ts +0 -363
  82. package/src/utils/zod-utils.ts +0 -56
  83. package/tests/integration/instagram-clone.integration.test.ts +0 -1019
  84. package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
  85. package/tests/integration/transactions.integration.test.ts +0 -528
  86. package/tsconfig.json +0 -12
@@ -1,88 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { ExecutableBuilder, AttrBuilder, OpBuilder, Condition } from '../shared';
3
-
4
- /**
5
- * A single page of scan results plus metadata for manual pagination.
6
- */
7
- export interface ScanResult<Model> {
8
- /** Items returned in this page. */
9
- items: Model[];
10
- /** Cursor for the next page; `undefined` when there are no more results. */
11
- lastEvaluatedKey?: Record<string, any>;
12
- /** Number of items returned after applying the filter. */
13
- count?: number;
14
- /** Number of items examined before applying the filter. */
15
- scannedCount?: number;
16
- }
17
-
18
- /**
19
- * Builder for DynamoDB Scan operations.
20
- * Scans the entire table or index without requiring key conditions.
21
- */
22
- export interface ScanBuilder<Model> extends ExecutableBuilder<Model[]> {
23
- /**
24
- * Adds a filter expression to the scan.
25
- * Returns a new immutable builder.
26
- */
27
- filter(fn: (attr: AttrBuilder<Model>, op: OpBuilder) => Condition): ScanBuilder<Model>;
28
-
29
- /**
30
- * Adds a projection expression to only return specific attributes.
31
- * Returns a new immutable builder.
32
- */
33
- select(attrs: (keyof Model)[]): ScanBuilder<Model>;
34
-
35
- /**
36
- * Sets the maximum number of items to evaluate (not necessarily return).
37
- * Returns a new immutable builder.
38
- */
39
- limit(count: number): ScanBuilder<Model>;
40
-
41
- /**
42
- * Enables consistent read (eventual consistency is default).
43
- * Returns a new immutable builder.
44
- */
45
- consistentRead(): ScanBuilder<Model>;
46
-
47
- /**
48
- * Specifies an index to scan.
49
- * Returns a new immutable builder.
50
- */
51
- usingIndex(indexName: string): ScanBuilder<Model>;
52
-
53
- /**
54
- * Sets the starting position for the scan (pagination).
55
- * Returns a new immutable builder.
56
- */
57
- startFrom(exclusiveStartKey: Record<string, any>): ScanBuilder<Model>;
58
-
59
- /**
60
- * Enables parallel scan by specifying segment and total segments.
61
- * Returns a new immutable builder.
62
- */
63
- segment(segmentNumber: number, totalSegments: number): ScanBuilder<Model>;
64
-
65
- /**
66
- * Executes a single Scan request and returns the page along with the
67
- * `lastEvaluatedKey` cursor and counts. Use this when you want to drive
68
- * pagination yourself (e.g. expose a cursor to a client).
69
- */
70
- executeWithPagination(): Promise<ScanResult<Model>>;
71
-
72
- /**
73
- * Returns an async iterator that paginates internally and yields one item at
74
- * a time. Memory stays at one page; you can `break` out of the loop early.
75
- *
76
- * `Limit` set via `.limit()` is forwarded to DynamoDB as a *per-request*
77
- * cap, not a total cap. To cap the total, count yourself inside the loop:
78
- *
79
- * ```ts
80
- * let n = 0;
81
- * for await (const item of builder.iterate()) {
82
- * if (n++ >= 1000) break;
83
- * process(item);
84
- * }
85
- * ```
86
- */
87
- iterate(): AsyncIterableIterator<Model>;
88
- }
@@ -1,58 +0,0 @@
1
- import { Condition } from './types';
2
-
3
- /**
4
- * Builds a DynamoDB expression from a condition tree
5
- */
6
- export function buildExpression(condition: Condition): {
7
- expression: string;
8
- names: Record<string, string>;
9
- values: Record<string, any>;
10
- } {
11
- const names: Record<string, string> = {};
12
- const values: Record<string, any> = {};
13
-
14
- function processCondition(cond: Condition): string {
15
- // If it's a combinator (AND/OR)
16
- if (cond.operator && cond.children) {
17
- const childExpressions = cond.children
18
- .map((child) => processCondition(child))
19
- .filter((expr) => expr.length > 0);
20
-
21
- if (childExpressions.length === 0) return '';
22
- if (childExpressions.length === 1) return childExpressions[0]!;
23
-
24
- const combined = childExpressions.map((expr) => `(${expr})`).join(` ${cond.operator} `);
25
-
26
- return cond.isNegated ? `NOT (${combined})` : combined;
27
- }
28
-
29
- // It's a leaf condition
30
- if (cond.names) {
31
- Object.assign(names, cond.names);
32
- }
33
- if (cond.values) {
34
- Object.assign(values, cond.values);
35
- }
36
-
37
- return cond.isNegated ? `NOT (${cond.expression})` : cond.expression;
38
- }
39
-
40
- const expression = processCondition(condition);
41
-
42
- return { expression, names, values };
43
- }
44
-
45
- /**
46
- * Combines multiple conditions into a DynamoDB-compatible expression object.
47
- */
48
- export function buildConditionExpr(conditions: Condition[]) {
49
- const expression = conditions.map((c) => c.expression).join(' AND ');
50
- const names = Object.assign({}, ...conditions.map((c) => c.names ?? {}));
51
- const values = Object.assign({}, ...conditions.map((c) => c.values ?? {}));
52
-
53
- return {
54
- ConditionExpression: expression || undefined,
55
- ExpressionAttributeNames: Object.keys(names).length ? names : undefined,
56
- ExpressionAttributeValues: Object.keys(values).length ? values : undefined,
57
- };
58
- }
@@ -1,4 +0,0 @@
1
- export * from './types';
2
- export * from './operators';
3
- export * from './conditions';
4
- export * from './projection';
@@ -1,270 +0,0 @@
1
- import { createOpBuilder } from './operators';
2
-
3
- describe('OpBuilder Counter Isolation', () => {
4
- it('should have isolated counters for each opBuilder instance', () => {
5
- const op1 = createOpBuilder();
6
- const op2 = createOpBuilder();
7
-
8
- const attr = { name: 'username' };
9
-
10
- // Create conditions with both builders
11
- const cond1a = op1.eq(attr, 'alice');
12
- const cond1b = op1.eq(attr, 'bob');
13
-
14
- const cond2a = op2.eq(attr, 'charlie');
15
- const cond2b = op2.eq(attr, 'dave');
16
-
17
- // Each builder should have its own counter starting at 0
18
- expect(cond1a.values).toEqual({ ':username_0': 'alice' });
19
- expect(cond1b.values).toEqual({ ':username_1': 'bob' });
20
-
21
- // Second builder should also start at 0
22
- expect(cond2a.values).toEqual({ ':username_0': 'charlie' });
23
- expect(cond2b.values).toEqual({ ':username_1': 'dave' });
24
- });
25
-
26
- it('should not share state between different builder instances', () => {
27
- const op1 = createOpBuilder();
28
- const op2 = createOpBuilder();
29
-
30
- const attr = { name: 'age' };
31
-
32
- // Use first builder
33
- op1.gt(attr, 18);
34
- op1.lt(attr, 65);
35
-
36
- // Second builder should start from 0, not continue from first
37
- const cond = op2.eq(attr, 25);
38
- expect(cond.values).toEqual({ ':age_0': 25 });
39
- });
40
-
41
- it('should generate unique names for multiple values in same condition', () => {
42
- const op = createOpBuilder();
43
- const attr = { name: 'score' };
44
-
45
- const between = op.between(attr, 10, 20);
46
-
47
- expect(between.values).toEqual({
48
- ':score_low_0': 10,
49
- ':score_high_1': 20,
50
- });
51
- });
52
- });
53
-
54
- describe('New Operators', () => {
55
- describe('contains operator', () => {
56
- it('should build contains expression', () => {
57
- const op = createOpBuilder();
58
- const attr = { name: 'tags' };
59
-
60
- const condition = op.contains(attr, 'featured');
61
-
62
- expect(condition.expression).toBe('contains(#tags, :tags_0)');
63
- expect(condition.names).toEqual({ '#tags': 'tags' });
64
- expect(condition.values).toEqual({ ':tags_0': 'featured' });
65
- });
66
- });
67
-
68
- describe('exists operator', () => {
69
- it('should build attribute_exists expression', () => {
70
- const op = createOpBuilder();
71
- const attr = { name: 'email' };
72
-
73
- const condition = op.exists(attr);
74
-
75
- expect(condition.expression).toBe('attribute_exists(#email)');
76
- expect(condition.names).toEqual({ '#email': 'email' });
77
- expect(condition.values).toBeUndefined();
78
- });
79
- });
80
-
81
- describe('notExists operator', () => {
82
- it('should build attribute_not_exists expression', () => {
83
- const op = createOpBuilder();
84
- const attr = { name: 'deletedAt' };
85
-
86
- const condition = op.notExists(attr);
87
-
88
- expect(condition.expression).toBe('attribute_not_exists(#deletedAt)');
89
- expect(condition.names).toEqual({ '#deletedAt': 'deletedAt' });
90
- expect(condition.values).toBeUndefined();
91
- });
92
- });
93
-
94
- describe('attributeType operator', () => {
95
- it('should build attribute_type expression for String', () => {
96
- const op = createOpBuilder();
97
- const attr = { name: 'name' };
98
-
99
- const condition = op.attributeType(attr, 'S');
100
-
101
- expect(condition.expression).toBe('attribute_type(#name, :name_type_0)');
102
- expect(condition.names).toEqual({ '#name': 'name' });
103
- expect(condition.values).toEqual({ ':name_type_0': 'S' });
104
- });
105
-
106
- it('should build attribute_type expression for Number', () => {
107
- const op = createOpBuilder();
108
- const attr = { name: 'age' };
109
-
110
- const condition = op.attributeType(attr, 'N');
111
-
112
- expect(condition.expression).toBe('attribute_type(#age, :age_type_0)');
113
- expect(condition.names).toEqual({ '#age': 'age' });
114
- expect(condition.values).toEqual({ ':age_type_0': 'N' });
115
- });
116
-
117
- it('should build attribute_type expression for List', () => {
118
- const op = createOpBuilder();
119
- const attr = { name: 'items' };
120
-
121
- const condition = op.attributeType(attr, 'L');
122
-
123
- expect(condition.expression).toBe('attribute_type(#items, :items_type_0)');
124
- expect(condition.names).toEqual({ '#items': 'items' });
125
- expect(condition.values).toEqual({ ':items_type_0': 'L' });
126
- });
127
- });
128
-
129
- describe('in operator', () => {
130
- it('should build IN expression with multiple values', () => {
131
- const op = createOpBuilder();
132
- const attr = { name: 'status' };
133
-
134
- const condition = op.in(attr, ['active', 'pending', 'approved']);
135
-
136
- expect(condition.expression).toBe('#status IN (:status_in0_0, :status_in1_1, :status_in2_2)');
137
- expect(condition.names).toEqual({ '#status': 'status' });
138
- expect(condition.values).toEqual({
139
- ':status_in0_0': 'active',
140
- ':status_in1_1': 'pending',
141
- ':status_in2_2': 'approved',
142
- });
143
- });
144
-
145
- it('should build IN expression with single value', () => {
146
- const op = createOpBuilder();
147
- const attr = { name: 'category' };
148
-
149
- const condition = op.in(attr, ['books']);
150
-
151
- expect(condition.expression).toBe('#category IN (:category_in0_0)');
152
- expect(condition.names).toEqual({ '#category': 'category' });
153
- expect(condition.values).toEqual({
154
- ':category_in0_0': 'books',
155
- });
156
- });
157
- });
158
-
159
- describe('size operator', () => {
160
- it('should build size() = expression', () => {
161
- const op = createOpBuilder();
162
- const attr = { name: 'tags' };
163
-
164
- const condition = op.size(attr).eq(5);
165
-
166
- expect(condition.expression).toBe('size(#tags) = :tags_size_0');
167
- expect(condition.names).toEqual({ '#tags': 'tags' });
168
- expect(condition.values).toEqual({ ':tags_size_0': 5 });
169
- });
170
-
171
- it('should build size() <> expression', () => {
172
- const op = createOpBuilder();
173
- const attr = { name: 'items' };
174
-
175
- const condition = op.size(attr).ne(0);
176
-
177
- expect(condition.expression).toBe('size(#items) <> :items_size_0');
178
- expect(condition.names).toEqual({ '#items': 'items' });
179
- expect(condition.values).toEqual({ ':items_size_0': 0 });
180
- });
181
-
182
- it('should build size() < expression', () => {
183
- const op = createOpBuilder();
184
- const attr = { name: 'name' };
185
-
186
- const condition = op.size(attr).lt(100);
187
-
188
- expect(condition.expression).toBe('size(#name) < :name_size_0');
189
- expect(condition.names).toEqual({ '#name': 'name' });
190
- expect(condition.values).toEqual({ ':name_size_0': 100 });
191
- });
192
-
193
- it('should build size() <= expression', () => {
194
- const op = createOpBuilder();
195
- const attr = { name: 'description' };
196
-
197
- const condition = op.size(attr).lte(500);
198
-
199
- expect(condition.expression).toBe('size(#description) <= :description_size_0');
200
- expect(condition.names).toEqual({ '#description': 'description' });
201
- expect(condition.values).toEqual({ ':description_size_0': 500 });
202
- });
203
-
204
- it('should build size() > expression', () => {
205
- const op = createOpBuilder();
206
- const attr = { name: 'password' };
207
-
208
- const condition = op.size(attr).gt(8);
209
-
210
- expect(condition.expression).toBe('size(#password) > :password_size_0');
211
- expect(condition.names).toEqual({ '#password': 'password' });
212
- expect(condition.values).toEqual({ ':password_size_0': 8 });
213
- });
214
-
215
- it('should build size() >= expression', () => {
216
- const op = createOpBuilder();
217
- const attr = { name: 'content' };
218
-
219
- const condition = op.size(attr).gte(10);
220
-
221
- expect(condition.expression).toBe('size(#content) >= :content_size_0');
222
- expect(condition.names).toEqual({ '#content': 'content' });
223
- expect(condition.values).toEqual({ ':content_size_0': 10 });
224
- });
225
- });
226
-
227
- describe('Combined operators', () => {
228
- it('should combine exists and other conditions with AND', () => {
229
- const op = createOpBuilder();
230
- const attr1 = { name: 'email' };
231
- const attr2 = { name: 'verified' };
232
-
233
- const condition = op.and(op.exists(attr1), op.eq(attr2, true));
234
-
235
- expect(condition.operator).toBe('AND');
236
- expect(condition.children).toBeDefined();
237
- expect(condition.children).toHaveLength(2);
238
- expect(condition.children![0]!.expression).toBe('attribute_exists(#email)');
239
- expect(condition.children![1]!.expression).toBe('#verified = :verified_0');
240
- });
241
-
242
- it('should combine size with comparison', () => {
243
- const op = createOpBuilder();
244
- const attr1 = { name: 'tags' };
245
- const attr2 = { name: 'status' };
246
-
247
- const condition = op.and(op.size(attr1).gt(0), op.eq(attr2, 'active'));
248
-
249
- expect(condition.operator).toBe('AND');
250
- expect(condition.children).toBeDefined();
251
- expect(condition.children).toHaveLength(2);
252
- expect(condition.children![0]!.expression).toBe('size(#tags) > :tags_size_0');
253
- expect(condition.children![1]!.expression).toBe('#status = :status_1');
254
- });
255
-
256
- it('should use IN with OR', () => {
257
- const op = createOpBuilder();
258
- const attr1 = { name: 'status' };
259
- const attr2 = { name: 'priority' };
260
-
261
- const condition = op.or(op.in(attr1, ['active', 'pending']), op.eq(attr2, 'high'));
262
-
263
- expect(condition.operator).toBe('OR');
264
- expect(condition.children).toBeDefined();
265
- expect(condition.children).toHaveLength(2);
266
- expect(condition.children![0]!.expression).toContain('IN');
267
- expect(condition.children![1]!.expression).toBe('#priority = :priority_2');
268
- });
269
- });
270
- });
@@ -1,208 +0,0 @@
1
- import { OpBuilder } from './types';
2
-
3
- /**
4
- * Creates a scoped OpBuilder with its own counter for unique value placeholders.
5
- * Each builder instance maintains isolated state to prevent naming conflicts.
6
- */
7
- export function createOpBuilder(): OpBuilder {
8
- let valueCounter = 0;
9
-
10
- /**
11
- * Generates a unique value placeholder name within this builder's scope
12
- */
13
- function getUniqueValueName(baseName: string): string {
14
- return `${baseName}_${valueCounter++}`;
15
- }
16
-
17
- return {
18
- eq: (attr, value) => {
19
- const valueName = getUniqueValueName(attr.name);
20
- return {
21
- expression: `#${attr.name} = :${valueName}`,
22
- names: { [`#${attr.name}`]: attr.name },
23
- values: { [`:${valueName}`]: value },
24
- keyOperator: 'eq',
25
- };
26
- },
27
- ne: (attr, value) => {
28
- const valueName = getUniqueValueName(attr.name);
29
- return {
30
- expression: `#${attr.name} <> :${valueName}`,
31
- names: { [`#${attr.name}`]: attr.name },
32
- values: { [`:${valueName}`]: value },
33
- keyOperator: 'ne',
34
- };
35
- },
36
- lt: (attr, value) => {
37
- const valueName = getUniqueValueName(attr.name);
38
- return {
39
- expression: `#${attr.name} < :${valueName}`,
40
- names: { [`#${attr.name}`]: attr.name },
41
- values: { [`:${valueName}`]: value },
42
- keyOperator: 'lt',
43
- };
44
- },
45
- lte: (attr, value) => {
46
- const valueName = getUniqueValueName(attr.name);
47
- return {
48
- expression: `#${attr.name} <= :${valueName}`,
49
- names: { [`#${attr.name}`]: attr.name },
50
- values: { [`:${valueName}`]: value },
51
- keyOperator: 'lte',
52
- };
53
- },
54
- gt: (attr, value) => {
55
- const valueName = getUniqueValueName(attr.name);
56
- return {
57
- expression: `#${attr.name} > :${valueName}`,
58
- names: { [`#${attr.name}`]: attr.name },
59
- values: { [`:${valueName}`]: value },
60
- keyOperator: 'gt',
61
- };
62
- },
63
- gte: (attr, value) => {
64
- const valueName = getUniqueValueName(attr.name);
65
- return {
66
- expression: `#${attr.name} >= :${valueName}`,
67
- names: { [`#${attr.name}`]: attr.name },
68
- values: { [`:${valueName}`]: value },
69
- keyOperator: 'gte',
70
- };
71
- },
72
- between: (attr, low, high) => {
73
- const lowName = getUniqueValueName(`${attr.name}_low`);
74
- const highName = getUniqueValueName(`${attr.name}_high`);
75
- return {
76
- expression: `#${attr.name} BETWEEN :${lowName} AND :${highName}`,
77
- names: { [`#${attr.name}`]: attr.name },
78
- values: {
79
- [`:${lowName}`]: low,
80
- [`:${highName}`]: high,
81
- },
82
- keyOperator: 'between',
83
- };
84
- },
85
- beginsWith: (attr, value) => {
86
- const valueName = getUniqueValueName(attr.name);
87
- return {
88
- expression: `begins_with(#${attr.name}, :${valueName})`,
89
- names: { [`#${attr.name}`]: attr.name },
90
- values: { [`:${valueName}`]: value },
91
- keyOperator: 'beginsWith',
92
- };
93
- },
94
- contains: (attr, value) => {
95
- const valueName = getUniqueValueName(attr.name);
96
- return {
97
- expression: `contains(#${attr.name}, :${valueName})`,
98
- names: { [`#${attr.name}`]: attr.name },
99
- values: { [`:${valueName}`]: value },
100
- };
101
- },
102
- exists: (attr) => ({
103
- expression: `attribute_exists(#${attr.name})`,
104
- names: { [`#${attr.name}`]: attr.name },
105
- }),
106
- notExists: (attr) => ({
107
- expression: `attribute_not_exists(#${attr.name})`,
108
- names: { [`#${attr.name}`]: attr.name },
109
- }),
110
- attributeType: (attr, type) => {
111
- const valueName = getUniqueValueName(`${attr.name}_type`);
112
- return {
113
- expression: `attribute_type(#${attr.name}, :${valueName})`,
114
- names: { [`#${attr.name}`]: attr.name },
115
- values: { [`:${valueName}`]: type },
116
- };
117
- },
118
- in: (attr, values) => {
119
- const valueNames = values.map((_, i) => getUniqueValueName(`${attr.name}_in${i}`));
120
- const placeholders = valueNames.map((name) => `:${name}`).join(', ');
121
- const valuesObj: Record<string, any> = {};
122
- valueNames.forEach((name, i) => {
123
- valuesObj[`:${name}`] = values[i];
124
- });
125
-
126
- return {
127
- expression: `#${attr.name} IN (${placeholders})`,
128
- names: { [`#${attr.name}`]: attr.name },
129
- values: valuesObj,
130
- };
131
- },
132
- size: (attr) => {
133
- const sizeExpr = `size(#${attr.name})`;
134
- const names = { [`#${attr.name}`]: attr.name };
135
-
136
- return {
137
- eq: (value: number) => {
138
- const valueName = getUniqueValueName(`${attr.name}_size`);
139
- return {
140
- expression: `${sizeExpr} = :${valueName}`,
141
- names,
142
- values: { [`:${valueName}`]: value },
143
- };
144
- },
145
- ne: (value: number) => {
146
- const valueName = getUniqueValueName(`${attr.name}_size`);
147
- return {
148
- expression: `${sizeExpr} <> :${valueName}`,
149
- names,
150
- values: { [`:${valueName}`]: value },
151
- };
152
- },
153
- lt: (value: number) => {
154
- const valueName = getUniqueValueName(`${attr.name}_size`);
155
- return {
156
- expression: `${sizeExpr} < :${valueName}`,
157
- names,
158
- values: { [`:${valueName}`]: value },
159
- };
160
- },
161
- lte: (value: number) => {
162
- const valueName = getUniqueValueName(`${attr.name}_size`);
163
- return {
164
- expression: `${sizeExpr} <= :${valueName}`,
165
- names,
166
- values: { [`:${valueName}`]: value },
167
- };
168
- },
169
- gt: (value: number) => {
170
- const valueName = getUniqueValueName(`${attr.name}_size`);
171
- return {
172
- expression: `${sizeExpr} > :${valueName}`,
173
- names,
174
- values: { [`:${valueName}`]: value },
175
- };
176
- },
177
- gte: (value: number) => {
178
- const valueName = getUniqueValueName(`${attr.name}_size`);
179
- return {
180
- expression: `${sizeExpr} >= :${valueName}`,
181
- names,
182
- values: { [`:${valueName}`]: value },
183
- };
184
- },
185
- };
186
- },
187
- and: (...conditions) => ({
188
- expression: '',
189
- operator: 'AND',
190
- children: conditions,
191
- }),
192
- or: (...conditions) => ({
193
- expression: '',
194
- operator: 'OR',
195
- children: conditions,
196
- }),
197
- not: (condition) => ({
198
- ...condition,
199
- isNegated: true,
200
- }),
201
- };
202
- }
203
-
204
- /**
205
- * @deprecated Use createOpBuilder() instead to avoid global state.
206
- * This export is maintained for backward compatibility.
207
- */
208
- export const opBuilder: OpBuilder = createOpBuilder();
@@ -1,28 +0,0 @@
1
- /**
2
- * Builds a `ProjectionExpression` together with the matching
3
- * `ExpressionAttributeNames` map so that any attribute — including
4
- * DynamoDB reserved words like `name`, `date`, `status`, `type`, `data`,
5
- * `count`, `size`, `value`, `time`, `year`, `source`, … — is referenced
6
- * via a `#placeholder`.
7
- *
8
- * Without this indirection DynamoDB rejects the request with
9
- * `ValidationException: Attribute name is a reserved keyword`.
10
- */
11
- export function buildProjectionExpression(attrs: string[]): {
12
- ProjectionExpression: string;
13
- ExpressionAttributeNames: Record<string, string>;
14
- } {
15
- const names: Record<string, string> = {};
16
- const projectionParts: string[] = [];
17
-
18
- attrs.forEach((attr) => {
19
- const placeholder = `#${attr}`;
20
- names[placeholder] = attr;
21
- projectionParts.push(placeholder);
22
- });
23
-
24
- return {
25
- ProjectionExpression: projectionParts.join(', '),
26
- ExpressionAttributeNames: names,
27
- };
28
- }