@ftschopp/dynatable-core 1.0.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 (197) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +17 -0
  3. package/dist/builders/batch-get/create-batch-get-builder.d.ts +18 -0
  4. package/dist/builders/batch-get/create-batch-get-builder.d.ts.map +1 -0
  5. package/dist/builders/batch-get/create-batch-get-builder.js +71 -0
  6. package/dist/builders/batch-get/index.d.ts +3 -0
  7. package/dist/builders/batch-get/index.d.ts.map +1 -0
  8. package/dist/builders/batch-get/index.js +18 -0
  9. package/dist/builders/batch-get/types.d.ts +25 -0
  10. package/dist/builders/batch-get/types.d.ts.map +1 -0
  11. package/dist/builders/batch-get/types.js +2 -0
  12. package/dist/builders/batch-write/create-batch-write-builder.d.ts +12 -0
  13. package/dist/builders/batch-write/create-batch-write-builder.d.ts.map +1 -0
  14. package/dist/builders/batch-write/create-batch-write-builder.js +35 -0
  15. package/dist/builders/batch-write/index.d.ts +3 -0
  16. package/dist/builders/batch-write/index.d.ts.map +1 -0
  17. package/dist/builders/batch-write/index.js +18 -0
  18. package/dist/builders/batch-write/types.d.ts +30 -0
  19. package/dist/builders/batch-write/types.d.ts.map +1 -0
  20. package/dist/builders/batch-write/types.js +2 -0
  21. package/dist/builders/delete/create-delete-builder.d.ts +9 -0
  22. package/dist/builders/delete/create-delete-builder.d.ts.map +1 -0
  23. package/dist/builders/delete/create-delete-builder.js +72 -0
  24. package/dist/builders/delete/index.d.ts +3 -0
  25. package/dist/builders/delete/index.d.ts.map +1 -0
  26. package/dist/builders/delete/index.js +18 -0
  27. package/dist/builders/delete/types.d.ts +11 -0
  28. package/dist/builders/delete/types.d.ts.map +1 -0
  29. package/dist/builders/delete/types.js +2 -0
  30. package/dist/builders/get/create-get-builder.d.ts +12 -0
  31. package/dist/builders/get/create-get-builder.d.ts.map +1 -0
  32. package/dist/builders/get/create-get-builder.js +95 -0
  33. package/dist/builders/get/index.d.ts +3 -0
  34. package/dist/builders/get/index.d.ts.map +1 -0
  35. package/dist/builders/get/index.js +18 -0
  36. package/dist/builders/get/types.d.ts +28 -0
  37. package/dist/builders/get/types.d.ts.map +1 -0
  38. package/dist/builders/get/types.js +2 -0
  39. package/dist/builders/index.d.ts +12 -0
  40. package/dist/builders/index.d.ts.map +1 -0
  41. package/dist/builders/index.js +29 -0
  42. package/dist/builders/put/create-put-builder.d.ts +9 -0
  43. package/dist/builders/put/create-put-builder.d.ts.map +1 -0
  44. package/dist/builders/put/create-put-builder.js +104 -0
  45. package/dist/builders/put/index.d.ts +3 -0
  46. package/dist/builders/put/index.d.ts.map +1 -0
  47. package/dist/builders/put/index.js +18 -0
  48. package/dist/builders/put/types.d.ts +17 -0
  49. package/dist/builders/put/types.d.ts.map +1 -0
  50. package/dist/builders/put/types.js +2 -0
  51. package/dist/builders/query/create-query-builder.d.ts +9 -0
  52. package/dist/builders/query/create-query-builder.d.ts.map +1 -0
  53. package/dist/builders/query/create-query-builder.js +283 -0
  54. package/dist/builders/query/index.d.ts +3 -0
  55. package/dist/builders/query/index.d.ts.map +1 -0
  56. package/dist/builders/query/index.js +18 -0
  57. package/dist/builders/query/types.d.ts +79 -0
  58. package/dist/builders/query/types.d.ts.map +1 -0
  59. package/dist/builders/query/types.js +2 -0
  60. package/dist/builders/scan/create-scan-builder.d.ts +12 -0
  61. package/dist/builders/scan/create-scan-builder.d.ts.map +1 -0
  62. package/dist/builders/scan/create-scan-builder.js +103 -0
  63. package/dist/builders/scan/index.d.ts +3 -0
  64. package/dist/builders/scan/index.d.ts.map +1 -0
  65. package/dist/builders/scan/index.js +18 -0
  66. package/dist/builders/scan/types.d.ts +43 -0
  67. package/dist/builders/scan/types.d.ts.map +1 -0
  68. package/dist/builders/scan/types.js +2 -0
  69. package/dist/builders/shared/conditions.d.ts +18 -0
  70. package/dist/builders/shared/conditions.d.ts.map +1 -0
  71. package/dist/builders/shared/conditions.js +48 -0
  72. package/dist/builders/shared/index.d.ts +4 -0
  73. package/dist/builders/shared/index.d.ts.map +1 -0
  74. package/dist/builders/shared/index.js +19 -0
  75. package/dist/builders/shared/operators.d.ts +12 -0
  76. package/dist/builders/shared/operators.d.ts.map +1 -0
  77. package/dist/builders/shared/operators.js +197 -0
  78. package/dist/builders/shared/types.d.ts +77 -0
  79. package/dist/builders/shared/types.d.ts.map +1 -0
  80. package/dist/builders/shared/types.js +6 -0
  81. package/dist/builders/transact-get/create-transact-get-builder.d.ts +18 -0
  82. package/dist/builders/transact-get/create-transact-get-builder.d.ts.map +1 -0
  83. package/dist/builders/transact-get/create-transact-get-builder.js +60 -0
  84. package/dist/builders/transact-get/index.d.ts +3 -0
  85. package/dist/builders/transact-get/index.d.ts.map +1 -0
  86. package/dist/builders/transact-get/index.js +18 -0
  87. package/dist/builders/transact-get/types.d.ts +28 -0
  88. package/dist/builders/transact-get/types.d.ts.map +1 -0
  89. package/dist/builders/transact-get/types.js +2 -0
  90. package/dist/builders/transact-write/create-transact-write-builder.d.ts +18 -0
  91. package/dist/builders/transact-write/create-transact-write-builder.d.ts.map +1 -0
  92. package/dist/builders/transact-write/create-transact-write-builder.js +97 -0
  93. package/dist/builders/transact-write/index.d.ts +3 -0
  94. package/dist/builders/transact-write/index.d.ts.map +1 -0
  95. package/dist/builders/transact-write/index.js +18 -0
  96. package/dist/builders/transact-write/types.d.ts +34 -0
  97. package/dist/builders/transact-write/types.d.ts.map +1 -0
  98. package/dist/builders/transact-write/types.js +2 -0
  99. package/dist/builders/update/create-update-builder.d.ts +14 -0
  100. package/dist/builders/update/create-update-builder.d.ts.map +1 -0
  101. package/dist/builders/update/create-update-builder.js +180 -0
  102. package/dist/builders/update/index.d.ts +3 -0
  103. package/dist/builders/update/index.d.ts.map +1 -0
  104. package/dist/builders/update/index.js +18 -0
  105. package/dist/builders/update/types.d.ts +35 -0
  106. package/dist/builders/update/types.d.ts.map +1 -0
  107. package/dist/builders/update/types.js +2 -0
  108. package/dist/core/types.d.ts +224 -0
  109. package/dist/core/types.d.ts.map +1 -0
  110. package/dist/core/types.js +3 -0
  111. package/dist/entity.d.ts +73 -0
  112. package/dist/entity.d.ts.map +1 -0
  113. package/dist/entity.js +161 -0
  114. package/dist/index.d.ts +4 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.js +20 -0
  117. package/dist/table.d.ts +70 -0
  118. package/dist/table.d.ts.map +1 -0
  119. package/dist/table.js +69 -0
  120. package/dist/utils/dynamodb-logger.d.ts +118 -0
  121. package/dist/utils/dynamodb-logger.d.ts.map +1 -0
  122. package/dist/utils/dynamodb-logger.js +125 -0
  123. package/dist/utils/model-utils.d.ts +24 -0
  124. package/dist/utils/model-utils.d.ts.map +1 -0
  125. package/dist/utils/model-utils.js +88 -0
  126. package/dist/utils/zod-utils.d.ts +13 -0
  127. package/dist/utils/zod-utils.d.ts.map +1 -0
  128. package/dist/utils/zod-utils.js +35 -0
  129. package/eslint.config.mjs +4 -0
  130. package/jest.config.js +11 -0
  131. package/package.json +36 -0
  132. package/src/builders/README.md +272 -0
  133. package/src/builders/batch-get/README.md +98 -0
  134. package/src/builders/batch-get/create-batch-get-builder.test.ts +165 -0
  135. package/src/builders/batch-get/create-batch-get-builder.ts +106 -0
  136. package/src/builders/batch-get/index.ts +2 -0
  137. package/src/builders/batch-get/types.ts +29 -0
  138. package/src/builders/batch-write/README.md +204 -0
  139. package/src/builders/batch-write/create-batch-write-builder.test.ts +173 -0
  140. package/src/builders/batch-write/create-batch-write-builder.ts +49 -0
  141. package/src/builders/batch-write/index.ts +2 -0
  142. package/src/builders/batch-write/types.ts +33 -0
  143. package/src/builders/delete/create-delete-builder.test.ts +294 -0
  144. package/src/builders/delete/create-delete-builder.ts +100 -0
  145. package/src/builders/delete/index.ts +2 -0
  146. package/src/builders/delete/types.ts +11 -0
  147. package/src/builders/get/create-get-builder.test.ts +272 -0
  148. package/src/builders/get/create-get-builder.ts +140 -0
  149. package/src/builders/get/index.ts +2 -0
  150. package/src/builders/get/types.ts +30 -0
  151. package/src/builders/index.ts +14 -0
  152. package/src/builders/put/create-put-builder.test.ts +213 -0
  153. package/src/builders/put/create-put-builder.ts +151 -0
  154. package/src/builders/put/index.ts +2 -0
  155. package/src/builders/put/types.ts +18 -0
  156. package/src/builders/query/create-query-builder.test.ts +230 -0
  157. package/src/builders/query/create-query-builder.ts +353 -0
  158. package/src/builders/query/index.ts +2 -0
  159. package/src/builders/query/types.ts +95 -0
  160. package/src/builders/scan/create-scan-builder.test.ts +260 -0
  161. package/src/builders/scan/create-scan-builder.ts +217 -0
  162. package/src/builders/scan/index.ts +2 -0
  163. package/src/builders/scan/types.ts +49 -0
  164. package/src/builders/shared/conditions.ts +58 -0
  165. package/src/builders/shared/index.ts +3 -0
  166. package/src/builders/shared/operators.test.ts +270 -0
  167. package/src/builders/shared/operators.ts +200 -0
  168. package/src/builders/shared/types.ts +100 -0
  169. package/src/builders/transact-get/README.md +167 -0
  170. package/src/builders/transact-get/create-transact-get-builder.test.ts +239 -0
  171. package/src/builders/transact-get/create-transact-get-builder.ts +67 -0
  172. package/src/builders/transact-get/index.ts +2 -0
  173. package/src/builders/transact-get/types.ts +31 -0
  174. package/src/builders/transact-write/README.md +166 -0
  175. package/src/builders/transact-write/create-transact-write-builder.test.ts +288 -0
  176. package/src/builders/transact-write/create-transact-write-builder.ts +118 -0
  177. package/src/builders/transact-write/index.ts +2 -0
  178. package/src/builders/transact-write/types.ts +33 -0
  179. package/src/builders/update/create-update-builder.test.ts +333 -0
  180. package/src/builders/update/create-update-builder.ts +284 -0
  181. package/src/builders/update/index.ts +2 -0
  182. package/src/builders/update/types.ts +42 -0
  183. package/src/core/types.test.ts +506 -0
  184. package/src/core/types.ts +290 -0
  185. package/src/entity.ts +337 -0
  186. package/src/index.ts +22 -0
  187. package/src/table.ts +109 -0
  188. package/src/utils/dynamodb-logger.test.ts +246 -0
  189. package/src/utils/dynamodb-logger.ts +175 -0
  190. package/src/utils/model-utils.test.ts +232 -0
  191. package/src/utils/model-utils.ts +101 -0
  192. package/src/utils/zod-utils.test.ts +272 -0
  193. package/src/utils/zod-utils.ts +36 -0
  194. package/tests/integration/instagram-clone.integration.test.ts +966 -0
  195. package/tests/integration/pagination-timestamps.integration.test.ts +375 -0
  196. package/tests/integration/transactions.integration.test.ts +529 -0
  197. package/tsconfig.json +12 -0
