@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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { P as PhysicalTable, f as Entity } from './operators-BVreW0ky.js';
|
|
2
|
+
import { AttributeDefinition, KeySchemaElement, GlobalSecondaryIndex, LocalSecondaryIndex } from '@aws-sdk/client-dynamodb';
|
|
3
|
+
|
|
4
|
+
interface TableSnapshot {
|
|
5
|
+
TableName: string;
|
|
6
|
+
AttributeDefinitions: AttributeDefinition[];
|
|
7
|
+
KeySchema: KeySchemaElement[];
|
|
8
|
+
GlobalSecondaryIndexes?: GlobalSecondaryIndex[];
|
|
9
|
+
LocalSecondaryIndexes?: LocalSecondaryIndex[];
|
|
10
|
+
}
|
|
11
|
+
interface MizzleSnapshot {
|
|
12
|
+
version: string;
|
|
13
|
+
tables: Record<string, TableSnapshot>;
|
|
14
|
+
}
|
|
15
|
+
interface SchemaCurrent {
|
|
16
|
+
tables: PhysicalTable[];
|
|
17
|
+
entities: Entity[];
|
|
18
|
+
}
|
|
19
|
+
declare function saveSnapshot(dir: string, snapshot: MizzleSnapshot): Promise<void>;
|
|
20
|
+
declare function loadSnapshot(dir: string): Promise<MizzleSnapshot | null>;
|
|
21
|
+
declare function generateSnapshot(schema: SchemaCurrent): MizzleSnapshot;
|
|
22
|
+
declare function getNextMigrationVersion(migrationsDir: string): Promise<string>;
|
|
23
|
+
|
|
24
|
+
export { type MizzleSnapshot, type SchemaCurrent, type TableSnapshot, generateSnapshot, getNextMigrationVersion, loadSnapshot, saveSnapshot };
|
package/dist/snapshot.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a,b,c,d}from"./chunk-AQVECMXP.js";import"./chunk-GPYZK4WY.js";import"./chunk-DU7UPWBW.js";export{c as generateSnapshot,d as getNextMigrationVersion,b as loadSnapshot,a as saveSnapshot};
|
package/dist/table.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { A as AnyTable, b as AtomicValues, f as Entity, g as EntityConfig, h as EntityWithColumns, k as InferInsertModel, l as InferModelFromColumns, m as InferSelectModel, n as InferSelectedModel, M as MapColumnName, P as PhysicalTable, q as PhysicalTableConfig, r as StrategyCallback, t as TableDefinition, u as UpdateTableConfig, V as dynamoEntity, W as dynamoTable } from './operators-BVreW0ky.js';
|
package/dist/table.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a,b,c,d}from"./chunk-GPYZK4WY.js";import"./chunk-DU7UPWBW.js";export{b as Entity,a as PhysicalTable,c as dynamoEntity,d as dynamoTable};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{R as a,S as b,T as c}from"./chunk-UM3YF5EC.js";import"./chunk-GPYZK4WY.js";import"./chunk-DU7UPWBW.js";export{a as ConditionCheckBuilder,c as TransactionExecutor,b as TransactionProxy};
|
package/package.json
CHANGED
|
@@ -1,41 +1,90 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurios/mizzle",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"private": false,
|
|
3
|
+
"version": "1.1.3",
|
|
5
4
|
"description": "a drizzle-like orm for dynamoDB",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"provenance": true
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"aws",
|
|
11
|
+
"database",
|
|
12
|
+
"dynamodb",
|
|
13
|
+
"mizzle",
|
|
14
|
+
"nosql",
|
|
15
|
+
"orm",
|
|
16
|
+
"singletable",
|
|
17
|
+
"ts",
|
|
18
|
+
"typescript"
|
|
19
|
+
],
|
|
20
|
+
"homepage": "https://mizzle-docs.vercel.app",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/realfakenerd/mizzle/issues"
|
|
18
23
|
},
|
|
24
|
+
"license": "MIT",
|
|
19
25
|
"author": {
|
|
20
26
|
"name": "Lucas",
|
|
21
27
|
"url": "https://github.com/realfakenerd"
|
|
22
28
|
},
|
|
23
29
|
"repository": {
|
|
24
|
-
"
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/realfakenerd/mizzle.git",
|
|
32
|
+
"directory": "packages/mizzle"
|
|
25
33
|
},
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
"type": "module",
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"module": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.js",
|
|
42
|
+
"development": "./src/index.ts"
|
|
43
|
+
},
|
|
44
|
+
"./columns": {
|
|
45
|
+
"types": "./dist/columns.d.ts",
|
|
46
|
+
"import": "./dist/columns.js",
|
|
47
|
+
"development": "./src/columns/index.ts"
|
|
48
|
+
},
|
|
49
|
+
"./table": {
|
|
50
|
+
"types": "./dist/table.d.ts",
|
|
51
|
+
"import": "./dist/table.js",
|
|
52
|
+
"development": "./src/core/table.ts"
|
|
53
|
+
},
|
|
54
|
+
"./snapshot": {
|
|
55
|
+
"types": "./dist/snapshot.d.ts",
|
|
56
|
+
"import": "./dist/snapshot.js",
|
|
57
|
+
"development": "./src/core/snapshot.ts"
|
|
58
|
+
},
|
|
59
|
+
"./diff": {
|
|
60
|
+
"types": "./dist/diff.d.ts",
|
|
61
|
+
"import": "./dist/diff.js",
|
|
62
|
+
"development": "./src/core/diff.ts"
|
|
63
|
+
},
|
|
64
|
+
"./introspection": {
|
|
65
|
+
"types": "./dist/introspection.d.ts",
|
|
66
|
+
"import": "./dist/introspection.js",
|
|
67
|
+
"development": "./src/core/introspection.ts"
|
|
68
|
+
},
|
|
69
|
+
"./db": {
|
|
70
|
+
"types": "./dist/db.d.ts",
|
|
71
|
+
"import": "./dist/db.js",
|
|
72
|
+
"development": "./src/db.ts"
|
|
73
|
+
}
|
|
30
74
|
},
|
|
31
75
|
"dependencies": {
|
|
32
76
|
"@aws-sdk/client-dynamodb": "3.962.0",
|
|
33
77
|
"@aws-sdk/lib-dynamodb": "3.962.0"
|
|
34
78
|
},
|
|
35
79
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"
|
|
80
|
+
"tsup": "^8.5.1",
|
|
81
|
+
"@repo/shared": "0.0.3",
|
|
82
|
+
"@repo/vitest-config": "0.0.0",
|
|
83
|
+
"@repo/typescript-config": "0.0.0"
|
|
84
|
+
},
|
|
85
|
+
"scripts": {
|
|
86
|
+
"check": "tsc --noEmit",
|
|
87
|
+
"build": "tsup",
|
|
88
|
+
"test": "vitest run"
|
|
40
89
|
}
|
|
41
|
-
}
|
|
90
|
+
}
|
package/src/builders/base.ts
CHANGED
|
@@ -1,71 +1,68 @@
|
|
|
1
1
|
import { type IMizzleClient } from "../core/client";
|
|
2
2
|
import { Entity } from "../core/table";
|
|
3
3
|
import { QueryPromise } from "./query-promise";
|
|
4
|
-
import { resolveTableName, mapToLogical } from "@
|
|
4
|
+
import { resolveTableName, mapToLogical } from "@repo/shared";
|
|
5
5
|
import { resolveStrategies, type StrategyResolution } from "../core/strategies";
|
|
6
6
|
import { type Expression } from "../expressions/operators";
|
|
7
|
-
import { ENTITY_SYMBOLS } from "@
|
|
7
|
+
import { ENTITY_SYMBOLS } from "@repo/shared";
|
|
8
8
|
|
|
9
|
-
export abstract class BaseBuilder<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
) {
|
|
17
|
-
super();
|
|
18
|
-
}
|
|
9
|
+
export abstract class BaseBuilder<TEntity extends Entity, TResult> extends QueryPromise<TResult> {
|
|
10
|
+
constructor(
|
|
11
|
+
protected readonly entity: TEntity,
|
|
12
|
+
protected readonly client: IMizzleClient,
|
|
13
|
+
) {
|
|
14
|
+
super();
|
|
15
|
+
}
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
public get tableName(): string {
|
|
18
|
+
return resolveTableName(this.entity as any);
|
|
19
|
+
}
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
protected get physicalTable() {
|
|
22
|
+
return this.entity[ENTITY_SYMBOLS.PHYSICAL_TABLE];
|
|
23
|
+
}
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
protected resolveKeys(
|
|
26
|
+
whereClause?: Expression,
|
|
27
|
+
providedValues?: Record<string, unknown>,
|
|
28
|
+
indexName?: string,
|
|
29
|
+
): StrategyResolution {
|
|
30
|
+
return resolveStrategies(this.entity, whereClause, providedValues, indexName);
|
|
31
|
+
}
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
protected createExpressionContext(prefix = "") {
|
|
34
|
+
const expressionAttributeNames: Record<string, string> = {};
|
|
35
|
+
const expressionAttributeValues: Record<string, unknown> = {};
|
|
36
|
+
let nameCount = 0;
|
|
37
|
+
let valueCount = 0;
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
const addName = (name: string) => {
|
|
40
|
+
const segments = name.split(".");
|
|
41
|
+
const placeholders = segments.map((segment) => {
|
|
42
|
+
// Check if we already mapped this name segment to avoid redundancy?
|
|
43
|
+
// Actually, simple counter is fine for now and safer against collisions.
|
|
44
|
+
const placeholder = `#${prefix}n${nameCount++}`;
|
|
45
|
+
expressionAttributeNames[placeholder] = segment;
|
|
46
|
+
return placeholder;
|
|
47
|
+
});
|
|
48
|
+
return placeholders.join(".");
|
|
49
|
+
};
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
const addValue = (value: unknown) => {
|
|
52
|
+
const placeholder = `:${prefix}v${valueCount++}`;
|
|
53
|
+
expressionAttributeValues[placeholder] = value;
|
|
54
|
+
return placeholder;
|
|
55
|
+
};
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
return {
|
|
58
|
+
expressionAttributeNames,
|
|
59
|
+
expressionAttributeValues,
|
|
60
|
+
addName,
|
|
61
|
+
addValue,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
protected mapToLogical(item: Record<string, unknown>): Record<string, unknown> {
|
|
66
|
+
return mapToLogical(this.entity as any, item);
|
|
67
|
+
}
|
|
71
68
|
}
|
|
@@ -1,76 +1,81 @@
|
|
|
1
1
|
import { BatchGetCommand } from "@aws-sdk/lib-dynamodb";
|
|
2
|
-
import { ENTITY_SYMBOLS } from "@
|
|
2
|
+
import { ENTITY_SYMBOLS } from "@repo/shared";
|
|
3
3
|
import { Entity, type InferSelectModel } from "../core/table";
|
|
4
4
|
import { BaseBuilder } from "./base";
|
|
5
5
|
import type { IMizzleClient } from "../core/client";
|
|
6
6
|
|
|
7
7
|
export class BatchGetBuilder {
|
|
8
|
-
|
|
8
|
+
constructor(private client: IMizzleClient) {}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
items<TEntity extends Entity>(entity: TEntity, keys: Partial<InferSelectModel<TEntity>>[]) {
|
|
11
|
+
return new BatchGetBase(entity, this.client, keys);
|
|
12
|
+
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface BatchGetResult<T> {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
succeeded: T[];
|
|
17
|
+
failed: Partial<T>[];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export class BatchGetBase<
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
TEntity extends Entity,
|
|
22
|
+
TResult = InferSelectModel<TEntity>,
|
|
23
23
|
> extends BaseBuilder<TEntity, BatchGetResult<TResult>> {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
static readonly [ENTITY_SYMBOLS.ENTITY_KIND]: string = "BatchGetBase";
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
entity: TEntity,
|
|
28
|
+
client: IMizzleClient,
|
|
29
|
+
private keysData: Partial<InferSelectModel<TEntity>>[],
|
|
30
|
+
) {
|
|
31
|
+
super(entity, client);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public override async execute(): Promise<BatchGetResult<TResult>> {
|
|
35
|
+
const succeeded: TResult[] = [];
|
|
36
|
+
const failed: Partial<TResult>[] = [];
|
|
37
|
+
|
|
38
|
+
// Group keys by resolved DynamoDB keys
|
|
39
|
+
let currentKeys = this.keysData.map(
|
|
40
|
+
(k) => this.resolveKeys(undefined, k as Record<string, unknown>).keys,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let attempts = 0;
|
|
44
|
+
const maxBatchAttempts = 5; // Internal limit for unprocessed retries
|
|
45
|
+
|
|
46
|
+
while (currentKeys.length > 0 && attempts < maxBatchAttempts) {
|
|
47
|
+
attempts++;
|
|
48
|
+
|
|
49
|
+
const command = new BatchGetCommand({
|
|
50
|
+
RequestItems: {
|
|
51
|
+
[this.tableName]: {
|
|
52
|
+
Keys: currentKeys,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const response = (await this.client.send(command)) as {
|
|
58
|
+
Responses?: Record<string, unknown[]>;
|
|
59
|
+
UnprocessedKeys?: Record<string, { Keys?: Record<string, unknown>[] }>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (response.Responses?.[this.tableName]) {
|
|
63
|
+
succeeded.push(...(response.Responses[this.tableName] as TResult[]));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const unprocessed = response.UnprocessedKeys?.[this.tableName]?.Keys;
|
|
67
|
+
|
|
68
|
+
if (unprocessed && unprocessed.length > 0) {
|
|
69
|
+
currentKeys = unprocessed;
|
|
70
|
+
} else {
|
|
71
|
+
currentKeys = [];
|
|
72
|
+
}
|
|
32
73
|
}
|
|
33
74
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const failed: Partial<TResult>[] = [];
|
|
37
|
-
|
|
38
|
-
// Group keys by resolved DynamoDB keys
|
|
39
|
-
let currentKeys = this.keysData.map(k => this.resolveKeys(undefined, k as Record<string, unknown>).keys);
|
|
40
|
-
|
|
41
|
-
let attempts = 0;
|
|
42
|
-
const maxBatchAttempts = 5; // Internal limit for unprocessed retries
|
|
43
|
-
|
|
44
|
-
while (currentKeys.length > 0 && attempts < maxBatchAttempts) {
|
|
45
|
-
attempts++;
|
|
46
|
-
|
|
47
|
-
const command = new BatchGetCommand({
|
|
48
|
-
RequestItems: {
|
|
49
|
-
[this.tableName]: {
|
|
50
|
-
Keys: currentKeys
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const response = await this.client.send(command) as { Responses?: Record<string, unknown[]>, UnprocessedKeys?: Record<string, { Keys?: Record<string, unknown>[] }> };
|
|
56
|
-
|
|
57
|
-
if (response.Responses?.[this.tableName]) {
|
|
58
|
-
succeeded.push(...(response.Responses[this.tableName] as TResult[]));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const unprocessed = response.UnprocessedKeys?.[this.tableName]?.Keys;
|
|
62
|
-
|
|
63
|
-
if (unprocessed && unprocessed.length > 0) {
|
|
64
|
-
currentKeys = unprocessed;
|
|
65
|
-
} else {
|
|
66
|
-
currentKeys = [];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (currentKeys.length > 0) {
|
|
71
|
-
failed.push(...(currentKeys as Partial<TResult>[]));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return { succeeded, failed };
|
|
75
|
+
if (currentKeys.length > 0) {
|
|
76
|
+
failed.push(...(currentKeys as Partial<TResult>[]));
|
|
75
77
|
}
|
|
78
|
+
|
|
79
|
+
return { succeeded, failed };
|
|
80
|
+
}
|
|
76
81
|
}
|
|
@@ -1,99 +1,102 @@
|
|
|
1
1
|
import { BatchWriteCommand } from "@aws-sdk/lib-dynamodb";
|
|
2
|
-
import { ENTITY_SYMBOLS } from "@
|
|
2
|
+
import { ENTITY_SYMBOLS } from "@repo/shared";
|
|
3
3
|
import { Entity, type InferInsertModel } from "../core/table";
|
|
4
4
|
import { BaseBuilder } from "./base";
|
|
5
5
|
import type { IMizzleClient } from "../core/client";
|
|
6
6
|
import { calculateItemSize } from "../core/validation";
|
|
7
7
|
import { ItemSizeExceededError } from "../core/errors";
|
|
8
8
|
|
|
9
|
-
export type BatchWriteOperation<TEntity extends Entity> =
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export type BatchWriteOperation<TEntity extends Entity> =
|
|
10
|
+
| { type: "put"; item: InferInsertModel<TEntity> }
|
|
11
|
+
| { type: "delete"; keys: Partial<InferInsertModel<TEntity>> };
|
|
12
12
|
|
|
13
13
|
export interface BatchWriteResult<_T> {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
succeededCount: number;
|
|
15
|
+
failed: unknown[]; // Operations that failed after all retries
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export class BatchWriteBuilder {
|
|
19
|
-
|
|
19
|
+
constructor(private client: IMizzleClient) {}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
operations<TEntity extends Entity>(entity: TEntity, ops: BatchWriteOperation<TEntity>[]) {
|
|
22
|
+
return new BatchWriteBase(entity, this.client, ops);
|
|
23
|
+
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export class BatchWriteBase<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
constructor(
|
|
32
|
-
entity: TEntity,
|
|
33
|
-
client: IMizzleClient,
|
|
34
|
-
private ops: BatchWriteOperation<TEntity>[],
|
|
35
|
-
) {
|
|
36
|
-
super(entity, client);
|
|
37
|
-
}
|
|
26
|
+
export class BatchWriteBase<TEntity extends Entity> extends BaseBuilder<
|
|
27
|
+
TEntity,
|
|
28
|
+
BatchWriteResult<InferInsertModel<TEntity>>
|
|
29
|
+
> {
|
|
30
|
+
static readonly [ENTITY_SYMBOLS.ENTITY_KIND]: string = "BatchWriteBase";
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
throw new ItemSizeExceededError(`Item in batch exceeds the 400KB limit.`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
PutRequest: {
|
|
55
|
-
Item: item
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
} else {
|
|
59
|
-
return {
|
|
60
|
-
DeleteRequest: {
|
|
61
|
-
Key: op.keys as Record<string, unknown>
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
let currentRequests: unknown[] = [...requests];
|
|
68
|
-
let attempts = 0;
|
|
69
|
-
const maxBatchAttempts = 5;
|
|
70
|
-
|
|
71
|
-
while (currentRequests.length > 0 && attempts < maxBatchAttempts) {
|
|
72
|
-
attempts++;
|
|
73
|
-
|
|
74
|
-
const command = new BatchWriteCommand({
|
|
75
|
-
RequestItems: {
|
|
76
|
-
[this.tableName]: currentRequests as any[]
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
-
const response = await this.client.send(command) as { UnprocessedItems?: Record<string, any[]> };
|
|
82
|
-
|
|
83
|
-
const unprocessed = response.UnprocessedItems?.[this.tableName] || [];
|
|
84
|
-
succeededCount += (currentRequests.length - unprocessed.length);
|
|
85
|
-
|
|
86
|
-
if (unprocessed.length > 0) {
|
|
87
|
-
currentRequests = unprocessed;
|
|
88
|
-
} else {
|
|
89
|
-
currentRequests = [];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
32
|
+
constructor(
|
|
33
|
+
entity: TEntity,
|
|
34
|
+
client: IMizzleClient,
|
|
35
|
+
private ops: BatchWriteOperation<TEntity>[],
|
|
36
|
+
) {
|
|
37
|
+
super(entity, client);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public override async execute(): Promise<BatchWriteResult<InferInsertModel<TEntity>>> {
|
|
41
|
+
let succeededCount = 0;
|
|
42
|
+
let failed: unknown[] = [];
|
|
92
43
|
|
|
93
|
-
|
|
94
|
-
|
|
44
|
+
const requests = this.ops.map((op) => {
|
|
45
|
+
if (op.type === "put") {
|
|
46
|
+
const item = op.item as Record<string, unknown>;
|
|
47
|
+
|
|
48
|
+
// Size validation
|
|
49
|
+
const size = calculateItemSize(item);
|
|
50
|
+
if (size > 400 * 1024) {
|
|
51
|
+
throw new ItemSizeExceededError(`Item in batch exceeds the 400KB limit.`);
|
|
95
52
|
}
|
|
96
53
|
|
|
97
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
PutRequest: {
|
|
56
|
+
Item: item,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
} else {
|
|
60
|
+
return {
|
|
61
|
+
DeleteRequest: {
|
|
62
|
+
Key: op.keys as Record<string, unknown>,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let currentRequests: unknown[] = [...requests];
|
|
69
|
+
let attempts = 0;
|
|
70
|
+
const maxBatchAttempts = 5;
|
|
71
|
+
|
|
72
|
+
while (currentRequests.length > 0 && attempts < maxBatchAttempts) {
|
|
73
|
+
attempts++;
|
|
74
|
+
|
|
75
|
+
const command = new BatchWriteCommand({
|
|
76
|
+
RequestItems: {
|
|
77
|
+
[this.tableName]: currentRequests as Record<string, unknown>[],
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
const response = (await this.client.send(command)) as {
|
|
83
|
+
UnprocessedItems?: Record<string, any[]>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const unprocessed = response.UnprocessedItems?.[this.tableName] || [];
|
|
87
|
+
succeededCount += currentRequests.length - unprocessed.length;
|
|
88
|
+
|
|
89
|
+
if (unprocessed.length > 0) {
|
|
90
|
+
currentRequests = unprocessed;
|
|
91
|
+
} else {
|
|
92
|
+
currentRequests = [];
|
|
93
|
+
}
|
|
98
94
|
}
|
|
95
|
+
|
|
96
|
+
if (currentRequests.length > 0) {
|
|
97
|
+
failed = currentRequests;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { succeededCount, failed };
|
|
101
|
+
}
|
|
99
102
|
}
|