@lafken/dynamo 0.10.1
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/LICENCE +21 -0
- package/README.md +503 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +19 -0
- package/lib/main/index.d.ts +1 -0
- package/lib/main/index.js +17 -0
- package/lib/main/table/index.d.ts +2 -0
- package/lib/main/table/index.js +18 -0
- package/lib/main/table/table.d.ts +107 -0
- package/lib/main/table/table.js +141 -0
- package/lib/main/table/table.types.d.ts +368 -0
- package/lib/main/table/table.types.js +15 -0
- package/lib/resolver/index.d.ts +1 -0
- package/lib/resolver/index.js +17 -0
- package/lib/resolver/resolver.d.ts +12 -0
- package/lib/resolver/resolver.js +48 -0
- package/lib/resolver/resolver.types.d.ts +13 -0
- package/lib/resolver/resolver.types.js +2 -0
- package/lib/resolver/table/external/external.d.ts +14 -0
- package/lib/resolver/table/external/external.js +15 -0
- package/lib/resolver/table/internal/internal.d.ts +19 -0
- package/lib/resolver/table/internal/internal.js +224 -0
- package/lib/resolver/table/table.types.d.ts +4 -0
- package/lib/resolver/table/table.types.js +2 -0
- package/lib/service/client/client.d.ts +3 -0
- package/lib/service/client/client.js +10 -0
- package/lib/service/index.d.ts +3 -0
- package/lib/service/index.js +19 -0
- package/lib/service/query-builder/base/base.d.ts +19 -0
- package/lib/service/query-builder/base/base.js +151 -0
- package/lib/service/query-builder/base/base.types.d.ts +37 -0
- package/lib/service/query-builder/base/base.types.js +2 -0
- package/lib/service/query-builder/base/base.utils.d.ts +22 -0
- package/lib/service/query-builder/base/base.utils.js +89 -0
- package/lib/service/query-builder/batch-write/batch-write.d.ts +15 -0
- package/lib/service/query-builder/batch-write/batch-write.js +51 -0
- package/lib/service/query-builder/batch-write/batch-write.types.d.ts +9 -0
- package/lib/service/query-builder/batch-write/batch-write.types.js +2 -0
- package/lib/service/query-builder/bulk-create/bulk-create.d.ts +6 -0
- package/lib/service/query-builder/bulk-create/bulk-create.js +26 -0
- package/lib/service/query-builder/bulk-create/bulk-create.types.d.ts +4 -0
- package/lib/service/query-builder/bulk-create/bulk-create.types.js +2 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.d.ts +6 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.js +27 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.types.d.ts +7 -0
- package/lib/service/query-builder/bulk-delete/bulk-delete.types.js +2 -0
- package/lib/service/query-builder/create/create.d.ts +6 -0
- package/lib/service/query-builder/create/create.js +22 -0
- package/lib/service/query-builder/create/create.types.d.ts +6 -0
- package/lib/service/query-builder/create/create.types.js +2 -0
- package/lib/service/query-builder/delete/delete.d.ts +13 -0
- package/lib/service/query-builder/delete/delete.js +33 -0
- package/lib/service/query-builder/delete/delete.types.d.ts +7 -0
- package/lib/service/query-builder/delete/delete.types.js +2 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.d.ts +15 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.js +90 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.types.d.ts +5 -0
- package/lib/service/query-builder/dynamo-index/dynamo-index.types.js +2 -0
- package/lib/service/query-builder/find/find.d.ts +13 -0
- package/lib/service/query-builder/find/find.js +63 -0
- package/lib/service/query-builder/find/find.types.d.ts +8 -0
- package/lib/service/query-builder/find/find.types.js +2 -0
- package/lib/service/query-builder/find-all/find-all.d.ts +10 -0
- package/lib/service/query-builder/find-all/find-all.js +19 -0
- package/lib/service/query-builder/find-one/find-one.d.ts +9 -0
- package/lib/service/query-builder/find-one/find-one.js +20 -0
- package/lib/service/query-builder/index.d.ts +1 -0
- package/lib/service/query-builder/index.js +17 -0
- package/lib/service/query-builder/query-builder.types.d.ts +243 -0
- package/lib/service/query-builder/query-builder.types.js +3 -0
- package/lib/service/query-builder/scan/scan.d.ts +15 -0
- package/lib/service/query-builder/scan/scan.js +57 -0
- package/lib/service/query-builder/scan/scan.types.d.ts +6 -0
- package/lib/service/query-builder/scan/scan.types.js +2 -0
- package/lib/service/query-builder/update/update.d.ts +15 -0
- package/lib/service/query-builder/update/update.js +101 -0
- package/lib/service/query-builder/update/update.types.d.ts +6 -0
- package/lib/service/query-builder/update/update.types.js +2 -0
- package/lib/service/query-builder/update/update.utils.d.ts +11 -0
- package/lib/service/query-builder/update/update.utils.js +22 -0
- package/lib/service/query-builder/upsert/upsert.d.ts +14 -0
- package/lib/service/query-builder/upsert/upsert.js +40 -0
- package/lib/service/query-builder/upsert/upsert.types.d.ts +7 -0
- package/lib/service/query-builder/upsert/upsert.types.js +2 -0
- package/lib/service/repository/index.d.ts +3 -0
- package/lib/service/repository/index.js +19 -0
- package/lib/service/repository/repository.d.ts +3 -0
- package/lib/service/repository/repository.js +89 -0
- package/lib/service/repository/repository.types.d.ts +23 -0
- package/lib/service/repository/repository.types.js +2 -0
- package/lib/service/repository/repository.utils.d.ts +9 -0
- package/lib/service/repository/repository.utils.js +17 -0
- package/lib/service/transaction/index.d.ts +2 -0
- package/lib/service/transaction/index.js +18 -0
- package/lib/service/transaction/transaction.d.ts +4 -0
- package/lib/service/transaction/transaction.js +38 -0
- package/lib/service/transaction/transaction.types.d.ts +5 -0
- package/lib/service/transaction/transaction.types.js +2 -0
- package/package.json +99 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InternalTable = void 0;
|
|
4
|
+
const data_aws_cloudwatch_event_bus_1 = require("@cdktn/provider-aws/lib/data-aws-cloudwatch-event-bus");
|
|
5
|
+
const dynamodb_table_1 = require("@cdktn/provider-aws/lib/dynamodb-table");
|
|
6
|
+
const pipes_pipe_1 = require("@cdktn/provider-aws/lib/pipes-pipe");
|
|
7
|
+
const resolver_1 = require("@lafken/resolver");
|
|
8
|
+
const service_1 = require("../../../service");
|
|
9
|
+
const mapFieldType = {
|
|
10
|
+
String: 'S',
|
|
11
|
+
Number: 'N',
|
|
12
|
+
};
|
|
13
|
+
class InternalTable extends resolver_1.lafkenResource.make(dynamodb_table_1.DynamodbTable) {
|
|
14
|
+
constructor(scope, props) {
|
|
15
|
+
const { modelProps, partitionKey: partitionKeyName, sortKey: sortKeyName, fields, } = (0, service_1.getModelInformation)(props.classResource);
|
|
16
|
+
const { globalIndexes, localIndexes, indexAttributes } = InternalTable.getIndexes(scope, modelProps.indexes, sortKeyName);
|
|
17
|
+
const availableAttributes = new Set([...(indexAttributes || new Set())]);
|
|
18
|
+
availableAttributes.add(partitionKeyName.toString());
|
|
19
|
+
if (sortKeyName) {
|
|
20
|
+
availableAttributes.add(sortKeyName.toString());
|
|
21
|
+
}
|
|
22
|
+
super(scope, `${modelProps.name}-table`, {
|
|
23
|
+
name: modelProps.name,
|
|
24
|
+
rangeKey: sortKeyName,
|
|
25
|
+
hashKey: partitionKeyName,
|
|
26
|
+
attribute: InternalTable.getAttributes(fields, availableAttributes, modelProps.ttl),
|
|
27
|
+
globalSecondaryIndex: globalIndexes,
|
|
28
|
+
localSecondaryIndex: localIndexes,
|
|
29
|
+
streamEnabled: !!modelProps.stream?.enabled,
|
|
30
|
+
streamViewType: modelProps.stream?.enabled
|
|
31
|
+
? modelProps.stream?.type || 'NEW_AND_OLD_IMAGES'
|
|
32
|
+
: undefined,
|
|
33
|
+
ttl: modelProps.ttl
|
|
34
|
+
? {
|
|
35
|
+
attributeName: modelProps.ttl.toString(),
|
|
36
|
+
enabled: true,
|
|
37
|
+
}
|
|
38
|
+
: undefined,
|
|
39
|
+
...InternalTable.getBillingModeProps(modelProps),
|
|
40
|
+
replica: modelProps.replica,
|
|
41
|
+
});
|
|
42
|
+
this.isGlobal('dynamo', modelProps.name);
|
|
43
|
+
if (modelProps.stream?.enabled) {
|
|
44
|
+
const defaultBus = new data_aws_cloudwatch_event_bus_1.DataAwsCloudwatchEventBus(this, 'DefaultBus', {
|
|
45
|
+
name: 'default',
|
|
46
|
+
});
|
|
47
|
+
const role = new resolver_1.Role(this, `pipe-dynamo-${modelProps.name}-role`, {
|
|
48
|
+
services: [
|
|
49
|
+
{
|
|
50
|
+
type: 'dynamodb',
|
|
51
|
+
permissions: [
|
|
52
|
+
'DescribeStream',
|
|
53
|
+
'GetRecords',
|
|
54
|
+
'GetShardIterator',
|
|
55
|
+
'ListStreams',
|
|
56
|
+
],
|
|
57
|
+
resources: [this.streamArn],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'event',
|
|
61
|
+
permissions: ['PutEvents'],
|
|
62
|
+
resources: [defaultBus.arn],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
name: `pipe-dynamo-${modelProps.name}-role`,
|
|
66
|
+
principal: 'pipes.amazonaws.com',
|
|
67
|
+
});
|
|
68
|
+
const filters = this.createFilterCriteria(modelProps.stream, fields);
|
|
69
|
+
new pipes_pipe_1.PipesPipe(this, `${modelProps.name}-pipe`, {
|
|
70
|
+
name: `${modelProps.name}-pipe`,
|
|
71
|
+
roleArn: role.arn,
|
|
72
|
+
source: this.streamArn,
|
|
73
|
+
target: defaultBus.arn,
|
|
74
|
+
desiredState: 'RUNNING',
|
|
75
|
+
sourceParameters: {
|
|
76
|
+
dynamodbStreamParameters: {
|
|
77
|
+
startingPosition: 'LATEST',
|
|
78
|
+
batchSize: modelProps.stream.batchSize || 10,
|
|
79
|
+
maximumBatchingWindowInSeconds: modelProps.stream.maximumBatchingWindowInSeconds || 1,
|
|
80
|
+
maximumRecordAgeInSeconds: -1,
|
|
81
|
+
},
|
|
82
|
+
filterCriteria: filters.length
|
|
83
|
+
? { filter: filters.map((f) => ({ pattern: f.pattern })) }
|
|
84
|
+
: undefined,
|
|
85
|
+
},
|
|
86
|
+
targetParameters: {
|
|
87
|
+
eventbridgeEventBusParameters: {
|
|
88
|
+
detailType: 'db:stream',
|
|
89
|
+
source: `dynamodb.${modelProps.name}`,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
new resolver_1.ResourceOutput(this, modelProps.outputs);
|
|
95
|
+
}
|
|
96
|
+
static getAttributes(fields, indexAttributes, ttl) {
|
|
97
|
+
const attributes = [];
|
|
98
|
+
for (const key in fields) {
|
|
99
|
+
const field = fields[key];
|
|
100
|
+
const parsedType = mapFieldType[field.type];
|
|
101
|
+
if (!parsedType || !indexAttributes.has(field.name) || field.name === ttl) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
attributes.push({
|
|
105
|
+
name: field.name,
|
|
106
|
+
type: parsedType,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return attributes;
|
|
110
|
+
}
|
|
111
|
+
static getBillingModeProps(model) {
|
|
112
|
+
if (model.billingMode === 'provisioned') {
|
|
113
|
+
return {
|
|
114
|
+
billingModel: 'PROVISIONED',
|
|
115
|
+
readCapacity: model.readCapacity,
|
|
116
|
+
writeCapacity: model.writeCapacity,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (model.billingMode === undefined || model.billingMode === 'pay_per_request') {
|
|
120
|
+
return {
|
|
121
|
+
billingMode: 'PAY_PER_REQUEST',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
static getIndexes(scope, indexes = [], sortKeyName) {
|
|
126
|
+
if (indexes.length === 0) {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
const indexAttributes = new Set([]);
|
|
130
|
+
const globalIndexes = [];
|
|
131
|
+
const localIndexes = [];
|
|
132
|
+
for (const index of indexes) {
|
|
133
|
+
const projectionType = 'ALL';
|
|
134
|
+
let nonKeyAttributes;
|
|
135
|
+
if (Array.isArray(index.projection)) {
|
|
136
|
+
nonKeyAttributes = index.projection;
|
|
137
|
+
}
|
|
138
|
+
if (index.type === 'local') {
|
|
139
|
+
if (!sortKeyName) {
|
|
140
|
+
throw new Error('It is not possible to add a local secondary index without an associated sort key.');
|
|
141
|
+
}
|
|
142
|
+
indexAttributes.add(index.sortKey.toString());
|
|
143
|
+
localIndexes.push({
|
|
144
|
+
name: index.name,
|
|
145
|
+
rangeKey: index.sortKey.toString(),
|
|
146
|
+
projectionType,
|
|
147
|
+
nonKeyAttributes,
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
indexAttributes.add(index.partitionKey.toString());
|
|
152
|
+
if (index.sortKey) {
|
|
153
|
+
indexAttributes.add(index.sortKey.toString());
|
|
154
|
+
}
|
|
155
|
+
globalIndexes.push({
|
|
156
|
+
name: index.name,
|
|
157
|
+
hashKey: index.partitionKey.toString(),
|
|
158
|
+
rangeKey: index.sortKey ? index.sortKey.toString() : undefined,
|
|
159
|
+
projectionType,
|
|
160
|
+
nonKeyAttributes,
|
|
161
|
+
readCapacity: index.readCapacity,
|
|
162
|
+
writeCapacity: index.writeCapacity,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
indexAttributes,
|
|
167
|
+
localIndexes,
|
|
168
|
+
globalIndexes,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
createFilterCriteria(stream, fields) {
|
|
172
|
+
const filters = [];
|
|
173
|
+
if (!stream.filters)
|
|
174
|
+
return [];
|
|
175
|
+
if (stream.filters.eventName) {
|
|
176
|
+
filters.push({ pattern: JSON.stringify({ eventName: stream.filters.eventName }) });
|
|
177
|
+
}
|
|
178
|
+
if (stream.filters.keys) {
|
|
179
|
+
filters.push({
|
|
180
|
+
pattern: JSON.stringify({
|
|
181
|
+
dynamodb: {
|
|
182
|
+
Keys: this.getKeyFilterCriteria(stream.filters.keys, fields),
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (stream.filters.newImage) {
|
|
188
|
+
filters.push({
|
|
189
|
+
pattern: JSON.stringify({
|
|
190
|
+
dynamodb: {
|
|
191
|
+
NewImage: this.getKeyFilterCriteria(stream.filters.newImage, fields),
|
|
192
|
+
},
|
|
193
|
+
}),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (stream.filters.oldImage) {
|
|
197
|
+
filters.push({
|
|
198
|
+
pattern: JSON.stringify({
|
|
199
|
+
dynamodb: {
|
|
200
|
+
OldImage: this.getKeyFilterCriteria(stream.filters.oldImage, fields),
|
|
201
|
+
},
|
|
202
|
+
}),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return filters;
|
|
206
|
+
}
|
|
207
|
+
getKeyFilterCriteria(keys, fields) {
|
|
208
|
+
return Object.entries(keys).reduce((acc, [key, value]) => {
|
|
209
|
+
const field = fields[key];
|
|
210
|
+
if (!field) {
|
|
211
|
+
throw new Error(`field ${key} not found in dynamo table`);
|
|
212
|
+
}
|
|
213
|
+
const fieldType = mapFieldType[field.type];
|
|
214
|
+
if (!fieldType) {
|
|
215
|
+
throw new Error(`field ${key} has not valid type in filter criteria`);
|
|
216
|
+
}
|
|
217
|
+
acc[key] = {
|
|
218
|
+
[fieldType]: value,
|
|
219
|
+
};
|
|
220
|
+
return acc;
|
|
221
|
+
}, {});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
exports.InternalTable = InternalTable;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getClientWithXRay = exports.client = void 0;
|
|
4
|
+
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
5
|
+
const aws_xray_sdk_1 = require("aws-xray-sdk");
|
|
6
|
+
exports.client = new client_dynamodb_1.DynamoDBClient();
|
|
7
|
+
const getClientWithXRay = () => {
|
|
8
|
+
return (0, aws_xray_sdk_1.captureAWSv3Client)(exports.client);
|
|
9
|
+
};
|
|
10
|
+
exports.getClientWithXRay = getClientWithXRay;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./query-builder"), exports);
|
|
18
|
+
__exportStar(require("./repository"), exports);
|
|
19
|
+
__exportStar(require("./transaction"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClassResource } from '@lafken/common';
|
|
2
|
+
import type { LocalIndex } from '../../../main';
|
|
3
|
+
import type { GlobalIndexProperty } from '../dynamo-index/dynamo-index.types';
|
|
4
|
+
import type { AndFilter, Filter, KeyCondition, OrFilter } from '../query-builder.types';
|
|
5
|
+
import type { QueryBuilderProps } from './base.types';
|
|
6
|
+
export declare class QueryBuilderBase<E extends ClassResource> {
|
|
7
|
+
private options;
|
|
8
|
+
constructor(options: QueryBuilderProps<E>);
|
|
9
|
+
protected attributeNames: Record<string, string>;
|
|
10
|
+
protected attributeValues: Record<string, any>;
|
|
11
|
+
protected getKeyConditionExpression(expression: KeyCondition<E>, index?: LocalIndex<E> | GlobalIndexProperty): string;
|
|
12
|
+
protected getFilterExpression<T>(filter: Filter<T> | OrFilter<T> | AndFilter<T>, names?: string[], union?: 'or' | 'and', counter?: number): string;
|
|
13
|
+
protected getAttributesAndNames(): {
|
|
14
|
+
ExpressionAttributeNames: Record<string, string> | undefined;
|
|
15
|
+
ExpressionAttributeValues: Record<string, import("@aws-sdk/client-dynamodb").AttributeValue> | undefined;
|
|
16
|
+
};
|
|
17
|
+
private resolveFilter;
|
|
18
|
+
private validateGlobalKey;
|
|
19
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryBuilderBase = void 0;
|
|
4
|
+
const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
|
|
5
|
+
const base_utils_1 = require("./base.utils");
|
|
6
|
+
class QueryBuilderBase {
|
|
7
|
+
options;
|
|
8
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: ''
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
attributeNames = {};
|
|
13
|
+
attributeValues = {};
|
|
14
|
+
getKeyConditionExpression(expression, index) {
|
|
15
|
+
const { partition, sort } = expression;
|
|
16
|
+
const isGlobalIndex = index?.type === 'global';
|
|
17
|
+
this.validateGlobalKey(Object.keys(partition), isGlobalIndex);
|
|
18
|
+
sort && this.validateGlobalKey(Object.keys(sort), isGlobalIndex);
|
|
19
|
+
const partitionFilters = [];
|
|
20
|
+
for (const key in partition) {
|
|
21
|
+
partitionFilters.push(this.resolveFilter({
|
|
22
|
+
expressionName: key,
|
|
23
|
+
value: partition[key],
|
|
24
|
+
filterExpressionKey: 'equal',
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
if (!sort) {
|
|
28
|
+
return partitionFilters.join(' and ');
|
|
29
|
+
}
|
|
30
|
+
let sortValues = sort;
|
|
31
|
+
if (isGlobalIndex) {
|
|
32
|
+
let sortCount = Object.keys(sort).length;
|
|
33
|
+
sortValues = {};
|
|
34
|
+
let lastOmittedAttribute;
|
|
35
|
+
for (const attribute of index.sortKey) {
|
|
36
|
+
const attributeName = attribute;
|
|
37
|
+
if (sort[attributeName]) {
|
|
38
|
+
if (lastOmittedAttribute) {
|
|
39
|
+
throw new Error(`The sortKey is read from left to right. It is not possible to skip values; you must include the attribute"${lastOmittedAttribute}". Check the index "${index.name}"`);
|
|
40
|
+
}
|
|
41
|
+
if (sortCount > 1 && typeof sort[attributeName] === 'object') {
|
|
42
|
+
throw new Error('Only the last attribute in the index sortKey can add a value different from the equal.');
|
|
43
|
+
}
|
|
44
|
+
sortValues[attributeName] = sort[attributeName];
|
|
45
|
+
sortCount--;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
lastOmittedAttribute = attribute;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const key in sortValues) {
|
|
52
|
+
let filterExpressionKey = 'equal';
|
|
53
|
+
let sortValue = sort[key];
|
|
54
|
+
if (typeof sortValue === 'object') {
|
|
55
|
+
filterExpressionKey = Object.keys(sortValue || {})[0];
|
|
56
|
+
sortValue = sortValue[filterExpressionKey];
|
|
57
|
+
}
|
|
58
|
+
partitionFilters.push(this.resolveFilter({
|
|
59
|
+
expressionName: key,
|
|
60
|
+
value: sortValue,
|
|
61
|
+
filterExpressionKey,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
return partitionFilters.join(' and ');
|
|
65
|
+
}
|
|
66
|
+
getFilterExpression(filter, names = [], union = 'and', counter = 0) {
|
|
67
|
+
counter += 1;
|
|
68
|
+
const filterExpression = [];
|
|
69
|
+
let index = 0;
|
|
70
|
+
for (const key in filter) {
|
|
71
|
+
switch (key) {
|
|
72
|
+
case 'OR': {
|
|
73
|
+
const orFilter = filter;
|
|
74
|
+
const orExpressions = [];
|
|
75
|
+
for (const condition of orFilter.OR) {
|
|
76
|
+
const expression = this.getFilterExpression(condition, names, 'and', counter);
|
|
77
|
+
counter += 1;
|
|
78
|
+
orExpressions.push(expression);
|
|
79
|
+
}
|
|
80
|
+
filterExpression.push(`(${orExpressions.join(' or ')})`);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'AND': {
|
|
84
|
+
const andFilter = filter;
|
|
85
|
+
const expression = this.getFilterExpression(andFilter.AND, names, 'and', counter);
|
|
86
|
+
filterExpression.join(`(${expression})`);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
default: {
|
|
90
|
+
const currentKeyNames = [...names, key];
|
|
91
|
+
const keyName = currentKeyNames.join('.#');
|
|
92
|
+
const keyValue = `${currentKeyNames.join('_')}_${counter}_${index}`;
|
|
93
|
+
const keyFilter = filter[key];
|
|
94
|
+
if (typeof keyFilter === 'object') {
|
|
95
|
+
const keys = Object.keys(keyFilter || {});
|
|
96
|
+
const filterResolver = keys.find((key) => base_utils_1.filterKeys.has(key));
|
|
97
|
+
if (filterResolver) {
|
|
98
|
+
const expression = this.resolveFilter({
|
|
99
|
+
expressionName: keyName,
|
|
100
|
+
keyName: key,
|
|
101
|
+
filterExpressionKey: filterResolver,
|
|
102
|
+
expressionValue: keyValue,
|
|
103
|
+
value: keyFilter[filterResolver],
|
|
104
|
+
});
|
|
105
|
+
filterExpression.push(expression);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
this.attributeNames[`#${key}`] = key;
|
|
109
|
+
const expression = this.getFilterExpression(keyFilter, currentKeyNames, union);
|
|
110
|
+
filterExpression.push(expression);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
const expression = this.resolveFilter({
|
|
114
|
+
expressionName: keyName,
|
|
115
|
+
keyName: key,
|
|
116
|
+
filterExpressionKey: 'equal',
|
|
117
|
+
expressionValue: keyValue,
|
|
118
|
+
value: keyFilter,
|
|
119
|
+
});
|
|
120
|
+
filterExpression.push(expression);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
index++;
|
|
124
|
+
}
|
|
125
|
+
return `(${filterExpression.join(` ${union} `)})`;
|
|
126
|
+
}
|
|
127
|
+
getAttributesAndNames() {
|
|
128
|
+
return {
|
|
129
|
+
ExpressionAttributeNames: Object.keys(this.attributeNames).length > 0 ? this.attributeNames : undefined,
|
|
130
|
+
ExpressionAttributeValues: Object.keys(this.attributeValues).length > 0
|
|
131
|
+
? (0, util_dynamodb_1.marshall)(this.attributeValues)
|
|
132
|
+
: undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
resolveFilter = ({ expressionName, filterExpressionKey, expressionValue, value, keyName = expressionName, }) => {
|
|
136
|
+
const name = `#${expressionName}`;
|
|
137
|
+
const { attributeValues, expression } = base_utils_1.filterResolver[filterExpressionKey](name, expressionValue || expressionName, value);
|
|
138
|
+
this.attributeNames[`#${keyName}`] = keyName;
|
|
139
|
+
this.attributeValues = {
|
|
140
|
+
...this.attributeValues,
|
|
141
|
+
...attributeValues,
|
|
142
|
+
};
|
|
143
|
+
return expression;
|
|
144
|
+
};
|
|
145
|
+
validateGlobalKey(keys, isGlobalIndex) {
|
|
146
|
+
if (keys.length > 1 && !isGlobalIndex) {
|
|
147
|
+
throw new Error('partition keys only support multi-attributes when they are a global multi-attribute index.');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.QueryBuilderBase = QueryBuilderBase;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import type { ClassResource } from '@lafken/common';
|
|
3
|
+
import type { ModelInformation } from '../query-builder.types';
|
|
4
|
+
import type { filterResolver } from './base.utils';
|
|
5
|
+
export interface QueryBuilderProps<E extends ClassResource> extends ModelInformation<E> {
|
|
6
|
+
client: DynamoDBClient;
|
|
7
|
+
}
|
|
8
|
+
export type FilterResolverTypes = keyof typeof filterResolver;
|
|
9
|
+
export interface ResolveFilterProps {
|
|
10
|
+
/**
|
|
11
|
+
* value without #
|
|
12
|
+
*/
|
|
13
|
+
expressionName: string;
|
|
14
|
+
/**
|
|
15
|
+
* value of expression name
|
|
16
|
+
*
|
|
17
|
+
* @default {expressionName}
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* name used to attribute name
|
|
21
|
+
*
|
|
22
|
+
* @default {expressionName} with '#'
|
|
23
|
+
*/
|
|
24
|
+
keyName?: string;
|
|
25
|
+
/**
|
|
26
|
+
* value of expression without :
|
|
27
|
+
*/
|
|
28
|
+
expressionValue?: string;
|
|
29
|
+
/**
|
|
30
|
+
* value of expressionValue
|
|
31
|
+
*/
|
|
32
|
+
value: any;
|
|
33
|
+
/**
|
|
34
|
+
* key of executed function
|
|
35
|
+
*/
|
|
36
|
+
filterExpressionKey: FilterResolverTypes;
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ExpressionResolverResult {
|
|
2
|
+
expression: string;
|
|
3
|
+
attributeValues: Record<string, any>;
|
|
4
|
+
}
|
|
5
|
+
export declare const expressionResolver: <V>(key: string, valueName: V, sign: string, value: any) => ExpressionResolverResult;
|
|
6
|
+
export declare const filterResolver: {
|
|
7
|
+
equal: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
8
|
+
lessThan: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
9
|
+
lessOrEqualThan: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
10
|
+
greaterThan: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
11
|
+
greaterOrEqualThan: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
12
|
+
between: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
13
|
+
beginsWith: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
14
|
+
contains: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
15
|
+
exist: (key: string) => ExpressionResolverResult;
|
|
16
|
+
notExist: (key: string) => ExpressionResolverResult;
|
|
17
|
+
notEqual: (key: string, valueName: string, value: any) => ExpressionResolverResult;
|
|
18
|
+
in: (key: string, valueName: any, values: any[]) => ExpressionResolverResult;
|
|
19
|
+
notContains(key: string, valueName: string, value: any): ExpressionResolverResult;
|
|
20
|
+
};
|
|
21
|
+
export declare const notValueKeys: Set<"lessThan" | "lessOrEqualThan" | "greaterThan" | "greaterOrEqualThan" | "between" | "exist" | "notExist" | "contains" | "notContains" | "notEqual" | "in" | "beginsWith" | "equal">;
|
|
22
|
+
export declare const filterKeys: Set<string>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterKeys = exports.notValueKeys = exports.filterResolver = exports.expressionResolver = void 0;
|
|
4
|
+
const expressionResolver = (key, valueName, sign, value) => {
|
|
5
|
+
const valueResolver = `:${valueName}`;
|
|
6
|
+
return {
|
|
7
|
+
expression: `${key} ${sign} ${valueResolver}`,
|
|
8
|
+
attributeValues: {
|
|
9
|
+
[valueResolver]: value,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
exports.expressionResolver = expressionResolver;
|
|
14
|
+
exports.filterResolver = {
|
|
15
|
+
equal: (key, valueName, value) => {
|
|
16
|
+
return (0, exports.expressionResolver)(key, valueName, '=', value);
|
|
17
|
+
},
|
|
18
|
+
lessThan: (key, valueName, value) => {
|
|
19
|
+
return (0, exports.expressionResolver)(key, valueName, '<', value);
|
|
20
|
+
},
|
|
21
|
+
lessOrEqualThan: (key, valueName, value) => {
|
|
22
|
+
return (0, exports.expressionResolver)(key, valueName, '<=', value);
|
|
23
|
+
},
|
|
24
|
+
greaterThan: (key, valueName, value) => {
|
|
25
|
+
return (0, exports.expressionResolver)(key, valueName, '>', value);
|
|
26
|
+
},
|
|
27
|
+
greaterOrEqualThan: (key, valueName, value) => {
|
|
28
|
+
return (0, exports.expressionResolver)(key, valueName, '>=', value);
|
|
29
|
+
},
|
|
30
|
+
between: (key, valueName, value) => {
|
|
31
|
+
return {
|
|
32
|
+
expression: `${key} BETWEEN :${valueName}_0 and :${valueName}_1`,
|
|
33
|
+
attributeValues: {
|
|
34
|
+
[`:${valueName}_0`]: value[0],
|
|
35
|
+
[`:${valueName}_1`]: value[1],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
beginsWith: (key, valueName, value) => {
|
|
40
|
+
const valueResolver = `:${valueName}`;
|
|
41
|
+
return {
|
|
42
|
+
expression: `begins_with(${key}, ${valueResolver})`,
|
|
43
|
+
attributeValues: {
|
|
44
|
+
[valueResolver]: value,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
contains: (key, valueName, value) => {
|
|
49
|
+
const valueResolver = `:${valueName}`;
|
|
50
|
+
return {
|
|
51
|
+
expression: `contains(${key}, ${valueResolver})`,
|
|
52
|
+
attributeValues: {
|
|
53
|
+
[valueResolver]: value,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
exist: (key) => {
|
|
58
|
+
return {
|
|
59
|
+
expression: `attribute_exists(${key})`,
|
|
60
|
+
attributeValues: {},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
notExist: (key) => {
|
|
64
|
+
return {
|
|
65
|
+
expression: `attribute_not_exists(${key})`,
|
|
66
|
+
attributeValues: {},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
notEqual: (key, valueName, value) => {
|
|
70
|
+
return (0, exports.expressionResolver)(key, valueName, '<>', value);
|
|
71
|
+
},
|
|
72
|
+
in: (key, valueName, values) => {
|
|
73
|
+
const valueResolvers = values.map((_, index) => `:${valueName}_${index}`);
|
|
74
|
+
return {
|
|
75
|
+
expression: `${key} in (${valueResolvers.join(',')})`,
|
|
76
|
+
attributeValues: valueResolvers.reduce((acc, valueResolver, index) => {
|
|
77
|
+
acc[valueResolver] = values[index];
|
|
78
|
+
return acc;
|
|
79
|
+
}, {}),
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
notContains(key, valueName, value) {
|
|
83
|
+
const data = this.contains(key, valueName, value);
|
|
84
|
+
data.expression = `not ${data.expression}`;
|
|
85
|
+
return data;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
exports.notValueKeys = new Set(['exist', 'notExist']);
|
|
89
|
+
exports.filterKeys = new Set([...Object.keys(exports.filterResolver)]);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type BatchWriteItemCommandInput } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import type { ClassResource } from '@lafken/common';
|
|
3
|
+
import { QueryBuilderBase } from '../base/base';
|
|
4
|
+
import type { BatchWriteBuilderProps } from './batch-write.types';
|
|
5
|
+
export declare class BatchWriteBuilder<E extends ClassResource> extends QueryBuilderBase<E> {
|
|
6
|
+
private queryOptions;
|
|
7
|
+
private commands;
|
|
8
|
+
constructor(queryOptions: BatchWriteBuilderProps<E>);
|
|
9
|
+
getCommand(): BatchWriteItemCommandInput[];
|
|
10
|
+
then<T>(resolve: (value: void[]) => T, reject: (reason: any) => T): Promise<T>;
|
|
11
|
+
private exec;
|
|
12
|
+
private execAndRetry;
|
|
13
|
+
protected chunkItems<T>(items: Partial<T>[], size: number): Partial<T>[][];
|
|
14
|
+
private prepare;
|
|
15
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BatchWriteBuilder = void 0;
|
|
4
|
+
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
5
|
+
const base_1 = require("../base/base");
|
|
6
|
+
class BatchWriteBuilder extends base_1.QueryBuilderBase {
|
|
7
|
+
queryOptions;
|
|
8
|
+
commands = [];
|
|
9
|
+
constructor(queryOptions) {
|
|
10
|
+
super(queryOptions);
|
|
11
|
+
this.queryOptions = queryOptions;
|
|
12
|
+
this.prepare();
|
|
13
|
+
}
|
|
14
|
+
getCommand() {
|
|
15
|
+
return this.commands;
|
|
16
|
+
}
|
|
17
|
+
then(resolve, reject) {
|
|
18
|
+
return this.exec().then(resolve, reject);
|
|
19
|
+
}
|
|
20
|
+
async exec() {
|
|
21
|
+
return Promise.all(this.commands.map((command) => this.execAndRetry(command)));
|
|
22
|
+
}
|
|
23
|
+
async execAndRetry(inputCommand, attempt = 0) {
|
|
24
|
+
const command = new client_dynamodb_1.BatchWriteItemCommand(inputCommand);
|
|
25
|
+
const response = await this.queryOptions.client.send(command);
|
|
26
|
+
const unprocessedItems = response.UnprocessedItems || {};
|
|
27
|
+
if (Object.keys(unprocessedItems).length > 0) {
|
|
28
|
+
if (attempt === (this.queryOptions.maxAttempt ?? 5)) {
|
|
29
|
+
throw new Error('Failed to process all items after maximum retries');
|
|
30
|
+
}
|
|
31
|
+
await this.execAndRetry({
|
|
32
|
+
RequestItems: unprocessedItems,
|
|
33
|
+
}, attempt + 1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
chunkItems(items, size) {
|
|
37
|
+
const result = [];
|
|
38
|
+
const total = items.length;
|
|
39
|
+
for (let i = 0; i < total; i += size) {
|
|
40
|
+
result.push(items.slice(i, i + size));
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
prepare() {
|
|
45
|
+
const chunkedItems = this.chunkItems(this.queryOptions.items, 25);
|
|
46
|
+
for (const items of chunkedItems) {
|
|
47
|
+
this.commands.push(this.queryOptions.generateBatchCommand(items));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.BatchWriteBuilder = BatchWriteBuilder;
|