@@ -0,0 +1,270 @@
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
+ });
@@ -0,0 +1,200 @@
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
+ };
25
+ },
26
+ ne: (attr, value) => {
27
+ const valueName = getUniqueValueName(attr.name);
28
+ return {
29
+ expression: `#${attr.name} <> :${valueName}`,
30
+ names: { [`#${attr.name}`]: attr.name },
31
+ values: { [`:${valueName}`]: value },
32
+ };
33
+ },
34
+ lt: (attr, value) => {
35
+ const valueName = getUniqueValueName(attr.name);
36
+ return {
37
+ expression: `#${attr.name} < :${valueName}`,
38
+ names: { [`#${attr.name}`]: attr.name },
39
+ values: { [`:${valueName}`]: value },
40
+ };
41
+ },
42
+ lte: (attr, value) => {
43
+ const valueName = getUniqueValueName(attr.name);
44
+ return {
45
+ expression: `#${attr.name} <= :${valueName}`,
46
+ names: { [`#${attr.name}`]: attr.name },
47
+ values: { [`:${valueName}`]: value },
48
+ };
49
+ },
50
+ gt: (attr, value) => {
51
+ const valueName = getUniqueValueName(attr.name);
52
+ return {
53
+ expression: `#${attr.name} > :${valueName}`,
54
+ names: { [`#${attr.name}`]: attr.name },
55
+ values: { [`:${valueName}`]: value },
56
+ };
57
+ },
58
+ gte: (attr, value) => {
59
+ const valueName = getUniqueValueName(attr.name);
60
+ return {
61
+ expression: `#${attr.name} >= :${valueName}`,
62
+ names: { [`#${attr.name}`]: attr.name },
63
+ values: { [`:${valueName}`]: value },
64
+ };
65
+ },
66
+ between: (attr, low, high) => {
67
+ const lowName = getUniqueValueName(`${attr.name}_low`);
68
+ const highName = getUniqueValueName(`${attr.name}_high`);
69
+ return {
70
+ expression: `#${attr.name} BETWEEN :${lowName} AND :${highName}`,
71
+ names: { [`#${attr.name}`]: attr.name },
72
+ values: {
73
+ [`:${lowName}`]: low,
74
+ [`:${highName}`]: high,
75
+ },
76
+ };
77
+ },
78
+ beginsWith: (attr, value) => {
79
+ const valueName = getUniqueValueName(attr.name);
80
+ return {
81
+ expression: `begins_with(#${attr.name}, :${valueName})`,
82
+ names: { [`#${attr.name}`]: attr.name },
83
+ values: { [`:${valueName}`]: value },
84
+ };
85
+ },
86
+ contains: (attr, value) => {
87
+ const valueName = getUniqueValueName(attr.name);
88
+ return {
89
+ expression: `contains(#${attr.name}, :${valueName})`,
90
+ names: { [`#${attr.name}`]: attr.name },
91
+ values: { [`:${valueName}`]: value },
92
+ };
93
+ },
94
+ exists: (attr) => ({
95
+ expression: `attribute_exists(#${attr.name})`,
96
+ names: { [`#${attr.name}`]: attr.name },
97
+ }),
98
+ notExists: (attr) => ({
99
+ expression: `attribute_not_exists(#${attr.name})`,
100
+ names: { [`#${attr.name}`]: attr.name },
101
+ }),
102
+ attributeType: (attr, type) => {
103
+ const valueName = getUniqueValueName(`${attr.name}_type`);
104
+ return {
105
+ expression: `attribute_type(#${attr.name}, :${valueName})`,
106
+ names: { [`#${attr.name}`]: attr.name },
107
+ values: { [`:${valueName}`]: type },
108
+ };
109
+ },
110
+ in: (attr, values) => {
111
+ const valueNames = values.map((_, i) => getUniqueValueName(`${attr.name}_in${i}`));
112
+ const placeholders = valueNames.map((name) => `:${name}`).join(', ');
113
+ const valuesObj: Record<string, any> = {};
114
+ valueNames.forEach((name, i) => {
115
+ valuesObj[`:${name}`] = values[i];
116
+ });
117
+
118
+ return {
119
+ expression: `#${attr.name} IN (${placeholders})`,
120
+ names: { [`#${attr.name}`]: attr.name },
121
+ values: valuesObj,
122
+ };
123
+ },
124
+ size: (attr) => {
125
+ const sizeExpr = `size(#${attr.name})`;
126
+ const names = { [`#${attr.name}`]: attr.name };
127
+
128
+ return {
129
+ eq: (value: number) => {
130
+ const valueName = getUniqueValueName(`${attr.name}_size`);
131
+ return {
132
+ expression: `${sizeExpr} = :${valueName}`,
133
+ names,
134
+ values: { [`:${valueName}`]: value },
135
+ };
136
+ },
137
+ ne: (value: number) => {
138
+ const valueName = getUniqueValueName(`${attr.name}_size`);
139
+ return {
140
+ expression: `${sizeExpr} <> :${valueName}`,
141
+ names,
142
+ values: { [`:${valueName}`]: value },
143
+ };
144
+ },
145
+ lt: (value: number) => {
146
+ const valueName = getUniqueValueName(`${attr.name}_size`);
147
+ return {
148
+ expression: `${sizeExpr} < :${valueName}`,
149
+ names,
150
+ values: { [`:${valueName}`]: value },
151
+ };
152
+ },
153
+ lte: (value: number) => {
154
+ const valueName = getUniqueValueName(`${attr.name}_size`);
155
+ return {
156
+ expression: `${sizeExpr} <= :${valueName}`,
157
+ names,
158
+ values: { [`:${valueName}`]: value },
159
+ };
160
+ },
161
+ gt: (value: number) => {
162
+ const valueName = getUniqueValueName(`${attr.name}_size`);
163
+ return {
164
+ expression: `${sizeExpr} > :${valueName}`,
165
+ names,
166
+ values: { [`:${valueName}`]: value },
167
+ };
168
+ },
169
+ gte: (value: number) => {
170
+ const valueName = getUniqueValueName(`${attr.name}_size`);
171
+ return {
172
+ expression: `${sizeExpr} >= :${valueName}`,
173
+ names,
174
+ values: { [`:${valueName}`]: value },
175
+ };
176
+ },
177
+ };
178
+ },
179
+ and: (...conditions) => ({
180
+ expression: '',
181
+ operator: 'AND',
182
+ children: conditions,
183
+ }),
184
+ or: (...conditions) => ({
185
+ expression: '',
186
+ operator: 'OR',
187
+ children: conditions,
188
+ }),
189
+ not: (condition) => ({
190
+ ...condition,
191
+ isNegated: true,
192
+ }),
193
+ };
194
+ }
195
+
196
+ /**
197
+ * @deprecated Use createOpBuilder() instead to avoid global state.
198
+ * This export is maintained for backward compatibility.
199
+ */
200
+ export const opBuilder: OpBuilder = createOpBuilder();
@@ -0,0 +1,100 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /**
3
+ * Shared types for DynamoDB builders
4
+ */
5
+
6
+ /**
7
+ * Condition object for DynamoDB expressions
8
+ */
9
+ export type Condition = {
10
+ expression: string;
11
+ names?: Record<string, string>;
12
+ values?: Record<string, any>;
13
+ operator?: 'AND' | 'OR';
14
+ isNegated?: boolean;
15
+ children?: Condition[];
16
+ };
17
+
18
+ /**
19
+ * Attribute reference for building expressions
20
+ */
21
+ export type AttrRef = {
22
+ name: string;
23
+ path?: string;
24
+ };
25
+
26
+ /**
27
+ * Type-safe attribute builder for models
28
+ * Note: All properties are required (non-optional) to ensure type safety in expressions
29
+ */
30
+ export type AttrBuilder<Model> = {
31
+ [K in keyof Model]-?: AttrRef;
32
+ };
33
+
34
+ /**
35
+ * Available operators for building conditions
36
+ */
37
+ export type OpBuilder = {
38
+ // Comparison operators
39
+ eq: (attr: AttrRef, value: any) => Condition;
40
+ ne: (attr: AttrRef, value: any) => Condition;
41
+ lt: (attr: AttrRef, value: any) => Condition;
42
+ lte: (attr: AttrRef, value: any) => Condition;
43
+ gt: (attr: AttrRef, value: any) => Condition;
44
+ gte: (attr: AttrRef, value: any) => Condition;
45
+ between: (attr: AttrRef, low: any, high: any) => Condition;
46
+
47
+ // String/Set operators
48
+ beginsWith: (attr: AttrRef, value: string) => Condition;
49
+ contains: (attr: AttrRef, value: any) => Condition;
50
+
51
+ // Attribute existence operators
52
+ exists: (attr: AttrRef) => Condition;
53
+ notExists: (attr: AttrRef) => Condition;
54
+
55
+ // Type checking
56
+ attributeType: (
57
+ attr: AttrRef,
58
+ type: 'S' | 'N' | 'B' | 'SS' | 'NS' | 'BS' | 'M' | 'L' | 'NULL' | 'BOOL'
59
+ ) => Condition;
60
+
61
+ // IN operator
62
+ in: (attr: AttrRef, values: any[]) => Condition;
63
+
64
+ // Size function
65
+ size: (attr: AttrRef) => SizeRef;
66
+
67
+ // Logical operators
68
+ and: (...conditions: Condition[]) => Condition;
69
+ or: (...conditions: Condition[]) => Condition;
70
+ not: (condition: Condition) => Condition;
71
+ };
72
+
73
+ /**
74
+ * Reference to the size() function result for comparisons
75
+ */
76
+ export type SizeRef = {
77
+ eq: (value: number) => Condition;
78
+ ne: (value: number) => Condition;
79
+ lt: (value: number) => Condition;
80
+ lte: (value: number) => Condition;
81
+ gt: (value: number) => Condition;
82
+ gte: (value: number) => Condition;
83
+ };
84
+
85
+ /**
86
+ * Base interface for executable builders
87
+ */
88
+ export interface ExecutableBuilder<Result> {
89
+ dbParams(): any;
90
+ execute(): Promise<Result>;
91
+ }
92
+
93
+ /**
94
+ * Base interface for operation builders with conditions
95
+ */
96
+ export interface OperationBuilder<Model> {
97
+ where(fn: (attr: AttrBuilder<Model>, op: OpBuilder) => Condition): this;
98
+ dbParams(): any;
99
+ execute(): Promise<Model>;
100
+ }