@travetto/model-dynamodb 2.1.5 → 2.2.2
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/README.md +1 -1
- package/package.json +3 -3
- package/src/service.ts +36 -33
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install @travetto/model-dynamodb
|
|
|
10
10
|
|
|
11
11
|
This module provides an [DynamoDB](https://aws.amazon.com/dynamodb/)-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [DynamoDB](https://aws.amazon.com/dynamodb/). The entire document is stored as a single value, so nothing is needed to handle schema updates in real time. Indices on the other hand are more complicated, and will not be retroactively computed for new values.
|
|
12
12
|
|
|
13
|
-
Supported
|
|
13
|
+
Supported features:
|
|
14
14
|
|
|
15
15
|
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/service/crud.ts#L11)
|
|
16
16
|
* [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-dynamodb",
|
|
3
3
|
"displayName": "DynamoDB Model Support",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.2",
|
|
5
5
|
"description": "DynamoDB backing for the travetto model module.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"typescript",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@aws-sdk/client-dynamodb": "^3.131.0",
|
|
30
|
-
"@travetto/config": "^2.
|
|
31
|
-
"@travetto/model": "^2.
|
|
30
|
+
"@travetto/config": "^2.2.2",
|
|
31
|
+
"@travetto/model": "^2.2.2"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
package/src/service.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { DynamoDBModelConfig } from './config';
|
|
|
18
18
|
|
|
19
19
|
const EXP_ATTR = 'expires_at__';
|
|
20
20
|
|
|
21
|
-
function simpleName(idx: string) {
|
|
21
|
+
function simpleName(idx: string): string {
|
|
22
22
|
return idx.replace(/[^A-Za-z0-9]/g, '');
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -27,6 +27,7 @@ function toValue(val: unknown, forceString?: boolean): dynamodb.AttributeValue |
|
|
|
27
27
|
if (val === undefined || val === null || val === '') {
|
|
28
28
|
return { NULL: true };
|
|
29
29
|
} else if (typeof val === 'string' || forceString) {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
30
31
|
return { S: val as string };
|
|
31
32
|
} else if (typeof val === 'number') {
|
|
32
33
|
return { N: `${val}` };
|
|
@@ -60,7 +61,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
60
61
|
|
|
61
62
|
constructor(public readonly config: DynamoDBModelConfig) { }
|
|
62
63
|
|
|
63
|
-
#resolveTable(cls: Class) {
|
|
64
|
+
#resolveTable(cls: Class): string {
|
|
64
65
|
let table = ModelRegistry.getStore(cls).toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_');
|
|
65
66
|
if (this.config.namespace) {
|
|
66
67
|
table = `${this.config.namespace}_${table}`;
|
|
@@ -68,7 +69,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
68
69
|
return table;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
async #putItem<T extends ModelType>(cls: Class<T>, id: string, item: T, mode: 'create' | 'update' | 'upsert') {
|
|
72
|
+
async #putItem<T extends ModelType>(cls: Class<T>, id: string, item: T, mode: 'create' | 'update' | 'upsert'): Promise<dynamodb.PutItemCommandOutput> {
|
|
72
73
|
const config = ModelRegistry.get(cls);
|
|
73
74
|
let expiry: number | undefined;
|
|
74
75
|
|
|
@@ -101,7 +102,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
101
102
|
},
|
|
102
103
|
ReturnValues: 'NONE'
|
|
103
104
|
};
|
|
104
|
-
console.debug('Querying', { query }
|
|
105
|
+
console.debug('Querying', { query });
|
|
105
106
|
return await this.client.putItem(query);
|
|
106
107
|
} else {
|
|
107
108
|
const indices: Record<string, unknown> = {};
|
|
@@ -135,7 +136,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
138
|
} catch (err) {
|
|
138
|
-
if (err.name === 'ConditionalCheckFailedException') {
|
|
139
|
+
if (err instanceof Error && err.name === 'ConditionalCheckFailedException') {
|
|
139
140
|
if (mode === 'create') {
|
|
140
141
|
throw new ExistsError(cls, id);
|
|
141
142
|
} else if (mode === 'update') {
|
|
@@ -146,7 +147,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
#computeIndexConfig<T extends ModelType>(cls: Class<T>) {
|
|
150
|
+
#computeIndexConfig<T extends ModelType>(cls: Class<T>): { indices?: dynamodb.GlobalSecondaryIndex[], attributes: dynamodb.AttributeDefinition[] } {
|
|
150
151
|
const config = ModelRegistry.get(cls);
|
|
151
152
|
const attributes: dynamodb.AttributeDefinition[] = [];
|
|
152
153
|
const indices: dynamodb.GlobalSecondaryIndex[] = [];
|
|
@@ -182,7 +183,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
182
183
|
return { indices: indices.length ? indices : undefined, attributes };
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
async postConstruct() {
|
|
186
|
+
async postConstruct(): Promise<void> {
|
|
186
187
|
this.client = new dynamodb.DynamoDB({ ...this.config.client });
|
|
187
188
|
await ModelStorageUtil.registerModelChangeListener(this);
|
|
188
189
|
ShutdownManager.onShutdown(this.constructor.ᚕid, () => this.client.destroy());
|
|
@@ -194,7 +195,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
194
195
|
* Add a new model
|
|
195
196
|
* @param cls
|
|
196
197
|
*/
|
|
197
|
-
async createModel(cls: Class<ModelType>) {
|
|
198
|
+
async createModel(cls: Class<ModelType>): Promise<void> {
|
|
198
199
|
const table = this.#resolveTable(cls);
|
|
199
200
|
const idx = this.#computeIndexConfig(cls);
|
|
200
201
|
|
|
@@ -227,7 +228,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
227
228
|
* Remove a model
|
|
228
229
|
* @param cls
|
|
229
230
|
*/
|
|
230
|
-
async deleteModel(cls: Class<ModelType>) {
|
|
231
|
+
async deleteModel(cls: Class<ModelType>): Promise<void> {
|
|
231
232
|
const table = this.#resolveTable(cls);
|
|
232
233
|
const { Table: verify } = (await this.client.describeTable({ TableName: table }).catch(err => ({ Table: undefined })));
|
|
233
234
|
if (verify) {
|
|
@@ -239,7 +240,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
239
240
|
* When the model changes
|
|
240
241
|
* @param cls
|
|
241
242
|
*/
|
|
242
|
-
async changeModel(cls: Class<ModelType>) {
|
|
243
|
+
async changeModel(cls: Class<ModelType>): Promise<void> {
|
|
243
244
|
const table = this.#resolveTable(cls);
|
|
244
245
|
const idx = this.#computeIndexConfig(cls);
|
|
245
246
|
// const existing = await this.cl.describeTable({ TableName: table });
|
|
@@ -254,11 +255,11 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
254
255
|
});
|
|
255
256
|
}
|
|
256
257
|
|
|
257
|
-
async createStorage() {
|
|
258
|
+
async createStorage(): Promise<void> {
|
|
258
259
|
// Do nothing
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
async deleteStorage() {
|
|
262
|
+
async deleteStorage(): Promise<void> {
|
|
262
263
|
for (const model of ModelRegistry.getClasses()) {
|
|
263
264
|
await this.client.deleteTable({
|
|
264
265
|
TableName: this.#resolveTable(model)
|
|
@@ -271,7 +272,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
271
272
|
return Util.uuid();
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
async get<T extends ModelType>(cls: Class<T>, id: string) {
|
|
275
|
+
async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
|
|
275
276
|
const res = await this.client.getItem({
|
|
276
277
|
TableName: this.#resolveTable(cls),
|
|
277
278
|
Key: { id: toValue(id) }
|
|
@@ -283,13 +284,13 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
283
284
|
throw new NotFoundError(cls, id);
|
|
284
285
|
}
|
|
285
286
|
|
|
286
|
-
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
|
|
287
|
+
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
287
288
|
const prepped = await ModelCrudUtil.preStore(cls, item, this);
|
|
288
289
|
await this.#putItem(cls, prepped.id, prepped, 'create');
|
|
289
290
|
return prepped;
|
|
290
291
|
}
|
|
291
292
|
|
|
292
|
-
async update<T extends ModelType>(cls: Class<T>, item: T) {
|
|
293
|
+
async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
|
|
293
294
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
294
295
|
item = await ModelCrudUtil.preStore(cls, item, this);
|
|
295
296
|
if (ModelRegistry.get(cls).expiresAt) {
|
|
@@ -299,22 +300,24 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
299
300
|
return item;
|
|
300
301
|
}
|
|
301
302
|
|
|
302
|
-
async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>) {
|
|
303
|
+
async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
303
304
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
304
305
|
const prepped = await ModelCrudUtil.preStore(cls, item, this);
|
|
305
306
|
await this.#putItem(cls, prepped.id, prepped, 'upsert');
|
|
306
307
|
return prepped;
|
|
307
308
|
}
|
|
308
309
|
|
|
309
|
-
async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string) {
|
|
310
|
+
async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
|
|
310
311
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
311
312
|
const id = item.id;
|
|
312
|
-
item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id))
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
item = await ModelCrudUtil.naivePartialUpdate(cls, item, view, (): Promise<T> => this.get(cls, id));
|
|
314
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
315
|
+
const itemAsT = item as T;
|
|
316
|
+
await this.#putItem(cls, id, itemAsT, 'update');
|
|
317
|
+
return itemAsT;
|
|
315
318
|
}
|
|
316
319
|
|
|
317
|
-
async delete<T extends ModelType>(cls: Class<T>, id: string) {
|
|
320
|
+
async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
|
318
321
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
319
322
|
const res = await this.client.deleteItem({
|
|
320
323
|
TableName: this.#resolveTable(cls),
|
|
@@ -326,7 +329,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
326
329
|
}
|
|
327
330
|
}
|
|
328
331
|
|
|
329
|
-
async * list<T extends ModelType>(cls: Class<T>) {
|
|
332
|
+
async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
|
|
330
333
|
let done = false;
|
|
331
334
|
let token: Record<string, dynamodb.AttributeValue> | undefined;
|
|
332
335
|
while (!done) {
|
|
@@ -339,9 +342,9 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
339
342
|
for (const el of batch.Items) {
|
|
340
343
|
try {
|
|
341
344
|
yield await loadAndCheckExpiry(cls, el.body.S!);
|
|
342
|
-
} catch (
|
|
343
|
-
if (!(
|
|
344
|
-
throw
|
|
345
|
+
} catch (err) {
|
|
346
|
+
if (!(err instanceof NotFoundError)) {
|
|
347
|
+
throw err;
|
|
345
348
|
}
|
|
346
349
|
}
|
|
347
350
|
}
|
|
@@ -356,12 +359,12 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
356
359
|
}
|
|
357
360
|
|
|
358
361
|
// Expiry
|
|
359
|
-
async deleteExpired<T extends ModelType>(cls: Class<T>) {
|
|
362
|
+
async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
|
|
360
363
|
return -1;
|
|
361
364
|
}
|
|
362
365
|
|
|
363
366
|
// Indexed
|
|
364
|
-
async #getIdByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>) {
|
|
367
|
+
async #getIdByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<string> {
|
|
365
368
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
366
369
|
|
|
367
370
|
const idxCfg = ModelRegistry.getIndex(cls, idx, ['sorted', 'unsorted']);
|
|
@@ -394,11 +397,11 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
394
397
|
}
|
|
395
398
|
|
|
396
399
|
// Indexed
|
|
397
|
-
async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>) {
|
|
400
|
+
async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
|
|
398
401
|
return this.get(cls, await this.#getIdByIndex(cls, idx, body));
|
|
399
402
|
}
|
|
400
403
|
|
|
401
|
-
async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>) {
|
|
404
|
+
async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
|
|
402
405
|
return this.delete(cls, await this.#getIdByIndex(cls, idx, body));
|
|
403
406
|
}
|
|
404
407
|
|
|
@@ -406,7 +409,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
406
409
|
return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
|
|
407
410
|
}
|
|
408
411
|
|
|
409
|
-
async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>) {
|
|
412
|
+
async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
|
|
410
413
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
411
414
|
|
|
412
415
|
const cfg = ModelRegistry.getIndex(cls, idx, ['sorted', 'unsorted']);
|
|
@@ -432,9 +435,9 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
432
435
|
for (const el of batch.Items) {
|
|
433
436
|
try {
|
|
434
437
|
yield await loadAndCheckExpiry(cls, el.body.S!);
|
|
435
|
-
} catch (
|
|
436
|
-
if (!(
|
|
437
|
-
throw
|
|
438
|
+
} catch (err) {
|
|
439
|
+
if (!(err instanceof NotFoundError)) {
|
|
440
|
+
throw err;
|
|
438
441
|
}
|
|
439
442
|
}
|
|
440
443
|
}
|