@ftschopp/dynatable-core 1.4.2 → 1.4.3

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 (81) hide show
  1. package/CHANGELOG.md +7 -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/core/types.d.ts +5 -0
  5. package/dist/core/types.d.ts.map +1 -1
  6. package/package.json +11 -2
  7. package/eslint.config.mjs +0 -4
  8. package/jest.config.js +0 -11
  9. package/src/builders/README.md +0 -339
  10. package/src/builders/batch-get/README.md +0 -43
  11. package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
  12. package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
  13. package/src/builders/batch-get/index.ts +0 -2
  14. package/src/builders/batch-get/types.ts +0 -28
  15. package/src/builders/batch-write/README.md +0 -204
  16. package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
  17. package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
  18. package/src/builders/batch-write/index.ts +0 -2
  19. package/src/builders/batch-write/types.ts +0 -33
  20. package/src/builders/delete/create-delete-builder.test.ts +0 -294
  21. package/src/builders/delete/create-delete-builder.ts +0 -100
  22. package/src/builders/delete/index.ts +0 -2
  23. package/src/builders/delete/types.ts +0 -17
  24. package/src/builders/get/create-get-builder.test.ts +0 -272
  25. package/src/builders/get/create-get-builder.ts +0 -117
  26. package/src/builders/get/index.ts +0 -2
  27. package/src/builders/get/types.ts +0 -39
  28. package/src/builders/index.ts +0 -14
  29. package/src/builders/put/create-put-builder.test.ts +0 -213
  30. package/src/builders/put/create-put-builder.ts +0 -160
  31. package/src/builders/put/index.ts +0 -2
  32. package/src/builders/put/types.ts +0 -24
  33. package/src/builders/query/create-query-builder.test.ts +0 -770
  34. package/src/builders/query/create-query-builder.ts +0 -455
  35. package/src/builders/query/index.ts +0 -2
  36. package/src/builders/query/types.ts +0 -109
  37. package/src/builders/scan/create-scan-builder.test.ts +0 -416
  38. package/src/builders/scan/create-scan-builder.ts +0 -265
  39. package/src/builders/scan/index.ts +0 -2
  40. package/src/builders/scan/types.ts +0 -88
  41. package/src/builders/shared/conditions.ts +0 -58
  42. package/src/builders/shared/index.ts +0 -4
  43. package/src/builders/shared/operators.test.ts +0 -270
  44. package/src/builders/shared/operators.ts +0 -208
  45. package/src/builders/shared/projection.ts +0 -28
  46. package/src/builders/shared/types.ts +0 -101
  47. package/src/builders/transact-get/README.md +0 -167
  48. package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
  49. package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
  50. package/src/builders/transact-get/index.ts +0 -2
  51. package/src/builders/transact-get/types.ts +0 -31
  52. package/src/builders/transact-write/README.md +0 -202
  53. package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
  54. package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
  55. package/src/builders/transact-write/index.ts +0 -2
  56. package/src/builders/transact-write/types.ts +0 -71
  57. package/src/builders/update/create-update-builder.test.ts +0 -573
  58. package/src/builders/update/create-update-builder.ts +0 -375
  59. package/src/builders/update/index.ts +0 -2
  60. package/src/builders/update/types.ts +0 -63
  61. package/src/core/types.test.ts +0 -641
  62. package/src/core/types.ts +0 -366
  63. package/src/entity/create-entity-api.ts +0 -224
  64. package/src/entity/index.ts +0 -19
  65. package/src/entity/middleware/factories.ts +0 -15
  66. package/src/entity/middleware/types.ts +0 -7
  67. package/src/entity/middleware/with-middleware.ts +0 -37
  68. package/src/entity/types.ts +0 -79
  69. package/src/entity/validation/key-validation.ts +0 -34
  70. package/src/index.ts +0 -23
  71. package/src/table.ts +0 -116
  72. package/src/utils/dynamodb-logger.test.ts +0 -246
  73. package/src/utils/dynamodb-logger.ts +0 -175
  74. package/src/utils/model-utils.test.ts +0 -452
  75. package/src/utils/model-utils.ts +0 -188
  76. package/src/utils/zod-utils.test.ts +0 -363
  77. package/src/utils/zod-utils.ts +0 -56
  78. package/tests/integration/instagram-clone.integration.test.ts +0 -1019
  79. package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
  80. package/tests/integration/transactions.integration.test.ts +0 -528
  81. 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
- }