@aurios/mizzle 1.1.1 → 1.1.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 (116) hide show
  1. package/.turbo/turbo-build.log +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +57 -0
  4. package/dist/chunk-AQVECMXP.js +1 -0
  5. package/dist/chunk-DU7UPWBW.js +1 -0
  6. package/dist/chunk-GPYZK4WY.js +1 -0
  7. package/dist/chunk-NPPZW6VT.js +1 -0
  8. package/dist/chunk-TOYV2M4M.js +1 -0
  9. package/dist/chunk-UM3YF5EC.js +1 -0
  10. package/dist/columns.d.ts +1 -0
  11. package/dist/columns.js +1 -0
  12. package/dist/db-zHIHBm1E.d.ts +815 -0
  13. package/dist/db.d.ts +3 -0
  14. package/dist/db.js +1 -0
  15. package/dist/diff.d.ts +18 -0
  16. package/dist/diff.js +1 -0
  17. package/dist/index.d.ts +42 -0
  18. package/dist/index.js +1 -0
  19. package/dist/introspection.d.ts +7 -0
  20. package/dist/introspection.js +1 -0
  21. package/dist/operators-BVreW0ky.d.ts +719 -0
  22. package/dist/snapshot.d.ts +24 -0
  23. package/dist/snapshot.js +1 -0
  24. package/dist/table.d.ts +1 -0
  25. package/dist/table.js +1 -0
  26. package/dist/transaction-RE7LXTGV.js +1 -0
  27. package/package.json +73 -24
  28. package/src/builders/base.ts +53 -56
  29. package/src/builders/batch-get.ts +63 -58
  30. package/src/builders/batch-write.ts +81 -78
  31. package/src/builders/delete.ts +46 -53
  32. package/src/builders/insert.ts +158 -150
  33. package/src/builders/query-promise.ts +26 -35
  34. package/src/builders/relational-builder.ts +214 -191
  35. package/src/builders/select.ts +250 -238
  36. package/src/builders/transaction.ts +170 -151
  37. package/src/builders/update.ts +198 -191
  38. package/src/columns/binary-set.ts +29 -38
  39. package/src/columns/binary.ts +25 -35
  40. package/src/columns/boolean.ts +25 -30
  41. package/src/columns/date.ts +57 -64
  42. package/src/columns/index.ts +15 -15
  43. package/src/columns/json.ts +39 -48
  44. package/src/columns/list.ts +26 -36
  45. package/src/columns/map.ts +26 -34
  46. package/src/columns/number-set.ts +29 -38
  47. package/src/columns/number.ts +33 -40
  48. package/src/columns/string-set.ts +38 -47
  49. package/src/columns/string.ts +37 -49
  50. package/src/columns/uuid.ts +26 -33
  51. package/src/core/client.ts +9 -9
  52. package/src/core/column-builder.ts +194 -220
  53. package/src/core/column.ts +127 -135
  54. package/src/core/diff.ts +40 -34
  55. package/src/core/errors.ts +20 -17
  56. package/src/core/introspection.ts +62 -58
  57. package/src/core/operations.ts +17 -23
  58. package/src/core/parser.ts +82 -88
  59. package/src/core/relations.ts +165 -152
  60. package/src/core/retry.ts +52 -52
  61. package/src/core/snapshot.ts +131 -130
  62. package/src/core/strategies.ts +222 -214
  63. package/src/core/table.ts +189 -202
  64. package/src/core/validation.ts +52 -52
  65. package/src/db.ts +216 -213
  66. package/src/expressions/actions.ts +26 -26
  67. package/src/expressions/builder.ts +62 -54
  68. package/src/expressions/operators.ts +48 -48
  69. package/src/expressions/update-builder.ts +79 -75
  70. package/src/index.ts +2 -1
  71. package/src/indexes.ts +8 -8
  72. package/test/batch-resilience.test.ts +138 -0
  73. package/test/builders/delete.test.ts +100 -0
  74. package/test/builders/insert.test.ts +216 -0
  75. package/test/builders/relational-types.test.ts +55 -0
  76. package/test/builders/relational.integration.test.ts +291 -0
  77. package/test/builders/relational.test.ts +66 -0
  78. package/test/builders/select.test.ts +411 -0
  79. package/test/builders/transaction-errors.test.ts +46 -0
  80. package/test/builders/transaction-execution.test.ts +99 -0
  81. package/test/builders/transaction-proxy.test.ts +41 -0
  82. package/test/builders/update-expression.test.ts +106 -0
  83. package/test/builders/update.test.ts +179 -0
  84. package/test/core/diff.test.ts +152 -0
  85. package/test/core/expressions.test.ts +64 -0
  86. package/test/core/introspection.test.ts +47 -0
  87. package/test/core/parser.test.ts +69 -0
  88. package/test/core/snapshot-gen.test.ts +155 -0
  89. package/test/core/snapshot.test.ts +52 -0
  90. package/test/date-column.test.ts +159 -0
  91. package/test/fluent-writes.integration.test.ts +148 -0
  92. package/test/integration-retry.test.ts +77 -0
  93. package/test/integration.test.ts +105 -0
  94. package/test/item-size-error.test.ts +16 -0
  95. package/test/item-size-validation.test.ts +82 -0
  96. package/test/item-size.test.ts +47 -0
  97. package/test/iterator-pagination.integration.test.ts +132 -0
  98. package/test/jsdoc-builders.test.ts +55 -0
  99. package/test/jsdoc-schema.test.ts +107 -0
  100. package/test/json-column.test.ts +51 -0
  101. package/test/metadata.test.ts +54 -0
  102. package/test/mizzle-package.test.ts +20 -0
  103. package/test/relational-centralized.test.ts +83 -0
  104. package/test/relational-definition.test.ts +75 -0
  105. package/test/relational-init.test.ts +30 -0
  106. package/test/relational-proxy.test.ts +52 -0
  107. package/test/relations.test.ts +45 -0
  108. package/test/resilience-config.test.ts +34 -0
  109. package/test/retry-handler.test.ts +63 -0
  110. package/test/transaction.integration.test.ts +153 -0
  111. package/test/unified-select.integration.test.ts +153 -0
  112. package/test/unified-update.integration.test.ts +139 -0
  113. package/test/update.integration.test.ts +132 -0
  114. package/tsconfig.json +12 -9
  115. package/tsup.config.ts +11 -11
  116. package/vitest.config.ts +8 -0
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
3
+ import { dynamoTable, dynamoEntity } from "@aurios/mizzle/table";
4
+ import { mizzle } from "@aurios/mizzle/db";
5
+ import { string, uuid } from "@aurios/mizzle/columns";
6
+
7
+ // Mock DynamoDBDocumentClient.from to return a mock doc client
8
+ vi.mock("@aws-sdk/lib-dynamodb", async (importOriginal) => {
9
+ const original = await importOriginal<Record<string, unknown>>();
10
+ return {
11
+ ...original,
12
+ DynamoDBDocumentClient: {
13
+ from: vi.fn(() => ({
14
+ send: vi.fn(async () => ({ Items: [] })),
15
+ })),
16
+ },
17
+ };
18
+ });
19
+
20
+ const client = {
21
+ config: { protocol: "https:" },
22
+ send: vi.fn(),
23
+ } as unknown as DynamoDBClient;
24
+
25
+ const table = dynamoTable("mizzle-test", {
26
+ pk: string("pk"),
27
+ });
28
+
29
+ const users = dynamoEntity(table, "users", {
30
+ id: uuid("id"),
31
+ name: string("name"),
32
+ });
33
+
34
+ const db = mizzle({
35
+ client,
36
+ relations: {
37
+ users,
38
+ },
39
+ });
40
+
41
+ describe("RelationalQueryBuilder.findMany()", () => {
42
+ it("should fetch multiple records without relations", async () => {
43
+ // Since we don't have a real DB in this unit test without mocking,
44
+ // we'll at least verify the builder returns a promise and we can call findMany.
45
+ // Integration tests will verify actual data fetching.
46
+ const query = db.query.users.findMany();
47
+ expect(query).toBeInstanceOf(Promise);
48
+ });
49
+
50
+ it("should apply limit", async () => {
51
+ const query = db.query.users.findMany({ limit: 10 });
52
+ expect(query).toBeInstanceOf(Promise);
53
+ });
54
+
55
+ it("should apply where condition", async () => {
56
+ const query = db.query.users.findMany({
57
+ where: (cols, { eq }) => eq(cols.id, "123"),
58
+ });
59
+ expect(query).toBeInstanceOf(Promise);
60
+ });
61
+
62
+ it("should call findFirst", async () => {
63
+ const query = db.query.users.findFirst();
64
+ expect(query).toBeInstanceOf(Promise);
65
+ });
66
+ });
@@ -0,0 +1,411 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
+ import { dynamoTable, dynamoEntity } from "@aurios/mizzle/table";
3
+ import { string, uuid, number, boolean } from "@aurios/mizzle/columns";
4
+ import { prefixKey, staticKey } from "@aurios/mizzle";
5
+ import { DynamoDBClient, CreateTableCommand, DeleteTableCommand, DescribeTableCommand, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
6
+ import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
7
+ import { mizzle } from "@aurios/mizzle/db";
8
+ import { eq } from "@aurios/mizzle/expressions/operators";
9
+
10
+ const client = new DynamoDBClient({
11
+ endpoint: "http://localhost:8000",
12
+ region: "us-east-1",
13
+ credentials: {
14
+ accessKeyId: "local",
15
+ secretAccessKey: "local",
16
+ },
17
+ });
18
+
19
+ const docClient = DynamoDBDocumentClient.from(client);
20
+
21
+ describe("Select Command", () => {
22
+ const tableName = "TestTable_Select";
23
+ const table = dynamoTable(tableName, {
24
+ pk: string("pk"),
25
+ sk: string("sk"),
26
+ });
27
+
28
+ const user = dynamoEntity(
29
+ table,
30
+ "User",
31
+ {
32
+ id: uuid(),
33
+ name: string(),
34
+ age: number(),
35
+ active: boolean(),
36
+ role: string(),
37
+ },
38
+ (cols) => ({
39
+ pk: prefixKey("USER#", cols.id),
40
+ sk: staticKey("METADATA"),
41
+ }),
42
+ );
43
+
44
+ const testUser1 = {
45
+ pk: "USER#user1",
46
+ sk: "METADATA",
47
+ id: "user1",
48
+ name: "Alice",
49
+ age: 30,
50
+ active: true,
51
+ role: "admin",
52
+ };
53
+
54
+ const testUser2 = {
55
+ pk: "USER#user2",
56
+ sk: "METADATA",
57
+ id: "user2",
58
+ name: "Bob",
59
+ age: 25,
60
+ active: false,
61
+ role: "user",
62
+ };
63
+
64
+ beforeAll(async () => {
65
+ try {
66
+ await client.send(
67
+ new CreateTableCommand({
68
+ TableName: tableName,
69
+ KeySchema: [
70
+ { AttributeName: "pk", KeyType: "HASH" },
71
+ { AttributeName: "sk", KeyType: "RANGE" },
72
+ ],
73
+ AttributeDefinitions: [
74
+ { AttributeName: "pk", AttributeType: "S" },
75
+ { AttributeName: "sk", AttributeType: "S" },
76
+ ],
77
+ ProvisionedThroughput: {
78
+ ReadCapacityUnits: 5,
79
+ WriteCapacityUnits: 5,
80
+ },
81
+ }),
82
+ );
83
+
84
+ await docClient.send(new PutCommand({ TableName: tableName, Item: testUser1 }));
85
+ await docClient.send(new PutCommand({ TableName: tableName, Item: testUser2 }));
86
+ } catch {
87
+ // Table might already exist
88
+ }
89
+ });
90
+
91
+ afterAll(async () => {
92
+ try {
93
+ await client.send(new DeleteTableCommand({ TableName: tableName }));
94
+ } catch {
95
+ /* ignore */
96
+ }
97
+ });
98
+
99
+ it("should query by primary key", async () => {
100
+ const result = await mizzle(client).select().from(user).where(eq(user.id, "user1"));
101
+
102
+ expect(result).toHaveLength(1);
103
+ expect(result[0]).toMatchObject({
104
+ id: "user1",
105
+ name: "Alice",
106
+ });
107
+ });
108
+
109
+ it("should return empty array if no match found", async () => {
110
+ const result = await mizzle(client).select().from(user).where(eq(user.id, "non-existent"));
111
+
112
+ expect(result).toHaveLength(0);
113
+ });
114
+
115
+ it("should scan table if no keys provided", async () => {
116
+ // This should trigger a scan
117
+ const result = await mizzle(client).select().from(user).execute();
118
+
119
+ expect(result).toHaveLength(2);
120
+ expect(result).toEqual(
121
+ expect.arrayContaining([
122
+ expect.objectContaining({ id: "user1" }),
123
+ expect.objectContaining({ id: "user2" }),
124
+ ]),
125
+ );
126
+ });
127
+
128
+ // Note: To test Query with partial key, we need a table where SK is relevant for querying multiple items
129
+ // or use a GSI. Since our current schema uses specific IDs in PK, querying by PK usually yields one result.
130
+ // Let's create a GSI test case.
131
+ });
132
+
133
+ import { gsi } from "@aurios/mizzle/indexes";
134
+
135
+ describe("Select Command with GSI", () => {
136
+ const tableName = "TestTable_Select_GSI";
137
+
138
+ const table = dynamoTable(tableName, {
139
+ pk: string("pk"),
140
+ sk: string("sk"),
141
+ indexes: {
142
+ gsi1: gsi("gsi1pk", "gsi1sk"),
143
+ },
144
+ });
145
+
146
+ const user = dynamoEntity(
147
+ table,
148
+ "User",
149
+ {
150
+ id: uuid(),
151
+ role: string(),
152
+ name: string(),
153
+ createdAt: string(),
154
+ },
155
+ (cols) => ({
156
+ pk: prefixKey("USER#", cols.id),
157
+ sk: staticKey("METADATA"),
158
+ gsi1: {
159
+ pk: staticKey("ROLE"),
160
+ sk: prefixKey("", cols.role),
161
+ },
162
+ }),
163
+ );
164
+
165
+ beforeAll(async () => {
166
+ try {
167
+ await client.send(
168
+ new CreateTableCommand({
169
+ TableName: tableName,
170
+ KeySchema: [
171
+ { AttributeName: "pk", KeyType: "HASH" },
172
+ { AttributeName: "sk", KeyType: "RANGE" },
173
+ ],
174
+ AttributeDefinitions: [
175
+ { AttributeName: "pk", AttributeType: "S" },
176
+ { AttributeName: "sk", AttributeType: "S" },
177
+ { AttributeName: "gsi1pk", AttributeType: "S" },
178
+ { AttributeName: "gsi1sk", AttributeType: "S" },
179
+ ],
180
+ GlobalSecondaryIndexes: [
181
+ {
182
+ IndexName: "gsi1",
183
+ KeySchema: [
184
+ { AttributeName: "gsi1pk", KeyType: "HASH" },
185
+ { AttributeName: "gsi1sk", KeyType: "RANGE" },
186
+ ],
187
+ Projection: { ProjectionType: "ALL" },
188
+ ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 },
189
+ },
190
+ ],
191
+ ProvisionedThroughput: {
192
+ ReadCapacityUnits: 5,
193
+ WriteCapacityUnits: 5,
194
+ },
195
+ }),
196
+ );
197
+
198
+ // Wait for table to be active
199
+ let active = false;
200
+ while (!active) {
201
+ const { Table } = await client.send(new DescribeTableCommand({ TableName: tableName }));
202
+ if (Table?.TableStatus === "ACTIVE") active = true;
203
+ else await new Promise((r) => setTimeout(r, 100));
204
+ }
205
+
206
+ // Insert data
207
+ await docClient.send(
208
+ new PutCommand({
209
+ TableName: tableName,
210
+ Item: {
211
+ pk: "USER#1",
212
+ sk: "METADATA",
213
+ id: "1",
214
+ role: "admin",
215
+ name: "Alice",
216
+ gsi1pk: "ROLE",
217
+ gsi1sk: "admin",
218
+ },
219
+ }),
220
+ );
221
+ await docClient.send(
222
+ new PutCommand({
223
+ TableName: tableName,
224
+ Item: {
225
+ pk: "USER#2",
226
+ sk: "METADATA",
227
+ id: "2",
228
+ role: "admin",
229
+ name: "Bob",
230
+ gsi1pk: "ROLE",
231
+ gsi1sk: "admin",
232
+ },
233
+ }),
234
+ );
235
+ await docClient.send(
236
+ new PutCommand({
237
+ TableName: tableName,
238
+ Item: {
239
+ pk: "USER#3",
240
+ sk: "METADATA",
241
+ id: "3",
242
+ role: "user",
243
+ name: "Charlie",
244
+ gsi1pk: "ROLE",
245
+ gsi1sk: "user",
246
+ },
247
+ }),
248
+ );
249
+ } catch {
250
+ // Table might already exist
251
+ }
252
+ });
253
+
254
+ afterAll(async () => {
255
+ try {
256
+ await client.send(new DeleteTableCommand({ TableName: tableName }));
257
+ } catch {
258
+ /* ignore */
259
+ }
260
+ });
261
+
262
+ it("should query using GSI (static PK)", async () => {
263
+ // Query for all roles (since PK is static "ROLE")
264
+ // But our strategy says gsi1: { pk: staticKey("ROLE"), sk: cols.role }
265
+ // If we filter by something that resolves the PK, we can query.
266
+
267
+ // However, staticKey("ROLE") means the PK is *always* "ROLE".
268
+ // So any query on this entity that doesn't target main PK,
269
+ // if we can resolve "ROLE" (which is static), we should be able to query the index.
270
+
271
+ // Currently resolveStrategies logic requires us to 'find' the values in the where clause.
272
+ // For static key, it resolves immediately.
273
+
274
+ // So a query with NO where clause (or where clause that doesn't contradict)
275
+ // might resolve to this index if main PK is missing?
276
+
277
+ // Let's try querying where we filter by role="admin".
278
+ // This should provide the SK for the GSI, and PK is static.
279
+
280
+ const result = await mizzle(client).select().from(user).where(eq(user.role, "admin"));
281
+
282
+ expect(result).toHaveLength(2);
283
+ expect(result).toEqual(
284
+ expect.arrayContaining([
285
+ expect.objectContaining({ name: "Alice" }),
286
+ expect.objectContaining({ name: "Bob" }),
287
+ ]),
288
+ );
289
+ });
290
+ });
291
+
292
+ describe("Select Consistency and PageSize", () => {
293
+ const tableName = "TestTable_Consistency";
294
+ const table = dynamoTable(tableName, {
295
+ pk: string("pk"),
296
+ sk: string("sk"),
297
+ });
298
+
299
+ const user = dynamoEntity(
300
+ table,
301
+ "User",
302
+ {
303
+ id: uuid(),
304
+ name: string(),
305
+ },
306
+ (cols) => ({
307
+ pk: prefixKey("USER#", cols.id),
308
+ sk: staticKey("METADATA"),
309
+ }),
310
+ );
311
+
312
+ beforeAll(async () => {
313
+ try {
314
+ await client.send(
315
+ new CreateTableCommand({
316
+ TableName: tableName,
317
+ KeySchema: [
318
+ { AttributeName: "pk", KeyType: "HASH" },
319
+ { AttributeName: "sk", KeyType: "RANGE" },
320
+ ],
321
+ AttributeDefinitions: [
322
+ { AttributeName: "pk", AttributeType: "S" },
323
+ { AttributeName: "sk", AttributeType: "S" },
324
+ ],
325
+ ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 },
326
+ }),
327
+ );
328
+
329
+ // Wait for table to be active
330
+ let active = false;
331
+ while (!active) {
332
+ const { Table } = await client.send(new DescribeTableCommand({ TableName: tableName }));
333
+ if (Table?.TableStatus === "ACTIVE") active = true;
334
+ else await new Promise((r) => setTimeout(r, 100));
335
+ }
336
+
337
+ await docClient.send(
338
+ new PutCommand({
339
+ TableName: tableName,
340
+ Item: { pk: "USER#1", sk: "METADATA", id: "1", name: "Alice" },
341
+ }),
342
+ );
343
+ } catch {
344
+ /* ignore */
345
+ }
346
+ });
347
+
348
+ afterAll(async () => {
349
+ try {
350
+ await client.send(new DeleteTableCommand({ TableName: tableName }));
351
+ } catch {
352
+ /* ignore */
353
+ }
354
+ });
355
+
356
+ it("should allow consistentRead on Get", async () => {
357
+ const result = await mizzle(client)
358
+ .select()
359
+ .from(user)
360
+ .where(eq(user.id, "1"))
361
+ .consistentRead()
362
+ .execute();
363
+ expect(result).toHaveLength(1);
364
+ });
365
+
366
+ it("should allow consistentRead on Query", async () => {
367
+ const result = await mizzle(client)
368
+ .select()
369
+ .from(user)
370
+ .where(eq(user.id, "1"))
371
+ .consistentRead()
372
+ .execute();
373
+ expect(result).toHaveLength(1);
374
+ });
375
+
376
+ it("should allow consistentRead on Scan", async () => {
377
+ const result = await mizzle(client).select().from(user).consistentRead().execute();
378
+ expect(result).toHaveLength(1);
379
+ });
380
+
381
+ it("should respect pageSize hint", async () => {
382
+ const result = await mizzle(client).select().from(user).pageSize(1).execute();
383
+ expect(result).toHaveLength(1);
384
+ });
385
+
386
+ it("should yield items via iterator", async () => {
387
+ const iterator = mizzle(client).select().from(user).iterator();
388
+ const items = [];
389
+ for await (const item of iterator) {
390
+ items.push(item);
391
+ }
392
+ expect(items).toHaveLength(1);
393
+ });
394
+
395
+ it("should respect limit in iterator", async () => {
396
+ // Insert one more item to test limit
397
+ await docClient.send(
398
+ new PutCommand({
399
+ TableName: tableName,
400
+ Item: { pk: "USER#2", sk: "METADATA", id: "2", name: "Bob" },
401
+ }),
402
+ );
403
+
404
+ const iterator = mizzle(client).select().from(user).limit(1).iterator();
405
+ const items = [];
406
+ for await (const item of iterator) {
407
+ items.push(item);
408
+ }
409
+ expect(items).toHaveLength(1);
410
+ });
411
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { TransactionExecutor } from "@aurios/mizzle/builders/transaction";
3
+ import { TransactionFailedError } from "@aurios/mizzle/core/errors";
4
+ import type { IMizzleClient } from "@aurios/mizzle/core/client";
5
+
6
+ describe("Transaction Error Parsing", () => {
7
+ const mockClient = { send: vi.fn() } as unknown as IMizzleClient;
8
+
9
+ it("should parse TransactionCanceledException into TransactionFailedError", async () => {
10
+ const executor = new TransactionExecutor(mockClient);
11
+
12
+ const error = new Error("Transaction Canceled") as Error & {
13
+ name: string;
14
+ CancellationReasons: { Code: string; Message?: string }[];
15
+ };
16
+ error.name = "TransactionCanceledException";
17
+ error.CancellationReasons = [
18
+ { Code: "None" },
19
+ { Code: "ConditionalCheckFailed", Message: "Condition failed" },
20
+ ];
21
+
22
+ vi.mocked(mockClient.send).mockRejectedValueOnce(error);
23
+
24
+ try {
25
+ await executor.execute("token", []);
26
+ expect.fail("Should have thrown TransactionFailedError");
27
+ } catch (e) {
28
+ expect(e).toBeInstanceOf(TransactionFailedError);
29
+ const txErr = e as TransactionFailedError;
30
+ expect(txErr.reasons).toHaveLength(1);
31
+ expect(txErr.reasons[0]).toMatchObject({
32
+ index: 1,
33
+ code: "ConditionalCheckFailed",
34
+ message: "Condition failed",
35
+ });
36
+ }
37
+ });
38
+
39
+ it("should rethrow other errors", async () => {
40
+ const executor = new TransactionExecutor(mockClient);
41
+ const otherError = new Error("Other error");
42
+ vi.mocked(mockClient.send).mockRejectedValueOnce(otherError);
43
+
44
+ await expect(executor.execute("token", [])).rejects.toThrow("Other error");
45
+ });
46
+ });
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import {
3
+ TransactionExecutor,
4
+ ConditionCheckBuilder,
5
+ } from "@aurios/mizzle/builders/transaction";
6
+ import { dynamoTable, dynamoEntity } from "@aurios/mizzle/table";
7
+ import { string, number } from "@aurios/mizzle/columns";
8
+ import type { IMizzleClient } from "@aurios/mizzle/core/client";
9
+ import { mizzle } from "@aurios/mizzle/db";
10
+ import { eq, and } from "@aurios/mizzle/expressions/operators";
11
+ import { add, prefixKey } from "@aurios/mizzle";
12
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
13
+
14
+ describe("TransactionExecution Mapping", () => {
15
+ const table = dynamoTable("TestTable", { pk: string("pk"), sk: string("sk") });
16
+ const user = dynamoEntity(
17
+ table,
18
+ "User",
19
+ { id: string(), name: string(), age: number() },
20
+ (cols) => ({
21
+ pk: prefixKey("U#", cols.id),
22
+ sk: prefixKey("M#", cols.name),
23
+ }),
24
+ );
25
+
26
+ const rawClient = new DynamoDBClient({ region: "us-east-1" });
27
+ const mockClient = { send: vi.fn() } as any;
28
+ const db = mizzle(rawClient);
29
+ (db as any).docClient = mockClient;
30
+
31
+ it("should map InsertBase to Put item", async () => {
32
+ const executor = new TransactionExecutor(mockClient);
33
+ const op = db.insert(user).values({ id: "1", name: "Luke", age: 30 });
34
+
35
+ const mapped = (executor as any).mapToTransactItem(op);
36
+
37
+ expect(mapped).toMatchObject({
38
+ Put: {
39
+ TableName: "TestTable",
40
+ Item: {
41
+ pk: "U#1",
42
+ sk: "M#Luke",
43
+ id: "1",
44
+ name: "Luke",
45
+ age: 30,
46
+ },
47
+ },
48
+ });
49
+ });
50
+
51
+ it("should map UpdateBuilder to Update item", async () => {
52
+ const executor = new TransactionExecutor(mockClient);
53
+ // Both SET and ADD
54
+ const op = db
55
+ .update(user)
56
+ .set({ name: "Luke Skywalker", age: add(1) })
57
+ .where(and(eq(user.id, "1"), eq(user.name, "Luke")));
58
+
59
+ const mapped = (executor as any).mapToTransactItem(op);
60
+
61
+ expect(mapped.Update).toMatchObject({
62
+ TableName: "TestTable",
63
+ Key: { pk: "U#1", sk: "M#Luke" },
64
+ });
65
+ expect(mapped.Update.UpdateExpression).toContain("SET");
66
+ expect(mapped.Update.UpdateExpression).toContain("ADD");
67
+ });
68
+
69
+ it("should map DeleteBuilder to Delete item", async () => {
70
+ const executor = new TransactionExecutor(mockClient);
71
+ const op = db.delete(user, { id: "1", name: "Luke" });
72
+
73
+ const mapped = (executor as any).mapToTransactItem(op);
74
+
75
+ expect(mapped).toMatchObject({
76
+ Delete: {
77
+ TableName: "TestTable",
78
+ Key: { pk: "U#1", sk: "M#Luke" },
79
+ },
80
+ });
81
+ });
82
+
83
+ it("should map ConditionCheckBuilder to ConditionCheck item", async () => {
84
+ const executor = new TransactionExecutor(mockClient);
85
+ const op = new ConditionCheckBuilder(user, mockClient).where(
86
+ and(eq(user.id, "1"), eq(user.name, "Luke")),
87
+ );
88
+
89
+ const mapped = (executor as any).mapToTransactItem(op);
90
+
91
+ expect(mapped).toMatchObject({
92
+ ConditionCheck: {
93
+ TableName: "TestTable",
94
+ Key: { pk: "U#1", sk: "M#Luke" },
95
+ },
96
+ });
97
+ expect(mapped.ConditionCheck.ConditionExpression).toBeDefined();
98
+ });
99
+ });
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import {
3
+ TransactionProxy,
4
+ ConditionCheckBuilder,
5
+ } from "@aurios/mizzle/builders/transaction";
6
+ import { InsertBuilder } from "@aurios/mizzle/builders/insert";
7
+ import { UpdateBuilder } from "@aurios/mizzle/builders/update";
8
+ import { DeleteBuilder } from "@aurios/mizzle/builders/delete";
9
+ import { dynamoTable, dynamoEntity } from "@aurios/mizzle/table";
10
+ import { string } from "@aurios/mizzle/columns";
11
+ import type { IMizzleClient } from "@aurios/mizzle/core/client";
12
+
13
+ describe("TransactionProxy", () => {
14
+ const table = dynamoTable("TestTable", { pk: string("pk") });
15
+ const user = dynamoEntity(table, "User", { name: string() }, (cols) => ({ pk: cols.name }));
16
+ const mockClient = { send: vi.fn() } as unknown as IMizzleClient;
17
+
18
+ it("should return an InsertBuilder", () => {
19
+ const tx = new TransactionProxy(mockClient);
20
+ const builder = tx.insert(user);
21
+ expect(builder).toBeInstanceOf(InsertBuilder);
22
+ });
23
+
24
+ it("should return an UpdateBuilder", () => {
25
+ const tx = new TransactionProxy(mockClient);
26
+ const builder = tx.update(user);
27
+ expect(builder).toBeInstanceOf(UpdateBuilder);
28
+ });
29
+
30
+ it("should return a DeleteBuilder", () => {
31
+ const tx = new TransactionProxy(mockClient);
32
+ const builder = tx.delete(user, { name: "test" });
33
+ expect(builder).toBeInstanceOf(DeleteBuilder);
34
+ });
35
+
36
+ it("should return a ConditionCheckBuilder", () => {
37
+ const tx = new TransactionProxy(mockClient);
38
+ const builder = tx.conditionCheck(user);
39
+ expect(builder).toBeInstanceOf(ConditionCheckBuilder);
40
+ });
41
+ });