@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
@@ -1,13 +1,13 @@
1
1
  import {
2
- type Expression,
3
- BinaryExpression,
4
- LogicalExpression,
5
- FunctionExpression,
2
+ type Expression,
3
+ BinaryExpression,
4
+ LogicalExpression,
5
+ FunctionExpression,
6
6
  } from "./operators";
7
7
 
8
8
  /**
9
9
  * Recursively builds a DynamoDB expression string from an Expression object.
10
- *
10
+ *
11
11
  * @param cond The expression object to build.
12
12
  * @param addName A callback to add an attribute name and return its placeholder.
13
13
  * @param addValue A callback to add an attribute value and return its placeholder.
@@ -15,63 +15,71 @@ import {
15
15
  * @returns A string suitable for use in ConditionExpression or FilterExpression.
16
16
  */
17
17
  export function buildExpression(
18
- cond: Expression,
19
- addName: (name: string) => string,
20
- addValue: (value: unknown) => string,
21
- excludeColumns?: Set<string>,
18
+ cond: Expression,
19
+ addName: (name: string) => string,
20
+ addValue: (value: unknown) => string,
21
+ excludeColumns?: Set<string>,
22
22
  ): string | undefined {
23
- if (cond instanceof LogicalExpression) {
24
- const parts = cond.conditions
25
- .map((c) => buildExpression(c, addName, addValue, excludeColumns))
26
- .filter((p): p is string => p !== undefined && p !== "");
27
-
28
- if (parts.length === 0) return undefined;
29
- if (parts.length === 1) return parts[0];
30
- return `(${parts.join(` ${cond.operator} `)})`;
31
- }
23
+ if (cond instanceof LogicalExpression) {
24
+ const parts = cond.conditions
25
+ .map((c) => buildExpression(c, addName, addValue, excludeColumns))
26
+ .filter((p): p is string => p !== undefined && p !== "");
32
27
 
33
- if (cond instanceof BinaryExpression) {
34
- if (excludeColumns?.has(cond.column.name)) {
35
- return undefined;
36
- }
37
- const colName = addName(cond.column.name);
38
- const mapValue = (v: unknown) => typeof (cond.column as any).mapToDynamoValue === "function"
39
- ? (cond.column as any).mapToDynamoValue(v)
40
- : v;
28
+ if (parts.length === 0) return undefined;
29
+ if (parts.length === 1) return parts[0];
30
+ return `(${parts.join(` ${cond.operator} `)})`;
31
+ }
41
32
 
42
- if (cond.operator === "between") {
43
- const valArray = cond.value as unknown[];
44
- const valKey1 = addValue(mapValue(valArray[0]));
45
- const valKey2 = addValue(mapValue(valArray[1]));
46
- return `${colName} BETWEEN ${valKey1} AND ${valKey2}`;
47
- }
33
+ if (cond instanceof BinaryExpression) {
34
+ if (excludeColumns?.has(cond.column.name)) {
35
+ return undefined;
36
+ }
37
+ const colName = addName(cond.column.name);
38
+ const mapValue = (v: unknown) =>
39
+ typeof (cond.column as unknown as { mapToDynamoValue: Function }).mapToDynamoValue ===
40
+ "function"
41
+ ? (
42
+ cond.column as unknown as { mapToDynamoValue: (v: unknown) => unknown }
43
+ ).mapToDynamoValue(v)
44
+ : v;
48
45
 
49
- if (cond.operator === "in") {
50
- const valArray = cond.value as unknown[];
51
- const valKeys = valArray.map((val) => addValue(mapValue(val)));
52
- return `${colName} IN (${valKeys.join(", ")})`;
53
- }
46
+ if (cond.operator === "between") {
47
+ const valArray = cond.value as unknown[];
48
+ const valKey1 = addValue(mapValue(valArray[0]));
49
+ const valKey2 = addValue(mapValue(valArray[1]));
50
+ return `${colName} BETWEEN ${valKey1} AND ${valKey2}`;
51
+ }
54
52
 
55
- const valKey = addValue(mapValue(cond.value));
56
- return `${colName} ${cond.operator} ${valKey}`;
53
+ if (cond.operator === "in") {
54
+ const valArray = cond.value as unknown[];
55
+ const valKeys = valArray.map((val) => addValue(mapValue(val)));
56
+ return `${colName} IN (${valKeys.join(", ")})`;
57
57
  }
58
58
 
59
- if (cond instanceof FunctionExpression) {
60
- if (excludeColumns?.has(cond.column.name)) {
61
- return undefined;
62
- }
63
- const colName = addName(cond.column.name);
64
- const mapValue = (v: unknown) => typeof (cond.column as any).mapToDynamoValue === "function"
65
- ? (cond.column as any).mapToDynamoValue(v)
66
- : v;
59
+ const valKey = addValue(mapValue(cond.value));
60
+ return `${colName} ${cond.operator} ${valKey}`;
61
+ }
67
62
 
68
- if (cond.operator === "attribute_exists") {
69
- return `attribute_exists(${colName})`;
70
- }
63
+ if (cond instanceof FunctionExpression) {
64
+ if (excludeColumns?.has(cond.column.name)) {
65
+ return undefined;
66
+ }
67
+ const colName = addName(cond.column.name);
68
+ const mapValue = (v: unknown) =>
69
+ typeof (cond.column as unknown as { mapToDynamoValue: Function }).mapToDynamoValue ===
70
+ "function"
71
+ ? (
72
+ cond.column as unknown as { mapToDynamoValue: (v: unknown) => unknown }
73
+ ).mapToDynamoValue(v)
74
+ : v;
71
75
 
72
- const valKey = addValue(mapValue(cond.value));
73
- return `${cond.operator}(${colName}, ${valKey})`;
76
+ if (cond.operator === "attribute_exists") {
77
+ return `attribute_exists(${colName})`;
74
78
  }
75
79
 
76
- return undefined;
77
- }
80
+ const valKey = addValue(mapValue(cond.value));
81
+ return `${cond.operator}(${colName}, ${valKey})`;
82
+ }
83
+
84
+ return undefined;
85
+ }
@@ -5,102 +5,102 @@ type LogicalOperators = "AND" | "OR";
5
5
  type FunctionOperators = "begins_with" | "contains" | "attribute_exists";
6
6
 
7
7
  export abstract class Expression {
8
- abstract readonly type: "binary" | "logical" | "function";
8
+ abstract readonly type: "binary" | "logical" | "function";
9
9
  }
10
10
 
11
11
  export class BinaryExpression extends Expression {
12
- readonly type = "binary";
13
- constructor(
14
- readonly column: Column,
15
- readonly operator: BinaryOperators,
16
- readonly value: unknown,
17
- ) {
18
- super();
19
- }
12
+ readonly type = "binary";
13
+ constructor(
14
+ readonly column: Column,
15
+ readonly operator: BinaryOperators,
16
+ readonly value: unknown,
17
+ ) {
18
+ super();
19
+ }
20
20
  }
21
21
 
22
22
  export class LogicalExpression extends Expression {
23
- readonly type = "logical";
24
- constructor(
25
- readonly conditions: Expression[],
26
- readonly operator: LogicalOperators,
27
- ) {
28
- super();
29
- }
23
+ readonly type = "logical";
24
+ constructor(
25
+ readonly conditions: Expression[],
26
+ readonly operator: LogicalOperators,
27
+ ) {
28
+ super();
29
+ }
30
30
  }
31
31
 
32
32
  export class FunctionExpression extends Expression {
33
- readonly type = "function";
34
- constructor(
35
- readonly column: Column,
36
- readonly operator: FunctionOperators,
37
- readonly value: unknown,
38
- ) {
39
- super();
40
- }
33
+ readonly type = "function";
34
+ constructor(
35
+ readonly column: Column,
36
+ readonly operator: FunctionOperators,
37
+ readonly value: unknown,
38
+ ) {
39
+ super();
40
+ }
41
41
  }
42
42
 
43
43
  export function eq(column: Column, value: unknown) {
44
- return new BinaryExpression(column, "=", value);
44
+ return new BinaryExpression(column, "=", value);
45
45
  }
46
46
 
47
47
  export function gt(column: Column, value: unknown) {
48
- return new BinaryExpression(column, ">", value);
48
+ return new BinaryExpression(column, ">", value);
49
49
  }
50
50
 
51
51
  export function gte(column: Column, value: unknown) {
52
- return new BinaryExpression(column, ">=", value);
52
+ return new BinaryExpression(column, ">=", value);
53
53
  }
54
54
 
55
55
  export function lt(column: Column, value: unknown) {
56
- return new BinaryExpression(column, "<", value);
56
+ return new BinaryExpression(column, "<", value);
57
57
  }
58
58
 
59
59
  export function lte(column: Column, value: unknown) {
60
- return new BinaryExpression(column, "<=", value);
60
+ return new BinaryExpression(column, "<=", value);
61
61
  }
62
62
 
63
63
  export function between(column: Column, values: [unknown, unknown]) {
64
- return new BinaryExpression(column, "between", values);
64
+ return new BinaryExpression(column, "between", values);
65
65
  }
66
66
 
67
67
  export function inList(column: Column, values: unknown[]) {
68
- return new BinaryExpression(column, "in", values);
68
+ return new BinaryExpression(column, "in", values);
69
69
  }
70
70
 
71
71
  export function beginsWith(column: Column, value: string) {
72
- return new FunctionExpression(column, "begins_with", value);
72
+ return new FunctionExpression(column, "begins_with", value);
73
73
  }
74
74
 
75
75
  export function contains(column: Column, value: unknown) {
76
- return new FunctionExpression(column, "contains", value);
76
+ return new FunctionExpression(column, "contains", value);
77
77
  }
78
78
 
79
79
  export function attributeExists(column: Column) {
80
- return new FunctionExpression(column, "attribute_exists", undefined);
80
+ return new FunctionExpression(column, "attribute_exists", undefined);
81
81
  }
82
82
 
83
83
  export function or(...conditions: Expression[]) {
84
- return new LogicalExpression(conditions, "OR");
84
+ return new LogicalExpression(conditions, "OR");
85
85
  }
86
86
 
87
87
  export function and(...conditions: Expression[]) {
88
- return new LogicalExpression(conditions, "AND");
88
+ return new LogicalExpression(conditions, "AND");
89
89
  }
90
90
 
91
91
  export const operators = {
92
- eq,
93
- gt,
94
- gte,
95
- lt,
96
- lte,
97
- between,
98
- in: inList,
99
- and,
100
- or,
101
- contains,
102
- beginsWith,
103
- attributeExists,
92
+ eq,
93
+ gt,
94
+ gte,
95
+ lt,
96
+ lte,
97
+ between,
98
+ in: inList,
99
+ and,
100
+ or,
101
+ contains,
102
+ beginsWith,
103
+ attributeExists,
104
104
  };
105
105
 
106
106
  export type Operators = typeof operators;
@@ -1,98 +1,102 @@
1
1
  import { type Column } from "../core/column";
2
- import { UpdateAction } from "./actions";
2
+ import { UpdateAction, SetAction, AddAction, DeleteAction } from "./actions";
3
3
 
4
4
  export interface UpdateState {
5
- set: Record<string, { value: unknown; functionName?: string; usePathAsFirstArg?: boolean }>;
6
- add: Record<string, unknown>;
7
- remove: string[];
8
- delete: Record<string, unknown>;
5
+ set: Record<string, { value: unknown; functionName?: string; usePathAsFirstArg?: boolean }>;
6
+ add: Record<string, unknown>;
7
+ remove: string[];
8
+ delete: Record<string, unknown>;
9
9
  }
10
10
 
11
11
  export function createUpdateState(): UpdateState {
12
- return {
13
- set: {},
14
- add: {},
15
- remove: [],
16
- delete: {},
17
- };
12
+ return {
13
+ set: {},
14
+ add: {},
15
+ remove: [],
16
+ delete: {},
17
+ };
18
18
  }
19
19
 
20
20
  export function partitionUpdateValues(
21
- values: Record<string, unknown | UpdateAction>,
22
- state: UpdateState,
23
- columns?: Record<string, Column>
21
+ values: Record<string, unknown | UpdateAction>,
22
+ state: UpdateState,
23
+ columns?: Record<string, Column>,
24
24
  ): void {
25
- for (const [key, val] of Object.entries(values)) {
26
- const col = columns?.[key];
27
- const mapValue = (v: unknown) => (col && typeof (col as any).mapToDynamoValue === "function")
28
- ? (col as any).mapToDynamoValue(v)
29
- : v;
25
+ for (const [key, val] of Object.entries(values)) {
26
+ const col = columns?.[key];
27
+ const mapValue = (v: unknown) =>
28
+ col &&
29
+ typeof (col as unknown as { mapToDynamoValue: Function }).mapToDynamoValue === "function"
30
+ ? (col as unknown as { mapToDynamoValue: (v: unknown) => unknown }).mapToDynamoValue(v)
31
+ : v;
30
32
 
31
- if (val instanceof UpdateAction) {
32
- switch (val.action) {
33
- case "SET":
34
- state.set[key] = {
35
- value: mapValue((val as any).value),
36
- functionName: (val as any).functionName,
37
- usePathAsFirstArg: (val as any).usePathAsFirstArg,
38
- };
39
- break;
40
- case "ADD":
41
- state.add[key] = mapValue((val as any).value);
42
- break;
43
- case "REMOVE":
44
- state.remove.push(key);
45
- break;
46
- case "DELETE":
47
- state.delete[key] = mapValue((val as any).value);
48
- break;
49
- }
50
- } else if (val !== undefined) {
51
- state.set[key] = { value: mapValue(val) };
33
+ if (val instanceof UpdateAction) {
34
+ switch (val.action) {
35
+ case "SET": {
36
+ const setVal = val as SetAction;
37
+ state.set[key] = {
38
+ value: mapValue(setVal.value),
39
+ functionName: setVal.functionName,
40
+ usePathAsFirstArg: setVal.usePathAsFirstArg,
41
+ };
42
+ break;
52
43
  }
44
+ case "ADD":
45
+ state.add[key] = mapValue((val as AddAction).value);
46
+ break;
47
+ case "REMOVE":
48
+ state.remove.push(key);
49
+ break;
50
+ case "DELETE":
51
+ state.delete[key] = mapValue((val as DeleteAction).value);
52
+ break;
53
+ }
54
+ } else if (val !== undefined) {
55
+ state.set[key] = { value: mapValue(val) };
53
56
  }
57
+ }
54
58
  }
55
59
 
56
60
  export function buildUpdateExpressionString(
57
- state: UpdateState,
58
- addName: (name: string) => string,
59
- addValue: (value: unknown) => string
61
+ state: UpdateState,
62
+ addName: (name: string) => string,
63
+ addValue: (value: unknown) => string,
60
64
  ): string {
61
- const sections: string[] = [];
65
+ const sections: string[] = [];
62
66
 
63
- if (Object.keys(state.set).length > 0) {
64
- const parts = Object.entries(state.set).map(([key, config]) => {
65
- const name = addName(key);
66
- const value = addValue(config.value);
67
- if (config.functionName) {
68
- if (config.usePathAsFirstArg) {
69
- return `${name} = ${config.functionName}(${name}, ${value})`;
70
- }
71
- return `${name} = ${config.functionName}(${value})`;
72
- }
73
- return `${name} = ${value}`;
74
- });
75
- sections.push(`SET ${parts.join(", ")}`);
76
- }
67
+ if (Object.keys(state.set).length > 0) {
68
+ const parts = Object.entries(state.set).map(([key, config]) => {
69
+ const name = addName(key);
70
+ const value = addValue(config.value);
71
+ if (config.functionName) {
72
+ if (config.usePathAsFirstArg) {
73
+ return `${name} = ${config.functionName}(${name}, ${value})`;
74
+ }
75
+ return `${name} = ${config.functionName}(${value})`;
76
+ }
77
+ return `${name} = ${value}`;
78
+ });
79
+ sections.push(`SET ${parts.join(", ")}`);
80
+ }
77
81
 
78
- if (Object.keys(state.add).length > 0) {
79
- const parts = Object.entries(state.add).map(([key, val]) => {
80
- return `${addName(key)} ${addValue(val)}`;
81
- });
82
- sections.push(`ADD ${parts.join(", ")}`);
83
- }
82
+ if (Object.keys(state.add).length > 0) {
83
+ const parts = Object.entries(state.add).map(([key, val]) => {
84
+ return `${addName(key)} ${addValue(val)}`;
85
+ });
86
+ sections.push(`ADD ${parts.join(", ")}`);
87
+ }
84
88
 
85
- if (state.remove.length > 0) {
86
- const parts = state.remove.map((key) => addName(key));
87
- sections.push(`REMOVE ${parts.join(", ")}`);
88
- }
89
+ if (state.remove.length > 0) {
90
+ const parts = state.remove.map((key) => addName(key));
91
+ sections.push(`REMOVE ${parts.join(", ")}`);
92
+ }
89
93
 
90
- if (Object.keys(state.delete).length > 0) {
91
- const parts = Object.entries(state.delete).map(([key, val]) => {
92
- return `${addName(key)} ${addValue(val)}`;
93
- });
94
- sections.push(`DELETE ${parts.join(", ")}`);
95
- }
94
+ if (Object.keys(state.delete).length > 0) {
95
+ const parts = Object.entries(state.delete).map(([key, val]) => {
96
+ return `${addName(key)} ${addValue(val)}`;
97
+ });
98
+ sections.push(`DELETE ${parts.join(", ")}`);
99
+ }
96
100
 
97
- return sections.join(" ");
101
+ return sections.join(" ");
98
102
  }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from "@mizzle/shared";
1
+ export * from "@repo/shared";
2
2
  export * from "./expressions/operators";
3
3
  export * from "./expressions/builder";
4
4
  export * from "./expressions/actions";
@@ -13,6 +13,7 @@ export * from "./builders/relational-builder";
13
13
  export * from "./core/table";
14
14
  export * from "./core/strategies";
15
15
  export * from "./core/relations";
16
+ export * from "./core/diff";
16
17
  export * from "./core/retry";
17
18
  export * from "./core/errors";
18
19
  export * from "./columns";
package/src/indexes.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  export interface IndexColumnConfig {
2
- pk?: string;
3
- sk?: string;
2
+ pk?: string;
3
+ sk?: string;
4
4
  }
5
5
 
6
6
  export class IndexBuilder {
7
- constructor(
8
- public type: "gsi" | "lsi",
9
- public config: IndexColumnConfig,
10
- ) {}
7
+ constructor(
8
+ public type: "gsi" | "lsi",
9
+ public config: IndexColumnConfig,
10
+ ) {}
11
11
  }
12
12
 
13
13
  export function gsi(pkColumn: string, skColumn?: string) {
14
- return new IndexBuilder("gsi", { pk: pkColumn, sk: skColumn });
14
+ return new IndexBuilder("gsi", { pk: pkColumn, sk: skColumn });
15
15
  }
16
16
 
17
17
  export function lsi(skColumn: string) {
18
- return new IndexBuilder("lsi", { sk: skColumn });
18
+ return new IndexBuilder("lsi", { sk: skColumn });
19
19
  }
@@ -0,0 +1,138 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { mizzle } from "@aurios/mizzle";
3
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
4
+ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
5
+ import { ENTITY_SYMBOLS, TABLE_SYMBOLS } from "@repo/shared";
6
+
7
+ // Mock send method
8
+ const mockSend = vi.fn();
9
+ const mockDocClient = {
10
+ send: mockSend,
11
+ middlewareStack: {
12
+ add: vi.fn(),
13
+ remove: vi.fn(),
14
+ },
15
+ };
16
+
17
+ describe("Batch Resilience", () => {
18
+ const mockTable = {
19
+ [ENTITY_SYMBOLS.PHYSICAL_TABLE]: {
20
+ [TABLE_SYMBOLS.TABLE_NAME]: "test-table",
21
+ [TABLE_SYMBOLS.PARTITION_KEY]: { name: "pk" },
22
+ [TABLE_SYMBOLS.SORT_KEY]: undefined,
23
+ [TABLE_SYMBOLS.INDEXES]: {},
24
+ },
25
+ [ENTITY_SYMBOLS.ENTITY_STRATEGY]: { pk: { type: "static", segments: ["test"] } },
26
+ [ENTITY_SYMBOLS.COLUMNS]: {
27
+ pk: { name: "pk" },
28
+ name: { name: "name" },
29
+ },
30
+ } as any;
31
+
32
+ beforeEach(() => {
33
+ vi.spyOn(DynamoDBDocumentClient, "from").mockReturnValue(mockDocClient as any);
34
+ mockSend.mockReset();
35
+ });
36
+
37
+ afterEach(() => {
38
+ vi.restoreAllMocks();
39
+ });
40
+
41
+ it("should retry unprocessed keys in batchGet", async () => {
42
+ const client = new DynamoDBClient({});
43
+ const db = mizzle(client);
44
+
45
+ // 1st call: returns 1 item and 1 unprocessed key
46
+ mockSend.mockResolvedValueOnce({
47
+ Responses: {
48
+ "test-table": [{ pk: "test1", name: "Item 1" }],
49
+ },
50
+ UnprocessedKeys: {
51
+ "test-table": {
52
+ Keys: [{ pk: "test2" }],
53
+ },
54
+ },
55
+ });
56
+
57
+ // 2nd call: returns the 2nd item
58
+ mockSend.mockResolvedValueOnce({
59
+ Responses: {
60
+ "test-table": [{ pk: "test2", name: "Item 2" }],
61
+ },
62
+ });
63
+
64
+ const result = await db.batchGet(mockTable, [{ pk: "test1" }, { pk: "test2" }]).execute();
65
+
66
+ expect(result.succeeded[0]!.name).toBe("Item 1");
67
+ expect(result.succeeded[1]!.name).toBe("Item 2");
68
+ });
69
+
70
+ it("should retry unprocessed items in batchWrite", async () => {
71
+ const client = new DynamoDBClient({});
72
+ const db = mizzle(client);
73
+
74
+ // 1st call: 1 unprocessed item
75
+ mockSend.mockResolvedValueOnce({
76
+ UnprocessedItems: {
77
+ "test-table": [{ PutRequest: { Item: { pk: "test2", name: "Item 2" } } }],
78
+ },
79
+ });
80
+
81
+ // 2nd call: success
82
+ mockSend.mockResolvedValueOnce({
83
+ UnprocessedItems: {},
84
+ });
85
+
86
+ const result = await db
87
+ .batchWrite(mockTable, [
88
+ { type: "put", item: { pk: "test1", name: "Item 1" } },
89
+ { type: "put", item: { pk: "test2", name: "Item 2" } },
90
+ ])
91
+ .execute();
92
+
93
+ expect(result.succeededCount).toBe(2);
94
+ expect(mockSend).toHaveBeenCalledTimes(2);
95
+ });
96
+
97
+ it("should return failed keys after max attempts in batchGet", async () => {
98
+ const client = new DynamoDBClient({});
99
+ const db = mizzle(client);
100
+
101
+ // Always returns unprocessed
102
+ mockSend.mockResolvedValue({
103
+ UnprocessedKeys: {
104
+ "test-table": {
105
+ Keys: [{ pk: "test1" }],
106
+ },
107
+ },
108
+ });
109
+
110
+ const result = await db.batchGet(mockTable, [{ pk: "test1" }]).execute();
111
+
112
+ expect(result.succeeded).toHaveLength(0);
113
+ expect(result.failed).toHaveLength(1);
114
+ expect(result.failed[0]).toEqual({ pk: "test1" });
115
+ expect(mockSend).toHaveBeenCalledTimes(5); // Internal limit
116
+ });
117
+
118
+ it("should return failed items after max attempts in batchWrite", async () => {
119
+ const client = new DynamoDBClient({});
120
+ const db = mizzle(client);
121
+
122
+ mockSend.mockResolvedValue({
123
+ UnprocessedItems: {
124
+ "test-table": [{ PutRequest: { Item: { pk: "test1" } } }],
125
+ },
126
+ });
127
+
128
+ const result = await db
129
+ .batchWrite(mockTable, [{ type: "put", item: { pk: "test1" } }])
130
+ .execute();
131
+ const res = result as any;
132
+
133
+ expect(res.succeededCount).toBe(0);
134
+ expect(res.failed).toHaveLength(1);
135
+ expect(res.failed[0].PutRequest.Item.pk).toBe("test1");
136
+ expect(mockSend).toHaveBeenCalledTimes(5);
137
+ });
138
+ });