@aurios/mizzle 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.
- package/CHANGELOG.md +20 -0
- package/package.json +62 -0
- package/src/builders/base.ts +71 -0
- package/src/builders/batch-get.ts +76 -0
- package/src/builders/batch-write.ts +98 -0
- package/src/builders/delete.ts +61 -0
- package/src/builders/insert.ts +156 -0
- package/src/builders/query-promise.ts +41 -0
- package/src/builders/relational-builder.ts +211 -0
- package/src/builders/select.ts +196 -0
- package/src/builders/transaction.ts +170 -0
- package/src/builders/update.ts +155 -0
- package/src/columns/binary-set.ts +49 -0
- package/src/columns/binary.ts +48 -0
- package/src/columns/boolean.ts +45 -0
- package/src/columns/date.ts +83 -0
- package/src/columns/index.ts +44 -0
- package/src/columns/json.ts +62 -0
- package/src/columns/list.ts +47 -0
- package/src/columns/map.ts +46 -0
- package/src/columns/number-set.ts +49 -0
- package/src/columns/number.ts +57 -0
- package/src/columns/string-set.ts +59 -0
- package/src/columns/string.ts +64 -0
- package/src/columns/uuid.ts +51 -0
- package/src/core/client.ts +18 -0
- package/src/core/column-builder.ts +272 -0
- package/src/core/column.ts +167 -0
- package/src/core/diff.ts +50 -0
- package/src/core/errors.ts +34 -0
- package/src/core/introspection.ts +73 -0
- package/src/core/operations.ts +34 -0
- package/src/core/parser.ts +109 -0
- package/src/core/relations.ts +149 -0
- package/src/core/retry.ts +70 -0
- package/src/core/snapshot.ts +192 -0
- package/src/core/strategies.ts +271 -0
- package/src/core/table.ts +260 -0
- package/src/core/validation.ts +75 -0
- package/src/db.ts +199 -0
- package/src/expressions/actions.ts +66 -0
- package/src/expressions/builder.ts +77 -0
- package/src/expressions/operators.ts +108 -0
- package/src/expressions/update-builder.ts +98 -0
- package/src/index.ts +20 -0
- package/src/indexes.ts +19 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +20 -0
package/src/db.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
2
|
+
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
|
|
3
|
+
import { InsertBuilder } from "./builders/insert";
|
|
4
|
+
import { RelationnalQueryBuilder } from "./builders/relational-builder";
|
|
5
|
+
import { SelectBuilder, type SelectedFields } from "./builders/select";
|
|
6
|
+
import type { Entity, InferInsertModel, InferSelectModel } from "./core/table";
|
|
7
|
+
import { UpdateBuilder } from "./builders/update";
|
|
8
|
+
import { DeleteBuilder } from "./builders/delete";
|
|
9
|
+
import { BatchGetBuilder } from "./builders/batch-get";
|
|
10
|
+
import { BatchWriteBuilder, type BatchWriteOperation } from "./builders/batch-write";
|
|
11
|
+
import { TransactionProxy } from "./builders/transaction";
|
|
12
|
+
import { extractMetadata, type InternalRelationalSchema } from "./core/relations";
|
|
13
|
+
import { RetryHandler, type RetryConfig } from "./core/retry";
|
|
14
|
+
import { MizzleClient, type IMizzleClient } from "./core/client";
|
|
15
|
+
|
|
16
|
+
export type QuerySchema<TSchema extends Record<string, unknown>> = {
|
|
17
|
+
[K in keyof TSchema as TSchema[K] extends Entity ? K : never]: RelationnalQueryBuilder<TSchema[K] extends Entity ? TSchema[K] : never>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* DynamoDB database instance.
|
|
22
|
+
*/
|
|
23
|
+
export class DynamoDB<TSchema extends Record<string, unknown> = Record<string, unknown>> {
|
|
24
|
+
private docClient: IMizzleClient;
|
|
25
|
+
private schema?: InternalRelationalSchema;
|
|
26
|
+
private retryConfig: RetryConfig;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Access relational queries for entities defined in the schema.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const users = await db.query.users.findMany();
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
public readonly query: QuerySchema<TSchema>;
|
|
37
|
+
|
|
38
|
+
constructor(client: DynamoDBClient, relations?: TSchema, retry?: Partial<RetryConfig>) {
|
|
39
|
+
this.retryConfig = {
|
|
40
|
+
maxAttempts: retry?.maxAttempts ?? 3,
|
|
41
|
+
baseDelay: retry?.baseDelay ?? 100,
|
|
42
|
+
};
|
|
43
|
+
this.docClient = new MizzleClient(
|
|
44
|
+
DynamoDBDocumentClient.from(client),
|
|
45
|
+
new RetryHandler(this.retryConfig)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (relations) {
|
|
49
|
+
this.schema = extractMetadata(relations);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.query = new Proxy({} as QuerySchema<TSchema>, {
|
|
53
|
+
get: (_, prop) => {
|
|
54
|
+
if (typeof prop !== 'string') return undefined;
|
|
55
|
+
|
|
56
|
+
if (!this.schema) {
|
|
57
|
+
throw new Error("No relations defined. Initialize mizzle with a relations object to use db.query.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const entityMetadata = this.schema.entities[prop];
|
|
61
|
+
if (!entityMetadata) {
|
|
62
|
+
throw new Error(`Entity ${prop} not found in relations schema.`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return new RelationnalQueryBuilder(this.docClient, entityMetadata.entity, this.schema, prop);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Insert a new record into the database.
|
|
72
|
+
*/
|
|
73
|
+
insert<T extends Entity>(table: T): InsertBuilder<T> {
|
|
74
|
+
return new InsertBuilder(table, this.docClient);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Select records from the database.
|
|
79
|
+
*/
|
|
80
|
+
select<TSelection extends SelectedFields>(
|
|
81
|
+
fields?: TSelection,
|
|
82
|
+
): SelectBuilder<TSelection | undefined> {
|
|
83
|
+
return new SelectBuilder(this.docClient, fields);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Batch get items from the database.
|
|
88
|
+
*/
|
|
89
|
+
batchGet<T extends Entity>(
|
|
90
|
+
entity: T,
|
|
91
|
+
keys: Partial<InferSelectModel<T>>[],
|
|
92
|
+
) {
|
|
93
|
+
return new BatchGetBuilder(this.docClient).items(entity, keys);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Batch write items to the database.
|
|
98
|
+
*/
|
|
99
|
+
batchWrite<T extends Entity>(
|
|
100
|
+
entity: T,
|
|
101
|
+
operations: BatchWriteOperation<T>[],
|
|
102
|
+
) {
|
|
103
|
+
return new BatchWriteBuilder(this.docClient).operations(entity, operations);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Start a relational query manually for a specific entity.
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
110
|
+
_query<T extends Entity>(table: T) {
|
|
111
|
+
return new RelationnalQueryBuilder<T>(this.docClient, table);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Update existing records in the database.
|
|
116
|
+
*/
|
|
117
|
+
update<T extends Entity>(table: T): UpdateBuilder<T> {
|
|
118
|
+
return new UpdateBuilder(table, this.docClient);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Delete records from the database.
|
|
123
|
+
*/
|
|
124
|
+
delete<T extends Entity>(
|
|
125
|
+
table: T,
|
|
126
|
+
keys: Partial<InferInsertModel<T>>,
|
|
127
|
+
): DeleteBuilder<T> {
|
|
128
|
+
return new DeleteBuilder(table, this.docClient, keys as Record<string, unknown>);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Execute multiple operations atomically in a transaction.
|
|
133
|
+
*
|
|
134
|
+
* @param token ClientRequestToken for idempotency.
|
|
135
|
+
* @param callback Callback that returns an array of operations.
|
|
136
|
+
*/
|
|
137
|
+
async transaction(
|
|
138
|
+
token: string,
|
|
139
|
+
callback: (tx: TransactionProxy) => any[] | Promise<any[]>
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
const proxy = new TransactionProxy(this.docClient);
|
|
142
|
+
const operations = await callback(proxy);
|
|
143
|
+
|
|
144
|
+
if (operations.length === 0) return;
|
|
145
|
+
if (operations.length > 100) {
|
|
146
|
+
throw new Error("DynamoDB transactions are limited to 100 items.");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Executor will be implemented in transaction.ts
|
|
150
|
+
const { TransactionExecutor } = await import("./builders/transaction");
|
|
151
|
+
const executor = new TransactionExecutor(this.docClient);
|
|
152
|
+
await executor.execute(token, operations);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Configuration for initializing Mizzle.
|
|
158
|
+
*/
|
|
159
|
+
export interface MizzleConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> {
|
|
160
|
+
/**
|
|
161
|
+
* AWS DynamoDB Client.
|
|
162
|
+
*/
|
|
163
|
+
client: DynamoDBClient;
|
|
164
|
+
/**
|
|
165
|
+
* Relational schema definition.
|
|
166
|
+
*/
|
|
167
|
+
relations?: TSchema;
|
|
168
|
+
/**
|
|
169
|
+
* Retry configuration for transient errors.
|
|
170
|
+
*/
|
|
171
|
+
retry?: Partial<RetryConfig>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Initialize Mizzle with a DynamoDB client or a configuration object.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const db = mizzle(client);
|
|
180
|
+
* // or
|
|
181
|
+
* const db = mizzle({ client, relations });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export function mizzle<TSchema extends Record<string, unknown> = Record<string, unknown>>(
|
|
185
|
+
config: DynamoDBClient | MizzleConfig<TSchema>
|
|
186
|
+
): DynamoDB<TSchema> {
|
|
187
|
+
if (config instanceof DynamoDBClient) {
|
|
188
|
+
return new DynamoDB(config);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if ("client" in config && config.client instanceof DynamoDBClient) {
|
|
192
|
+
return new DynamoDB(config.client, config.relations, config.retry);
|
|
193
|
+
}
|
|
194
|
+
// Fallback for cases where instanceof might fail due to multiple SDK versions
|
|
195
|
+
if ("client" in config && config.client) {
|
|
196
|
+
return new DynamoDB(config.client as DynamoDBClient, config.relations, config.retry);
|
|
197
|
+
}
|
|
198
|
+
return new DynamoDB(config as unknown as DynamoDBClient);
|
|
199
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type ActionType = "SET" | "ADD" | "REMOVE" | "DELETE";
|
|
2
|
+
|
|
3
|
+
export abstract class UpdateAction {
|
|
4
|
+
abstract readonly action: ActionType;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class SetAction extends UpdateAction {
|
|
8
|
+
readonly action = "SET";
|
|
9
|
+
constructor(
|
|
10
|
+
readonly value: unknown,
|
|
11
|
+
readonly functionName?: "list_append" | "if_not_exists",
|
|
12
|
+
readonly usePathAsFirstArg: boolean = false
|
|
13
|
+
) {
|
|
14
|
+
super();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class AddAction extends UpdateAction {
|
|
19
|
+
readonly action = "ADD";
|
|
20
|
+
constructor(readonly value: unknown) {
|
|
21
|
+
super();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class RemoveAction extends UpdateAction {
|
|
26
|
+
readonly action = "REMOVE";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class DeleteAction extends UpdateAction {
|
|
30
|
+
readonly action = "DELETE";
|
|
31
|
+
constructor(readonly value: unknown) {
|
|
32
|
+
super();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function add(value: number | Set<unknown>) {
|
|
37
|
+
return new AddAction(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Maps to list_append(path, :value)
|
|
42
|
+
*/
|
|
43
|
+
export function append(value: unknown[]) {
|
|
44
|
+
return new SetAction(value, "list_append", true);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Maps to if_not_exists(path, :value)
|
|
49
|
+
*/
|
|
50
|
+
export function ifNotExists(value: unknown) {
|
|
51
|
+
return new SetAction(value, "if_not_exists", true);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function remove() {
|
|
55
|
+
return new RemoveAction();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function addToSet(values: Set<unknown> | unknown[]) {
|
|
59
|
+
const value = Array.isArray(values) ? new Set(values) : values;
|
|
60
|
+
return new AddAction(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function deleteFromSet(values: Set<unknown> | unknown[]) {
|
|
64
|
+
const value = Array.isArray(values) ? new Set(values) : values;
|
|
65
|
+
return new DeleteAction(value);
|
|
66
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Expression,
|
|
3
|
+
BinaryExpression,
|
|
4
|
+
LogicalExpression,
|
|
5
|
+
FunctionExpression,
|
|
6
|
+
} from "./operators";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Recursively builds a DynamoDB expression string from an Expression object.
|
|
10
|
+
*
|
|
11
|
+
* @param cond The expression object to build.
|
|
12
|
+
* @param addName A callback to add an attribute name and return its placeholder.
|
|
13
|
+
* @param addValue A callback to add an attribute value and return its placeholder.
|
|
14
|
+
* @param excludeColumns Optional set of physical column names to exclude from the expression.
|
|
15
|
+
* @returns A string suitable for use in ConditionExpression or FilterExpression.
|
|
16
|
+
*/
|
|
17
|
+
export function buildExpression(
|
|
18
|
+
cond: Expression,
|
|
19
|
+
addName: (name: string) => string,
|
|
20
|
+
addValue: (value: unknown) => string,
|
|
21
|
+
excludeColumns?: Set<string>,
|
|
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
|
+
}
|
|
32
|
+
|
|
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;
|
|
41
|
+
|
|
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
|
+
}
|
|
48
|
+
|
|
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
|
+
}
|
|
54
|
+
|
|
55
|
+
const valKey = addValue(mapValue(cond.value));
|
|
56
|
+
return `${colName} ${cond.operator} ${valKey}`;
|
|
57
|
+
}
|
|
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;
|
|
67
|
+
|
|
68
|
+
if (cond.operator === "attribute_exists") {
|
|
69
|
+
return `attribute_exists(${colName})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const valKey = addValue(mapValue(cond.value));
|
|
73
|
+
return `${cond.operator}(${colName}, ${valKey})`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Column } from "../core/column";
|
|
2
|
+
|
|
3
|
+
type BinaryOperators = "=" | "<" | "<=" | ">=" | ">" | "between" | "in";
|
|
4
|
+
type LogicalOperators = "AND" | "OR";
|
|
5
|
+
type FunctionOperators = "begins_with" | "contains" | "attribute_exists";
|
|
6
|
+
|
|
7
|
+
export abstract class Expression {
|
|
8
|
+
abstract readonly type: "binary" | "logical" | "function";
|
|
9
|
+
}
|
|
10
|
+
|
|
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
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class LogicalExpression extends Expression {
|
|
23
|
+
readonly type = "logical";
|
|
24
|
+
constructor(
|
|
25
|
+
readonly conditions: Expression[],
|
|
26
|
+
readonly operator: LogicalOperators,
|
|
27
|
+
) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
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
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function eq(column: Column, value: unknown) {
|
|
44
|
+
return new BinaryExpression(column, "=", value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function gt(column: Column, value: unknown) {
|
|
48
|
+
return new BinaryExpression(column, ">", value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function gte(column: Column, value: unknown) {
|
|
52
|
+
return new BinaryExpression(column, ">=", value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function lt(column: Column, value: unknown) {
|
|
56
|
+
return new BinaryExpression(column, "<", value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function lte(column: Column, value: unknown) {
|
|
60
|
+
return new BinaryExpression(column, "<=", value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function between(column: Column, values: [unknown, unknown]) {
|
|
64
|
+
return new BinaryExpression(column, "between", values);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function inList(column: Column, values: unknown[]) {
|
|
68
|
+
return new BinaryExpression(column, "in", values);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function beginsWith(column: Column, value: string) {
|
|
72
|
+
return new FunctionExpression(column, "begins_with", value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function contains(column: Column, value: unknown) {
|
|
76
|
+
return new FunctionExpression(column, "contains", value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function attributeExists(column: Column) {
|
|
80
|
+
return new FunctionExpression(column, "attribute_exists", undefined);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function or(...conditions: Expression[]) {
|
|
84
|
+
return new LogicalExpression(conditions, "OR");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function and(...conditions: Expression[]) {
|
|
88
|
+
return new LogicalExpression(conditions, "AND");
|
|
89
|
+
}
|
|
90
|
+
|
|
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,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type Operators = typeof operators;
|
|
107
|
+
|
|
108
|
+
export type Condition = Expression;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type Column } from "../core/column";
|
|
2
|
+
import { UpdateAction } from "./actions";
|
|
3
|
+
|
|
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>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createUpdateState(): UpdateState {
|
|
12
|
+
return {
|
|
13
|
+
set: {},
|
|
14
|
+
add: {},
|
|
15
|
+
remove: [],
|
|
16
|
+
delete: {},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function partitionUpdateValues(
|
|
21
|
+
values: Record<string, unknown | UpdateAction>,
|
|
22
|
+
state: UpdateState,
|
|
23
|
+
columns?: Record<string, Column>
|
|
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;
|
|
30
|
+
|
|
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) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function buildUpdateExpressionString(
|
|
57
|
+
state: UpdateState,
|
|
58
|
+
addName: (name: string) => string,
|
|
59
|
+
addValue: (value: unknown) => string
|
|
60
|
+
): string {
|
|
61
|
+
const sections: string[] = [];
|
|
62
|
+
|
|
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
|
+
}
|
|
77
|
+
|
|
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
|
+
}
|
|
84
|
+
|
|
85
|
+
if (state.remove.length > 0) {
|
|
86
|
+
const parts = state.remove.map((key) => addName(key));
|
|
87
|
+
sections.push(`REMOVE ${parts.join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
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
|
+
}
|
|
96
|
+
|
|
97
|
+
return sections.join(" ");
|
|
98
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * from "@mizzle/shared";
|
|
2
|
+
export * from "./expressions/operators";
|
|
3
|
+
export * from "./expressions/builder";
|
|
4
|
+
export * from "./expressions/actions";
|
|
5
|
+
export * from "./builders/insert";
|
|
6
|
+
export * from "./builders/select";
|
|
7
|
+
export * from "./builders/update";
|
|
8
|
+
export * from "./builders/delete";
|
|
9
|
+
export * from "./builders/transaction";
|
|
10
|
+
export * from "./builders/batch-get";
|
|
11
|
+
export * from "./builders/batch-write";
|
|
12
|
+
export * from "./builders/relational-builder";
|
|
13
|
+
export * from "./core/table";
|
|
14
|
+
export * from "./core/strategies";
|
|
15
|
+
export * from "./core/relations";
|
|
16
|
+
export * from "./core/retry";
|
|
17
|
+
export * from "./core/errors";
|
|
18
|
+
export * from "./columns";
|
|
19
|
+
export * from "./db";
|
|
20
|
+
export * from "./indexes";
|
package/src/indexes.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface IndexColumnConfig {
|
|
2
|
+
pk?: string;
|
|
3
|
+
sk?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class IndexBuilder {
|
|
7
|
+
constructor(
|
|
8
|
+
public type: "gsi" | "lsi",
|
|
9
|
+
public config: IndexColumnConfig,
|
|
10
|
+
) {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function gsi(pkColumn: string, skColumn?: string) {
|
|
14
|
+
return new IndexBuilder("gsi", { pk: pkColumn, sk: skColumn });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function lsi(skColumn: string) {
|
|
18
|
+
return new IndexBuilder("lsi", { sk: skColumn });
|
|
19
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: {
|
|
5
|
+
index: 'src/index.ts',
|
|
6
|
+
columns: 'src/columns/index.ts',
|
|
7
|
+
table: 'src/core/table.ts',
|
|
8
|
+
snapshot: 'src/core/snapshot.ts',
|
|
9
|
+
diff: 'src/core/diff.ts',
|
|
10
|
+
introspection: 'src/core/introspection.ts',
|
|
11
|
+
db: 'src/db.ts',
|
|
12
|
+
},
|
|
13
|
+
format: 'esm',
|
|
14
|
+
dts: true,
|
|
15
|
+
clean: true,
|
|
16
|
+
noExternal: ['@mizzle/shared'],
|
|
17
|
+
external: [/^@aws-sdk\//, 'uuid'],
|
|
18
|
+
minify: true,
|
|
19
|
+
splitting: true,
|
|
20
|
+
});
|