@aws-amplify/datastore 4.1.1 → 4.1.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/CHANGELOG.md +8 -0
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +52 -28
- package/lib/storage/adapter/AsyncStorageAdapter.js +212 -476
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +53 -28
- package/lib/storage/adapter/IndexedDBAdapter.js +589 -892
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/StorageAdapterBase.d.ts +146 -0
- package/lib/storage/adapter/StorageAdapterBase.js +479 -0
- package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +52 -28
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +215 -479
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +53 -28
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +588 -891
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +146 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js +477 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/package.json +6 -6
- package/src/storage/adapter/AsyncStorageAdapter.ts +239 -543
- package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
- package/src/storage/adapter/IndexedDBAdapter.ts +423 -786
- package/src/storage/adapter/StorageAdapterBase.ts +639 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import { ConsoleLogger as Logger } from '@aws-amplify/core';
|
|
2
|
+
import { Adapter } from './index';
|
|
3
|
+
import { ModelInstanceCreator } from '../../datastore/datastore';
|
|
4
|
+
import { ModelPredicateCreator } from '../../predicates';
|
|
5
|
+
import {
|
|
6
|
+
InternalSchema,
|
|
7
|
+
isPredicateObj,
|
|
8
|
+
ModelInstanceMetadata,
|
|
9
|
+
ModelPredicate,
|
|
10
|
+
NamespaceResolver,
|
|
11
|
+
OpType,
|
|
12
|
+
PaginationInput,
|
|
13
|
+
PersistentModel,
|
|
14
|
+
PersistentModelConstructor,
|
|
15
|
+
PredicateObject,
|
|
16
|
+
PredicatesGroup,
|
|
17
|
+
QueryOne,
|
|
18
|
+
RelationType,
|
|
19
|
+
} from '../../types';
|
|
20
|
+
import {
|
|
21
|
+
NAMESPACES,
|
|
22
|
+
getStorename,
|
|
23
|
+
getIndexKeys,
|
|
24
|
+
extractPrimaryKeyValues,
|
|
25
|
+
traverseModel,
|
|
26
|
+
validatePredicate,
|
|
27
|
+
getIndex,
|
|
28
|
+
getIndexFromAssociation,
|
|
29
|
+
isModelConstructor,
|
|
30
|
+
} from '../../util';
|
|
31
|
+
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
32
|
+
import type AsyncStorageDatabase from './AsyncStorageDatabase';
|
|
33
|
+
|
|
34
|
+
const logger = new Logger('DataStore');
|
|
35
|
+
const DB_NAME = 'amplify-datastore';
|
|
36
|
+
|
|
37
|
+
export abstract class StorageAdapterBase implements Adapter {
|
|
38
|
+
// Non-null assertions (bang operators) added to most properties to make TS happy.
|
|
39
|
+
// For now, we can be reasonably sure they're available when they're needed, because
|
|
40
|
+
// the adapter is not used directly outside the library boundary.
|
|
41
|
+
protected schema!: InternalSchema;
|
|
42
|
+
protected namespaceResolver!: NamespaceResolver;
|
|
43
|
+
protected modelInstanceCreator!: ModelInstanceCreator;
|
|
44
|
+
protected getModelConstructorByModelName!: (
|
|
45
|
+
namsespaceName: NAMESPACES,
|
|
46
|
+
modelName: string
|
|
47
|
+
) => PersistentModelConstructor<any>;
|
|
48
|
+
protected initPromise!: Promise<void>;
|
|
49
|
+
protected resolve!: (value?: any) => void;
|
|
50
|
+
protected reject!: (value?: any) => void;
|
|
51
|
+
protected dbName: string = DB_NAME;
|
|
52
|
+
protected abstract db: IDBPDatabase | AsyncStorageDatabase;
|
|
53
|
+
|
|
54
|
+
protected abstract preSetUpChecks(): Promise<void>;
|
|
55
|
+
protected abstract preOpCheck(): Promise<void>;
|
|
56
|
+
protected abstract initDb(): Promise<IDBPDatabase | AsyncStorageDatabase>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Initializes local DB
|
|
60
|
+
*
|
|
61
|
+
* @param theSchema
|
|
62
|
+
* @param namespaceResolver
|
|
63
|
+
* @param modelInstanceCreator
|
|
64
|
+
* @param getModelConstructorByModelName
|
|
65
|
+
* @param sessionId
|
|
66
|
+
*/
|
|
67
|
+
public async setUp(
|
|
68
|
+
theSchema: InternalSchema,
|
|
69
|
+
namespaceResolver: NamespaceResolver,
|
|
70
|
+
modelInstanceCreator: ModelInstanceCreator,
|
|
71
|
+
getModelConstructorByModelName: (
|
|
72
|
+
namsespaceName: NAMESPACES,
|
|
73
|
+
modelName: string
|
|
74
|
+
) => PersistentModelConstructor<any>,
|
|
75
|
+
sessionId?: string
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await this.preSetUpChecks();
|
|
78
|
+
|
|
79
|
+
if (!this.initPromise) {
|
|
80
|
+
this.initPromise = new Promise((res, rej) => {
|
|
81
|
+
this.resolve = res;
|
|
82
|
+
this.reject = rej;
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
await this.initPromise;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (sessionId) {
|
|
89
|
+
this.dbName = `${DB_NAME}-${sessionId}`;
|
|
90
|
+
}
|
|
91
|
+
this.schema = theSchema;
|
|
92
|
+
this.namespaceResolver = namespaceResolver;
|
|
93
|
+
this.modelInstanceCreator = modelInstanceCreator;
|
|
94
|
+
this.getModelConstructorByModelName = getModelConstructorByModelName;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
if (!this.db) {
|
|
98
|
+
this.db = await this.initDb();
|
|
99
|
+
this.resolve();
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.reject(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
* Abstract Methods for Adapter interface
|
|
108
|
+
* Not enough implementation similarities between the adapters
|
|
109
|
+
* to consolidate in the base class
|
|
110
|
+
*/
|
|
111
|
+
public abstract clear(): Promise<void>;
|
|
112
|
+
|
|
113
|
+
public abstract save<T extends PersistentModel>(
|
|
114
|
+
model: T,
|
|
115
|
+
condition?: ModelPredicate<T>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
public abstract query<T extends PersistentModel>(
|
|
119
|
+
modelConstructor: PersistentModelConstructor<T>,
|
|
120
|
+
predicate?: ModelPredicate<T>,
|
|
121
|
+
pagination?: PaginationInput<T>
|
|
122
|
+
): Promise<T[]>;
|
|
123
|
+
|
|
124
|
+
public abstract queryOne<T extends PersistentModel>(
|
|
125
|
+
modelConstructor: PersistentModelConstructor<T>,
|
|
126
|
+
firstOrLast: QueryOne
|
|
127
|
+
): Promise<T | undefined>;
|
|
128
|
+
|
|
129
|
+
public abstract batchSave<T extends PersistentModel>(
|
|
130
|
+
modelConstructor: PersistentModelConstructor<any>,
|
|
131
|
+
items: ModelInstanceMetadata[]
|
|
132
|
+
): Promise<[T, OpType][]>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param modelConstructor
|
|
136
|
+
* @returns local DB table name
|
|
137
|
+
*/
|
|
138
|
+
protected getStorenameForModel(
|
|
139
|
+
modelConstructor: PersistentModelConstructor<any>
|
|
140
|
+
): string {
|
|
141
|
+
const namespace = this.namespaceResolver(modelConstructor);
|
|
142
|
+
const { name: modelName } = modelConstructor;
|
|
143
|
+
|
|
144
|
+
return getStorename(namespace, modelName);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
*
|
|
149
|
+
* @param model - instantiated model record
|
|
150
|
+
* @returns the record's primary key values
|
|
151
|
+
*/
|
|
152
|
+
protected getIndexKeyValuesFromModel<T extends PersistentModel>(
|
|
153
|
+
model: T
|
|
154
|
+
): string[] {
|
|
155
|
+
const modelConstructor = Object.getPrototypeOf(model)
|
|
156
|
+
.constructor as PersistentModelConstructor<T>;
|
|
157
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
158
|
+
|
|
159
|
+
const keys = getIndexKeys(
|
|
160
|
+
this.schema.namespaces[namespaceName],
|
|
161
|
+
modelConstructor.name
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return extractPrimaryKeyValues(model, keys);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Common metadata for `save` operation
|
|
169
|
+
* used by individual storage adapters
|
|
170
|
+
*
|
|
171
|
+
* @param model
|
|
172
|
+
*/
|
|
173
|
+
protected saveMetadata<T extends PersistentModel>(
|
|
174
|
+
model: T
|
|
175
|
+
): {
|
|
176
|
+
storeName: string;
|
|
177
|
+
set: Set<string>;
|
|
178
|
+
connectionStoreNames;
|
|
179
|
+
modelKeyValues: string[];
|
|
180
|
+
} {
|
|
181
|
+
const modelConstructor = Object.getPrototypeOf(model)
|
|
182
|
+
.constructor as PersistentModelConstructor<T>;
|
|
183
|
+
const storeName = this.getStorenameForModel(modelConstructor);
|
|
184
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
185
|
+
|
|
186
|
+
const connectedModels = traverseModel(
|
|
187
|
+
modelConstructor.name,
|
|
188
|
+
model,
|
|
189
|
+
this.schema.namespaces[namespaceName],
|
|
190
|
+
this.modelInstanceCreator,
|
|
191
|
+
this.getModelConstructorByModelName!
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const set = new Set<string>();
|
|
195
|
+
const connectionStoreNames = Object.values(connectedModels).map(
|
|
196
|
+
({ modelName, item, instance }) => {
|
|
197
|
+
const storeName = getStorename(namespaceName, modelName);
|
|
198
|
+
set.add(storeName);
|
|
199
|
+
const keys = getIndexKeys(
|
|
200
|
+
this.schema.namespaces[namespaceName],
|
|
201
|
+
modelName
|
|
202
|
+
);
|
|
203
|
+
return { storeName, item, instance, keys };
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const modelKeyValues = this.getIndexKeyValuesFromModel(model);
|
|
208
|
+
|
|
209
|
+
return { storeName, set, connectionStoreNames, modelKeyValues };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Enforces conditional save. Throws if condition is not met.
|
|
214
|
+
* used by individual storage adapters
|
|
215
|
+
*
|
|
216
|
+
* @param model
|
|
217
|
+
*/
|
|
218
|
+
protected validateSaveCondition<T extends PersistentModel>(
|
|
219
|
+
condition?: ModelPredicate<T>,
|
|
220
|
+
fromDB?: unknown
|
|
221
|
+
): void {
|
|
222
|
+
if (!(condition && fromDB)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const predicates = ModelPredicateCreator.getPredicates(condition);
|
|
227
|
+
const { predicates: predicateObjs, type } = predicates!;
|
|
228
|
+
|
|
229
|
+
const isValid = validatePredicate(fromDB, type, predicateObjs);
|
|
230
|
+
|
|
231
|
+
if (!isValid) {
|
|
232
|
+
const msg = 'Conditional update failed';
|
|
233
|
+
logger.error(msg, { model: fromDB, condition: predicateObjs });
|
|
234
|
+
|
|
235
|
+
throw new Error(msg);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected abstract _get<T>(
|
|
240
|
+
storeOrStoreName: IDBPObjectStore | string,
|
|
241
|
+
keyArr: string[]
|
|
242
|
+
): Promise<T>;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Instantiate models from POJO records returned from the database
|
|
246
|
+
*
|
|
247
|
+
* @param namespaceName - string model namespace
|
|
248
|
+
* @param srcModelName - string model name
|
|
249
|
+
* @param records - array of uninstantiated records
|
|
250
|
+
* @returns
|
|
251
|
+
*/
|
|
252
|
+
protected async load<T>(
|
|
253
|
+
namespaceName: NAMESPACES,
|
|
254
|
+
srcModelName: string,
|
|
255
|
+
records: T[]
|
|
256
|
+
): Promise<T[]> {
|
|
257
|
+
const namespace = this.schema.namespaces[namespaceName];
|
|
258
|
+
const relations = namespace.relationships![srcModelName].relationTypes;
|
|
259
|
+
const connectionStoreNames = relations.map(({ modelName }) => {
|
|
260
|
+
return getStorename(namespaceName, modelName);
|
|
261
|
+
});
|
|
262
|
+
const modelConstructor = this.getModelConstructorByModelName!(
|
|
263
|
+
namespaceName,
|
|
264
|
+
srcModelName
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (connectionStoreNames.length === 0) {
|
|
268
|
+
return records.map(record =>
|
|
269
|
+
this.modelInstanceCreator(modelConstructor, record)
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return records.map(record =>
|
|
274
|
+
this.modelInstanceCreator(modelConstructor, record)
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Extracts operands from a predicate group into an array of key values
|
|
280
|
+
* Used in the query method
|
|
281
|
+
*
|
|
282
|
+
* @param predicates - predicate group
|
|
283
|
+
* @param keyPath - string array of key names ['id', 'sortKey']
|
|
284
|
+
* @returns string[] of key values
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```js
|
|
288
|
+
* { and:[{ id: { eq: 'abc' }}, { sortKey: { eq: 'def' }}] }
|
|
289
|
+
* ```
|
|
290
|
+
* Becomes
|
|
291
|
+
* ```
|
|
292
|
+
* ['abc', 'def']
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
private keyValueFromPredicate<T extends PersistentModel>(
|
|
296
|
+
predicates: PredicatesGroup<T>,
|
|
297
|
+
keyPath: string[]
|
|
298
|
+
): string[] | undefined {
|
|
299
|
+
const { predicates: predicateObjs } = predicates;
|
|
300
|
+
|
|
301
|
+
if (predicateObjs.length !== keyPath.length) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const keyValues = [] as any[];
|
|
306
|
+
|
|
307
|
+
for (const key of keyPath) {
|
|
308
|
+
const predicateObj = predicateObjs.find(
|
|
309
|
+
p =>
|
|
310
|
+
// it's a relevant predicate object only if it's an equality
|
|
311
|
+
// operation for a key field from the key:
|
|
312
|
+
isPredicateObj(p) &&
|
|
313
|
+
p.field === key &&
|
|
314
|
+
p.operator === 'eq' &&
|
|
315
|
+
p.operand !== null &&
|
|
316
|
+
p.operand !== undefined
|
|
317
|
+
) as PredicateObject<T>;
|
|
318
|
+
|
|
319
|
+
predicateObj && keyValues.push(predicateObj.operand);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return keyValues.length === keyPath.length ? keyValues : undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Common metadata for `query` operation
|
|
327
|
+
* used by individual storage adapters
|
|
328
|
+
*
|
|
329
|
+
* @param modelConstructor
|
|
330
|
+
* @param predicate
|
|
331
|
+
* @param pagination
|
|
332
|
+
*/
|
|
333
|
+
protected queryMetadata<T extends PersistentModel>(
|
|
334
|
+
modelConstructor: PersistentModelConstructor<T>,
|
|
335
|
+
predicate?: ModelPredicate<T>,
|
|
336
|
+
pagination?: PaginationInput<T>
|
|
337
|
+
) {
|
|
338
|
+
const storeName = this.getStorenameForModel(modelConstructor);
|
|
339
|
+
const namespaceName = this.namespaceResolver(
|
|
340
|
+
modelConstructor
|
|
341
|
+
) as NAMESPACES;
|
|
342
|
+
|
|
343
|
+
const predicates =
|
|
344
|
+
predicate && ModelPredicateCreator.getPredicates(predicate);
|
|
345
|
+
const keyPath = getIndexKeys(
|
|
346
|
+
this.schema.namespaces[namespaceName],
|
|
347
|
+
modelConstructor.name
|
|
348
|
+
);
|
|
349
|
+
const queryByKey =
|
|
350
|
+
predicates && this.keyValueFromPredicate(predicates, keyPath);
|
|
351
|
+
|
|
352
|
+
const hasSort = pagination && pagination.sort;
|
|
353
|
+
const hasPagination = pagination && pagination.limit;
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
storeName,
|
|
357
|
+
namespaceName,
|
|
358
|
+
queryByKey,
|
|
359
|
+
predicates,
|
|
360
|
+
hasSort,
|
|
361
|
+
hasPagination,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Delete record
|
|
367
|
+
* Cascades to related records (for Has One and Has Many relationships)
|
|
368
|
+
*
|
|
369
|
+
* @param modelOrModelConstructor
|
|
370
|
+
* @param condition
|
|
371
|
+
* @returns
|
|
372
|
+
*/
|
|
373
|
+
public async delete<T extends PersistentModel>(
|
|
374
|
+
modelOrModelConstructor: T | PersistentModelConstructor<T>,
|
|
375
|
+
condition?: ModelPredicate<T>
|
|
376
|
+
): Promise<[T[], T[]]> {
|
|
377
|
+
await this.preOpCheck();
|
|
378
|
+
|
|
379
|
+
const deleteQueue: { storeName: string; items: T[] }[] = [];
|
|
380
|
+
|
|
381
|
+
if (isModelConstructor(modelOrModelConstructor)) {
|
|
382
|
+
const modelConstructor =
|
|
383
|
+
modelOrModelConstructor as PersistentModelConstructor<T>;
|
|
384
|
+
const namespace = this.namespaceResolver(modelConstructor) as NAMESPACES;
|
|
385
|
+
|
|
386
|
+
const models = await this.query(modelConstructor, condition);
|
|
387
|
+
const relations =
|
|
388
|
+
this.schema.namespaces![namespace].relationships![modelConstructor.name]
|
|
389
|
+
.relationTypes;
|
|
390
|
+
|
|
391
|
+
if (condition !== undefined) {
|
|
392
|
+
await this.deleteTraverse(
|
|
393
|
+
relations,
|
|
394
|
+
models,
|
|
395
|
+
modelConstructor.name,
|
|
396
|
+
namespace,
|
|
397
|
+
deleteQueue
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
await this.deleteItem(deleteQueue);
|
|
401
|
+
|
|
402
|
+
const deletedModels = deleteQueue.reduce(
|
|
403
|
+
(acc, { items }) => acc.concat(items),
|
|
404
|
+
<T[]>[]
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
return [models, deletedModels];
|
|
408
|
+
} else {
|
|
409
|
+
await this.deleteTraverse(
|
|
410
|
+
relations,
|
|
411
|
+
models,
|
|
412
|
+
modelConstructor.name,
|
|
413
|
+
namespace,
|
|
414
|
+
deleteQueue
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
await this.deleteItem(deleteQueue);
|
|
418
|
+
|
|
419
|
+
const deletedModels = deleteQueue.reduce(
|
|
420
|
+
(acc, { items }) => acc.concat(items),
|
|
421
|
+
<T[]>[]
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
return [models, deletedModels];
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
const model = modelOrModelConstructor as T;
|
|
428
|
+
|
|
429
|
+
const modelConstructor = Object.getPrototypeOf(model)
|
|
430
|
+
.constructor as PersistentModelConstructor<T>;
|
|
431
|
+
const namespaceName = this.namespaceResolver(
|
|
432
|
+
modelConstructor
|
|
433
|
+
) as NAMESPACES;
|
|
434
|
+
|
|
435
|
+
const storeName = this.getStorenameForModel(modelConstructor);
|
|
436
|
+
|
|
437
|
+
if (condition) {
|
|
438
|
+
const keyValues = this.getIndexKeyValuesFromModel(model);
|
|
439
|
+
const fromDB = await this._get(storeName, keyValues);
|
|
440
|
+
|
|
441
|
+
if (fromDB === undefined) {
|
|
442
|
+
const msg = 'Model instance not found in storage';
|
|
443
|
+
logger.warn(msg, { model });
|
|
444
|
+
|
|
445
|
+
return [[model], []];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const predicates = ModelPredicateCreator.getPredicates(condition);
|
|
449
|
+
const { predicates: predicateObjs, type } =
|
|
450
|
+
predicates as PredicatesGroup<T>;
|
|
451
|
+
|
|
452
|
+
const isValid = validatePredicate(fromDB as T, type, predicateObjs);
|
|
453
|
+
if (!isValid) {
|
|
454
|
+
const msg = 'Conditional update failed';
|
|
455
|
+
logger.error(msg, { model: fromDB, condition: predicateObjs });
|
|
456
|
+
|
|
457
|
+
throw new Error(msg);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const relations =
|
|
461
|
+
this.schema.namespaces[namespaceName].relationships![
|
|
462
|
+
modelConstructor.name
|
|
463
|
+
].relationTypes;
|
|
464
|
+
|
|
465
|
+
await this.deleteTraverse(
|
|
466
|
+
relations,
|
|
467
|
+
[model],
|
|
468
|
+
modelConstructor.name,
|
|
469
|
+
namespaceName,
|
|
470
|
+
deleteQueue
|
|
471
|
+
);
|
|
472
|
+
} else {
|
|
473
|
+
const relations =
|
|
474
|
+
this.schema.namespaces[namespaceName].relationships![
|
|
475
|
+
modelConstructor.name
|
|
476
|
+
].relationTypes;
|
|
477
|
+
|
|
478
|
+
await this.deleteTraverse(
|
|
479
|
+
relations,
|
|
480
|
+
[model],
|
|
481
|
+
modelConstructor.name,
|
|
482
|
+
namespaceName,
|
|
483
|
+
deleteQueue
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
await this.deleteItem(deleteQueue);
|
|
487
|
+
|
|
488
|
+
const deletedModels = deleteQueue.reduce(
|
|
489
|
+
(acc, { items }) => acc.concat(items),
|
|
490
|
+
<T[]>[]
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
return [[model], deletedModels];
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
protected abstract deleteItem<T extends PersistentModel>(
|
|
498
|
+
deleteQueue?: {
|
|
499
|
+
storeName: string;
|
|
500
|
+
items: T[] | IDBValidKey[];
|
|
501
|
+
}[]
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
protected abstract getHasOneChild<T extends PersistentModel>(
|
|
505
|
+
model: T,
|
|
506
|
+
srcModel: string,
|
|
507
|
+
namespace: NAMESPACES,
|
|
508
|
+
rel: RelationType
|
|
509
|
+
): Promise<T | undefined>;
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Backwards compatability for pre-CPK codegen
|
|
513
|
+
* TODO - deprecate this in v6; will need to re-gen MIPR for older unit
|
|
514
|
+
* tests that hit this path
|
|
515
|
+
*/
|
|
516
|
+
protected abstract getHasOneChildLegacy<T extends PersistentModel>(
|
|
517
|
+
model: T,
|
|
518
|
+
srcModel: string,
|
|
519
|
+
namespace: NAMESPACES,
|
|
520
|
+
rel: RelationType
|
|
521
|
+
): Promise<T | undefined>;
|
|
522
|
+
|
|
523
|
+
protected abstract getHasManyChildren<T extends PersistentModel>(
|
|
524
|
+
storeName: string,
|
|
525
|
+
index: string,
|
|
526
|
+
keyValues: string[]
|
|
527
|
+
): Promise<T[] | undefined>;
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Recursively traverse relationship graph and add
|
|
531
|
+
* all Has One and Has Many relations to `deleteQueue` param
|
|
532
|
+
*
|
|
533
|
+
* Actual deletion of records added to `deleteQueue` occurs in the `delete` method
|
|
534
|
+
*
|
|
535
|
+
* @param relations
|
|
536
|
+
* @param models
|
|
537
|
+
* @param srcModel
|
|
538
|
+
* @param namespace
|
|
539
|
+
* @param deleteQueue
|
|
540
|
+
*/
|
|
541
|
+
protected async deleteTraverse<T extends PersistentModel>(
|
|
542
|
+
relations: RelationType[],
|
|
543
|
+
models: T[],
|
|
544
|
+
srcModel: string,
|
|
545
|
+
namespace: NAMESPACES,
|
|
546
|
+
deleteQueue: { storeName: string; items: T[] }[]
|
|
547
|
+
): Promise<void> {
|
|
548
|
+
for await (const rel of relations) {
|
|
549
|
+
const { modelName, relationType, targetNames, associatedWith } = rel;
|
|
550
|
+
|
|
551
|
+
const storeName = getStorename(namespace, modelName);
|
|
552
|
+
const index: string =
|
|
553
|
+
getIndex(
|
|
554
|
+
this.schema.namespaces[namespace].relationships![modelName]
|
|
555
|
+
.relationTypes,
|
|
556
|
+
srcModel
|
|
557
|
+
) ||
|
|
558
|
+
// if we were unable to find an index via relationTypes
|
|
559
|
+
// i.e. for keyName connections, attempt to find one by the
|
|
560
|
+
// associatedWith property
|
|
561
|
+
getIndexFromAssociation(
|
|
562
|
+
this.schema.namespaces[namespace].relationships![modelName].indexes,
|
|
563
|
+
associatedWith!
|
|
564
|
+
)!;
|
|
565
|
+
|
|
566
|
+
for await (const model of models) {
|
|
567
|
+
const childRecords: PersistentModel[] = [];
|
|
568
|
+
|
|
569
|
+
switch (relationType) {
|
|
570
|
+
case 'HAS_ONE':
|
|
571
|
+
let childRecord;
|
|
572
|
+
if (targetNames?.length) {
|
|
573
|
+
childRecord = await this.getHasOneChild(
|
|
574
|
+
model,
|
|
575
|
+
srcModel,
|
|
576
|
+
namespace,
|
|
577
|
+
rel
|
|
578
|
+
);
|
|
579
|
+
} else {
|
|
580
|
+
childRecord = await this.getHasOneChildLegacy(
|
|
581
|
+
model,
|
|
582
|
+
srcModel,
|
|
583
|
+
namespace,
|
|
584
|
+
rel
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (childRecord) {
|
|
589
|
+
childRecords.push(childRecord);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
break;
|
|
593
|
+
case 'HAS_MANY':
|
|
594
|
+
const keyValues: string[] = this.getIndexKeyValuesFromModel(model);
|
|
595
|
+
|
|
596
|
+
const records = await this.getHasManyChildren(
|
|
597
|
+
storeName,
|
|
598
|
+
index,
|
|
599
|
+
keyValues
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
if (records?.length) {
|
|
603
|
+
childRecords.push(...records);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
break;
|
|
607
|
+
case 'BELONGS_TO':
|
|
608
|
+
// Intentionally blank
|
|
609
|
+
break;
|
|
610
|
+
default:
|
|
611
|
+
throw new Error(`Invalid relation type ${relationType}`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// instantiate models before passing them to next recursive call
|
|
615
|
+
// necessary for extracting PK metadata in `getHasOneChild` and `getHasManyChildren`
|
|
616
|
+
const childModels = await this.load(namespace, modelName, childRecords);
|
|
617
|
+
|
|
618
|
+
await this.deleteTraverse(
|
|
619
|
+
this.schema.namespaces[namespace].relationships![modelName]
|
|
620
|
+
.relationTypes,
|
|
621
|
+
childModels,
|
|
622
|
+
modelName,
|
|
623
|
+
namespace,
|
|
624
|
+
deleteQueue
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
deleteQueue.push({
|
|
630
|
+
storeName: getStorename(namespace, srcModel),
|
|
631
|
+
items: models.map(record =>
|
|
632
|
+
this.modelInstanceCreator(
|
|
633
|
+
this.getModelConstructorByModelName!(namespace, srcModel),
|
|
634
|
+
record
|
|
635
|
+
)
|
|
636
|
+
),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|