@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.
- package/.turbo/turbo-build.log +37 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/chunk-AQVECMXP.js +1 -0
- package/dist/chunk-DU7UPWBW.js +1 -0
- package/dist/chunk-GPYZK4WY.js +1 -0
- package/dist/chunk-NPPZW6VT.js +1 -0
- package/dist/chunk-TOYV2M4M.js +1 -0
- package/dist/chunk-UM3YF5EC.js +1 -0
- package/dist/columns.d.ts +1 -0
- package/dist/columns.js +1 -0
- package/dist/db-zHIHBm1E.d.ts +815 -0
- package/dist/db.d.ts +3 -0
- package/dist/db.js +1 -0
- package/dist/diff.d.ts +18 -0
- package/dist/diff.js +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +1 -0
- package/dist/introspection.d.ts +7 -0
- package/dist/introspection.js +1 -0
- package/dist/operators-BVreW0ky.d.ts +719 -0
- package/dist/snapshot.d.ts +24 -0
- package/dist/snapshot.js +1 -0
- package/dist/table.d.ts +1 -0
- package/dist/table.js +1 -0
- package/dist/transaction-RE7LXTGV.js +1 -0
- package/package.json +73 -24
- package/src/builders/base.ts +53 -56
- package/src/builders/batch-get.ts +63 -58
- package/src/builders/batch-write.ts +81 -78
- package/src/builders/delete.ts +46 -53
- package/src/builders/insert.ts +158 -150
- package/src/builders/query-promise.ts +26 -35
- package/src/builders/relational-builder.ts +214 -191
- package/src/builders/select.ts +250 -238
- package/src/builders/transaction.ts +170 -151
- package/src/builders/update.ts +198 -191
- package/src/columns/binary-set.ts +29 -38
- package/src/columns/binary.ts +25 -35
- package/src/columns/boolean.ts +25 -30
- package/src/columns/date.ts +57 -64
- package/src/columns/index.ts +15 -15
- package/src/columns/json.ts +39 -48
- package/src/columns/list.ts +26 -36
- package/src/columns/map.ts +26 -34
- package/src/columns/number-set.ts +29 -38
- package/src/columns/number.ts +33 -40
- package/src/columns/string-set.ts +38 -47
- package/src/columns/string.ts +37 -49
- package/src/columns/uuid.ts +26 -33
- package/src/core/client.ts +9 -9
- package/src/core/column-builder.ts +194 -220
- package/src/core/column.ts +127 -135
- package/src/core/diff.ts +40 -34
- package/src/core/errors.ts +20 -17
- package/src/core/introspection.ts +62 -58
- package/src/core/operations.ts +17 -23
- package/src/core/parser.ts +82 -88
- package/src/core/relations.ts +165 -152
- package/src/core/retry.ts +52 -52
- package/src/core/snapshot.ts +131 -130
- package/src/core/strategies.ts +222 -214
- package/src/core/table.ts +189 -202
- package/src/core/validation.ts +52 -52
- package/src/db.ts +216 -213
- package/src/expressions/actions.ts +26 -26
- package/src/expressions/builder.ts +62 -54
- package/src/expressions/operators.ts +48 -48
- package/src/expressions/update-builder.ts +79 -75
- package/src/index.ts +2 -1
- package/src/indexes.ts +8 -8
- package/test/batch-resilience.test.ts +138 -0
- package/test/builders/delete.test.ts +100 -0
- package/test/builders/insert.test.ts +216 -0
- package/test/builders/relational-types.test.ts +55 -0
- package/test/builders/relational.integration.test.ts +291 -0
- package/test/builders/relational.test.ts +66 -0
- package/test/builders/select.test.ts +411 -0
- package/test/builders/transaction-errors.test.ts +46 -0
- package/test/builders/transaction-execution.test.ts +99 -0
- package/test/builders/transaction-proxy.test.ts +41 -0
- package/test/builders/update-expression.test.ts +106 -0
- package/test/builders/update.test.ts +179 -0
- package/test/core/diff.test.ts +152 -0
- package/test/core/expressions.test.ts +64 -0
- package/test/core/introspection.test.ts +47 -0
- package/test/core/parser.test.ts +69 -0
- package/test/core/snapshot-gen.test.ts +155 -0
- package/test/core/snapshot.test.ts +52 -0
- package/test/date-column.test.ts +159 -0
- package/test/fluent-writes.integration.test.ts +148 -0
- package/test/integration-retry.test.ts +77 -0
- package/test/integration.test.ts +105 -0
- package/test/item-size-error.test.ts +16 -0
- package/test/item-size-validation.test.ts +82 -0
- package/test/item-size.test.ts +47 -0
- package/test/iterator-pagination.integration.test.ts +132 -0
- package/test/jsdoc-builders.test.ts +55 -0
- package/test/jsdoc-schema.test.ts +107 -0
- package/test/json-column.test.ts +51 -0
- package/test/metadata.test.ts +54 -0
- package/test/mizzle-package.test.ts +20 -0
- package/test/relational-centralized.test.ts +83 -0
- package/test/relational-definition.test.ts +75 -0
- package/test/relational-init.test.ts +30 -0
- package/test/relational-proxy.test.ts +52 -0
- package/test/relations.test.ts +45 -0
- package/test/resilience-config.test.ts +34 -0
- package/test/retry-handler.test.ts +63 -0
- package/test/transaction.integration.test.ts +153 -0
- package/test/unified-select.integration.test.ts +153 -0
- package/test/unified-update.integration.test.ts +139 -0
- package/test/update.integration.test.ts +132 -0
- package/tsconfig.json +12 -9
- package/tsup.config.ts +11 -11
- package/vitest.config.ts +8 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
cond: Expression,
|
|
19
|
+
addName: (name: string) => string,
|
|
20
|
+
addValue: (value: unknown) => string,
|
|
21
|
+
excludeColumns?: Set<string>,
|
|
22
22
|
): string | undefined {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 (
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
76
|
+
if (cond.operator === "attribute_exists") {
|
|
77
|
+
return `attribute_exists(${colName})`;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
|
|
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
|
-
|
|
8
|
+
abstract readonly type: "binary" | "logical" | "function";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export class BinaryExpression extends Expression {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
44
|
+
return new BinaryExpression(column, "=", value);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function gt(column: Column, value: unknown) {
|
|
48
|
-
|
|
48
|
+
return new BinaryExpression(column, ">", value);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function gte(column: Column, value: unknown) {
|
|
52
|
-
|
|
52
|
+
return new BinaryExpression(column, ">=", value);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export function lt(column: Column, value: unknown) {
|
|
56
|
-
|
|
56
|
+
return new BinaryExpression(column, "<", value);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function lte(column: Column, value: unknown) {
|
|
60
|
-
|
|
60
|
+
return new BinaryExpression(column, "<=", value);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function between(column: Column, values: [unknown, unknown]) {
|
|
64
|
-
|
|
64
|
+
return new BinaryExpression(column, "between", values);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export function inList(column: Column, values: unknown[]) {
|
|
68
|
-
|
|
68
|
+
return new BinaryExpression(column, "in", values);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export function beginsWith(column: Column, value: string) {
|
|
72
|
-
|
|
72
|
+
return new FunctionExpression(column, "begins_with", value);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export function contains(column: Column, value: unknown) {
|
|
76
|
-
|
|
76
|
+
return new FunctionExpression(column, "contains", value);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export function attributeExists(column: Column) {
|
|
80
|
-
|
|
80
|
+
return new FunctionExpression(column, "attribute_exists", undefined);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function or(...conditions: Expression[]) {
|
|
84
|
-
|
|
84
|
+
return new LogicalExpression(conditions, "OR");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export function and(...conditions: Expression[]) {
|
|
88
|
-
|
|
88
|
+
return new LogicalExpression(conditions, "AND");
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export const operators = {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
return {
|
|
13
|
+
set: {},
|
|
14
|
+
add: {},
|
|
15
|
+
remove: [],
|
|
16
|
+
delete: {},
|
|
17
|
+
};
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function partitionUpdateValues(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
values: Record<string, unknown | UpdateAction>,
|
|
22
|
+
state: UpdateState,
|
|
23
|
+
columns?: Record<string, Column>,
|
|
24
24
|
): void {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
state: UpdateState,
|
|
62
|
+
addName: (name: string) => string,
|
|
63
|
+
addValue: (value: unknown) => string,
|
|
60
64
|
): string {
|
|
61
|
-
|
|
65
|
+
const sections: string[] = [];
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
101
|
+
return sections.join(" ");
|
|
98
102
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from "@
|
|
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
|
-
|
|
3
|
-
|
|
2
|
+
pk?: string;
|
|
3
|
+
sk?: string;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
export class IndexBuilder {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
14
|
+
return new IndexBuilder("gsi", { pk: pkColumn, sk: skColumn });
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function lsi(skColumn: string) {
|
|
18
|
-
|
|
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
|
+
});
|