@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
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import { ConsoleLogger as Logger } from '@aws-amplify/core';
|
|
2
1
|
import AsyncStorageDatabase from './AsyncStorageDatabase';
|
|
3
|
-
import { Adapter } from './index';
|
|
4
|
-
import { ModelInstanceCreator } from '../../datastore/datastore';
|
|
5
|
-
import { ModelPredicateCreator } from '../../predicates';
|
|
6
2
|
import {
|
|
7
|
-
InternalSchema,
|
|
8
|
-
isPredicateObj,
|
|
9
3
|
ModelInstanceMetadata,
|
|
10
4
|
ModelPredicate,
|
|
11
|
-
NamespaceResolver,
|
|
12
5
|
OpType,
|
|
13
6
|
PaginationInput,
|
|
14
7
|
PersistentModel,
|
|
15
8
|
PersistentModelConstructor,
|
|
16
|
-
PredicateObject,
|
|
17
9
|
PredicatesGroup,
|
|
18
10
|
QueryOne,
|
|
19
11
|
RelationType,
|
|
@@ -22,7 +14,6 @@ import {
|
|
|
22
14
|
DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR,
|
|
23
15
|
getIndex,
|
|
24
16
|
getIndexFromAssociation,
|
|
25
|
-
isModelConstructor,
|
|
26
17
|
traverseModel,
|
|
27
18
|
validatePredicate,
|
|
28
19
|
inMemoryPagination,
|
|
@@ -30,162 +21,114 @@ import {
|
|
|
30
21
|
keysEqual,
|
|
31
22
|
getStorename,
|
|
32
23
|
getIndexKeys,
|
|
33
|
-
extractPrimaryKeyValues,
|
|
34
24
|
IDENTIFIER_KEY_SEPARATOR,
|
|
35
25
|
} from '../../util';
|
|
26
|
+
import { StorageAdapterBase } from './StorageAdapterBase';
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// TODO: rejigger for DI?
|
|
44
|
-
private schema!: InternalSchema;
|
|
45
|
-
private namespaceResolver!: NamespaceResolver;
|
|
46
|
-
private modelInstanceCreator!: ModelInstanceCreator;
|
|
47
|
-
private getModelConstructorByModelName!: (
|
|
48
|
-
namsespaceName: NAMESPACES,
|
|
49
|
-
modelName: string
|
|
50
|
-
) => PersistentModelConstructor<any>;
|
|
51
|
-
private db!: AsyncStorageDatabase;
|
|
52
|
-
private initPromise!: Promise<void>;
|
|
53
|
-
private resolve!: (value?: any) => void;
|
|
54
|
-
private reject!: (value?: any) => void;
|
|
55
|
-
|
|
56
|
-
private getStorenameForModel(
|
|
57
|
-
modelConstructor: PersistentModelConstructor<any>
|
|
58
|
-
) {
|
|
59
|
-
const namespace = this.namespaceResolver(modelConstructor);
|
|
60
|
-
const { name: modelName } = modelConstructor;
|
|
28
|
+
export class AsyncStorageAdapter extends StorageAdapterBase {
|
|
29
|
+
protected db!: AsyncStorageDatabase;
|
|
30
|
+
|
|
31
|
+
// no-ops for this adapter
|
|
32
|
+
protected async preSetUpChecks() {}
|
|
33
|
+
protected async preOpCheck() {}
|
|
61
34
|
|
|
62
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Open AsyncStorage database
|
|
37
|
+
* Create new DB if one doesn't exist
|
|
38
|
+
*
|
|
39
|
+
* Called by `StorageAdapterBase.setUp()`
|
|
40
|
+
*
|
|
41
|
+
* @returns AsyncStorageDatabase instance
|
|
42
|
+
*/
|
|
43
|
+
protected async initDb(): Promise<AsyncStorageDatabase> {
|
|
44
|
+
const db = new AsyncStorageDatabase();
|
|
45
|
+
await db.init();
|
|
46
|
+
return db;
|
|
63
47
|
}
|
|
64
48
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
49
|
+
async clear(): Promise<void> {
|
|
50
|
+
await this.db.clear();
|
|
51
|
+
|
|
52
|
+
this.db = undefined!;
|
|
53
|
+
this.initPromise = undefined!;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async batchSave<T extends PersistentModel>(
|
|
57
|
+
modelConstructor: PersistentModelConstructor<any>,
|
|
58
|
+
items: ModelInstanceMetadata[]
|
|
59
|
+
): Promise<[T, OpType][]> {
|
|
60
|
+
if (items.length === 0) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const modelName = modelConstructor.name;
|
|
71
65
|
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
const storeName = getStorename(namespaceName, modelName);
|
|
67
|
+
const keys = getIndexKeys(this.schema.namespaces[namespaceName], modelName);
|
|
68
|
+
const batch: ModelInstanceMetadata[] = [];
|
|
69
|
+
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
const model = this.modelInstanceCreator(modelConstructor, item);
|
|
76
72
|
|
|
77
|
-
|
|
73
|
+
const connectedModels = traverseModel(
|
|
74
|
+
modelName,
|
|
75
|
+
model,
|
|
76
|
+
this.schema.namespaces[namespaceName],
|
|
77
|
+
this.modelInstanceCreator,
|
|
78
|
+
this.getModelConstructorByModelName
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const keyValuesPath = this.getIndexKeyValuesPath(model);
|
|
82
|
+
|
|
83
|
+
const { instance } = connectedModels.find(({ instance }) => {
|
|
84
|
+
const instanceKeyValuesPath = this.getIndexKeyValuesPath(instance);
|
|
85
|
+
return keysEqual([instanceKeyValuesPath], [keyValuesPath]);
|
|
86
|
+
})!;
|
|
87
|
+
|
|
88
|
+
batch.push(instance);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return await this.db.batchSave(storeName, batch, keys);
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return this.getIndexKeyValuesFromModel(model).join(
|
|
94
|
+
protected async _get<T>(storeName: string, keyArr: string[]): Promise<T> {
|
|
95
|
+
const itemKeyValuesPath: string = keyArr.join(
|
|
83
96
|
DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
|
|
84
97
|
);
|
|
85
|
-
}
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
theSchema: InternalSchema,
|
|
89
|
-
namespaceResolver: NamespaceResolver,
|
|
90
|
-
modelInstanceCreator: ModelInstanceCreator,
|
|
91
|
-
getModelConstructorByModelName: (
|
|
92
|
-
namsespaceName: NAMESPACES,
|
|
93
|
-
modelName: string
|
|
94
|
-
) => PersistentModelConstructor<any>
|
|
95
|
-
) {
|
|
96
|
-
if (!this.initPromise) {
|
|
97
|
-
this.initPromise = new Promise((res, rej) => {
|
|
98
|
-
this.resolve = res;
|
|
99
|
-
this.reject = rej;
|
|
100
|
-
});
|
|
101
|
-
} else {
|
|
102
|
-
await this.initPromise;
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
this.schema = theSchema;
|
|
106
|
-
this.namespaceResolver = namespaceResolver;
|
|
107
|
-
this.modelInstanceCreator = modelInstanceCreator;
|
|
108
|
-
this.getModelConstructorByModelName = getModelConstructorByModelName;
|
|
109
|
-
try {
|
|
110
|
-
if (!this.db) {
|
|
111
|
-
this.db = new AsyncStorageDatabase();
|
|
112
|
-
await this.db.init();
|
|
113
|
-
this.resolve();
|
|
114
|
-
}
|
|
115
|
-
} catch (error) {
|
|
116
|
-
this.reject(error);
|
|
117
|
-
}
|
|
99
|
+
return <T>await this.db.get(itemKeyValuesPath, storeName);
|
|
118
100
|
}
|
|
119
101
|
|
|
120
102
|
async save<T extends PersistentModel>(
|
|
121
103
|
model: T,
|
|
122
104
|
condition?: ModelPredicate<T>
|
|
123
105
|
): Promise<[T, OpType.INSERT | OpType.UPDATE][]> {
|
|
124
|
-
const
|
|
125
|
-
.
|
|
126
|
-
const storeName = this.getStorenameForModel(modelConstructor);
|
|
127
|
-
|
|
128
|
-
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
106
|
+
const { storeName, connectionStoreNames, modelKeyValues } =
|
|
107
|
+
this.saveMetadata(model);
|
|
129
108
|
|
|
130
|
-
const
|
|
131
|
-
modelConstructor.name,
|
|
132
|
-
model,
|
|
133
|
-
this.schema.namespaces[namespaceName],
|
|
134
|
-
this.modelInstanceCreator,
|
|
135
|
-
this.getModelConstructorByModelName
|
|
136
|
-
);
|
|
109
|
+
const fromDB = await this._get(storeName, modelKeyValues);
|
|
137
110
|
|
|
138
|
-
|
|
139
|
-
const connectionStoreNames = Object.values(connectedModels).map(
|
|
140
|
-
({ modelName, item, instance }) => {
|
|
141
|
-
const storeName = getStorename(namespaceName, modelName);
|
|
142
|
-
set.add(storeName);
|
|
143
|
-
const keys = getIndexKeys(
|
|
144
|
-
this.schema.namespaces[namespaceName],
|
|
145
|
-
modelName
|
|
146
|
-
);
|
|
147
|
-
return { storeName, item, instance, keys };
|
|
148
|
-
}
|
|
149
|
-
);
|
|
150
|
-
const keyValuesPath = this.getIndexKeyValuesPath(model);
|
|
151
|
-
|
|
152
|
-
const fromDB = await this.db.get(keyValuesPath, storeName);
|
|
153
|
-
|
|
154
|
-
if (condition && fromDB) {
|
|
155
|
-
const predicates = ModelPredicateCreator.getPredicates(condition);
|
|
156
|
-
const { predicates: predicateObjs, type } = predicates!;
|
|
157
|
-
|
|
158
|
-
const isValid = validatePredicate(fromDB, type, predicateObjs);
|
|
159
|
-
|
|
160
|
-
if (!isValid) {
|
|
161
|
-
const msg = 'Conditional update failed';
|
|
162
|
-
logger.error(msg, { model: fromDB, condition: predicateObjs });
|
|
163
|
-
|
|
164
|
-
throw new Error(msg);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
111
|
+
this.validateSaveCondition(condition, fromDB);
|
|
167
112
|
|
|
168
113
|
const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
|
|
169
|
-
|
|
170
114
|
for await (const resItem of connectionStoreNames) {
|
|
171
115
|
const { storeName, item, instance, keys } = resItem;
|
|
172
116
|
|
|
173
|
-
/* Find the key values in the item, and concatenate them */
|
|
174
117
|
const itemKeyValues: string[] = keys.map(key => item[key]);
|
|
175
|
-
const itemKeyValuesPath: string = itemKeyValues.join(
|
|
176
|
-
DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
|
|
177
|
-
);
|
|
178
118
|
|
|
179
|
-
const fromDB = <T>await this.
|
|
119
|
+
const fromDB = <T>await this._get(storeName, itemKeyValues);
|
|
180
120
|
const opType: OpType = fromDB ? OpType.UPDATE : OpType.INSERT;
|
|
181
|
-
const modelKeyValues = this.getIndexKeyValuesFromModel(model);
|
|
182
121
|
|
|
183
|
-
// If item key values and model key values are equal, save to db
|
|
184
122
|
if (
|
|
185
123
|
keysEqual(itemKeyValues, modelKeyValues) ||
|
|
186
124
|
opType === OpType.INSERT
|
|
187
125
|
) {
|
|
188
|
-
await this.db.save(
|
|
126
|
+
await this.db.save(
|
|
127
|
+
item,
|
|
128
|
+
storeName,
|
|
129
|
+
keys,
|
|
130
|
+
itemKeyValues.join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR)
|
|
131
|
+
);
|
|
189
132
|
|
|
190
133
|
result.push([instance, opType]);
|
|
191
134
|
}
|
|
@@ -193,57 +136,24 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
193
136
|
return result;
|
|
194
137
|
}
|
|
195
138
|
|
|
196
|
-
private async load<T>(
|
|
197
|
-
namespaceName: NAMESPACES,
|
|
198
|
-
srcModelName: string,
|
|
199
|
-
records: T[]
|
|
200
|
-
): Promise<T[]> {
|
|
201
|
-
const namespace = this.schema.namespaces[namespaceName];
|
|
202
|
-
const relations = namespace.relationships![srcModelName].relationTypes;
|
|
203
|
-
const connectionStoreNames = relations.map(({ modelName }) => {
|
|
204
|
-
return getStorename(namespaceName, modelName);
|
|
205
|
-
});
|
|
206
|
-
const modelConstructor = this.getModelConstructorByModelName(
|
|
207
|
-
namespaceName,
|
|
208
|
-
srcModelName
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
if (connectionStoreNames.length === 0) {
|
|
212
|
-
return records.map(record =>
|
|
213
|
-
this.modelInstanceCreator(modelConstructor, record)
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return records.map(record =>
|
|
218
|
-
this.modelInstanceCreator(modelConstructor, record)
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
139
|
async query<T extends PersistentModel>(
|
|
223
140
|
modelConstructor: PersistentModelConstructor<T>,
|
|
224
141
|
predicate?: ModelPredicate<T>,
|
|
225
142
|
pagination?: PaginationInput<T>
|
|
226
143
|
): Promise<T[]> {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.schema.namespaces[namespaceName],
|
|
236
|
-
modelConstructor.name
|
|
237
|
-
);
|
|
238
|
-
const queryByKey =
|
|
239
|
-
predicates && this.keyValueFromPredicate(predicates, keys);
|
|
240
|
-
|
|
241
|
-
const hasSort = pagination && pagination.sort;
|
|
242
|
-
const hasPagination = pagination && pagination.limit;
|
|
144
|
+
const {
|
|
145
|
+
storeName,
|
|
146
|
+
namespaceName,
|
|
147
|
+
queryByKey,
|
|
148
|
+
predicates,
|
|
149
|
+
hasSort,
|
|
150
|
+
hasPagination,
|
|
151
|
+
} = this.queryMetadata(modelConstructor, predicate, pagination);
|
|
243
152
|
|
|
244
153
|
const records: T[] = (await (async () => {
|
|
245
154
|
if (queryByKey) {
|
|
246
|
-
const
|
|
155
|
+
const keyValues = queryByKey.join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
|
|
156
|
+
const record = await this.getByKey(storeName, keyValues);
|
|
247
157
|
return record ? [record] : [];
|
|
248
158
|
}
|
|
249
159
|
|
|
@@ -267,8 +177,7 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
267
177
|
storeName: string,
|
|
268
178
|
keyValuePath: string
|
|
269
179
|
): Promise<T> {
|
|
270
|
-
|
|
271
|
-
return record;
|
|
180
|
+
return <T>await this.db.get(keyValuePath, storeName);
|
|
272
181
|
}
|
|
273
182
|
|
|
274
183
|
private async getAll<T extends PersistentModel>(
|
|
@@ -277,31 +186,6 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
277
186
|
return await this.db.getAll(storeName);
|
|
278
187
|
}
|
|
279
188
|
|
|
280
|
-
private keyValueFromPredicate<T extends PersistentModel>(
|
|
281
|
-
predicates: PredicatesGroup<T>,
|
|
282
|
-
keys: string[]
|
|
283
|
-
): string | undefined {
|
|
284
|
-
const { predicates: predicateObjs } = predicates;
|
|
285
|
-
|
|
286
|
-
if (predicateObjs.length !== keys.length) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const keyValues = [] as any[];
|
|
291
|
-
|
|
292
|
-
for (const key of keys) {
|
|
293
|
-
const predicateObj = predicateObjs.find(
|
|
294
|
-
p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
|
|
295
|
-
) as PredicateObject<T>;
|
|
296
|
-
|
|
297
|
-
predicateObj && keyValues.push(predicateObj.operand);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return keyValues.length === keys.length
|
|
301
|
-
? keyValues.join(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR)
|
|
302
|
-
: undefined;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
189
|
private async filterOnPredicate<T extends PersistentModel>(
|
|
306
190
|
storeName: string,
|
|
307
191
|
predicates: PredicatesGroup<T>
|
|
@@ -334,129 +218,7 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
334
218
|
return result && this.modelInstanceCreator(modelConstructor, result);
|
|
335
219
|
}
|
|
336
220
|
|
|
337
|
-
async
|
|
338
|
-
modelOrModelConstructor: T | PersistentModelConstructor<T>,
|
|
339
|
-
condition?: ModelPredicate<T>
|
|
340
|
-
): Promise<[T[], T[]]> {
|
|
341
|
-
const deleteQueue: { storeName: string; items: T[] }[] = [];
|
|
342
|
-
|
|
343
|
-
if (isModelConstructor(modelOrModelConstructor)) {
|
|
344
|
-
const modelConstructor =
|
|
345
|
-
modelOrModelConstructor as PersistentModelConstructor<T>;
|
|
346
|
-
const nameSpace = this.namespaceResolver(modelConstructor) as NAMESPACES;
|
|
347
|
-
|
|
348
|
-
// models to be deleted.
|
|
349
|
-
const models = await this.query(modelConstructor, condition!);
|
|
350
|
-
// TODO: refactor this to use a function like getRelations()
|
|
351
|
-
const relations =
|
|
352
|
-
this.schema.namespaces[nameSpace].relationships![modelConstructor.name]
|
|
353
|
-
.relationTypes;
|
|
354
|
-
|
|
355
|
-
if (condition !== undefined) {
|
|
356
|
-
await this.deleteTraverse(
|
|
357
|
-
relations,
|
|
358
|
-
models,
|
|
359
|
-
modelConstructor.name,
|
|
360
|
-
nameSpace,
|
|
361
|
-
deleteQueue
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
await this.deleteItem(deleteQueue);
|
|
365
|
-
|
|
366
|
-
const deletedModels = deleteQueue.reduce(
|
|
367
|
-
(acc, { items }) => acc.concat(items),
|
|
368
|
-
<T[]>[]
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
return [models, deletedModels];
|
|
372
|
-
} else {
|
|
373
|
-
await this.deleteTraverse(
|
|
374
|
-
relations,
|
|
375
|
-
models,
|
|
376
|
-
modelConstructor.name,
|
|
377
|
-
nameSpace,
|
|
378
|
-
deleteQueue
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
await this.deleteItem(deleteQueue);
|
|
382
|
-
|
|
383
|
-
const deletedModels = deleteQueue.reduce(
|
|
384
|
-
(acc, { items }) => acc.concat(items),
|
|
385
|
-
<T[]>[]
|
|
386
|
-
);
|
|
387
|
-
|
|
388
|
-
return [models, deletedModels];
|
|
389
|
-
}
|
|
390
|
-
} else {
|
|
391
|
-
const model = modelOrModelConstructor as T;
|
|
392
|
-
|
|
393
|
-
const modelConstructor = Object.getPrototypeOf(model)
|
|
394
|
-
.constructor as PersistentModelConstructor<T>;
|
|
395
|
-
const nameSpace = this.namespaceResolver(modelConstructor) as NAMESPACES;
|
|
396
|
-
|
|
397
|
-
const storeName = this.getStorenameForModel(modelConstructor);
|
|
398
|
-
|
|
399
|
-
if (condition) {
|
|
400
|
-
const keyValuePath = this.getIndexKeyValuesPath(model);
|
|
401
|
-
|
|
402
|
-
const fromDB = await this.db.get(keyValuePath, storeName);
|
|
403
|
-
|
|
404
|
-
if (fromDB === undefined) {
|
|
405
|
-
const msg = 'Model instance not found in storage';
|
|
406
|
-
logger.warn(msg, { model });
|
|
407
|
-
|
|
408
|
-
return [[model], []];
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const predicates = ModelPredicateCreator.getPredicates(condition);
|
|
412
|
-
const { predicates: predicateObjs, type } = predicates!;
|
|
413
|
-
|
|
414
|
-
const isValid = validatePredicate(fromDB, type, predicateObjs);
|
|
415
|
-
if (!isValid) {
|
|
416
|
-
const msg = 'Conditional update failed';
|
|
417
|
-
logger.error(msg, { model: fromDB, condition: predicateObjs });
|
|
418
|
-
|
|
419
|
-
throw new Error(msg);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const relations =
|
|
423
|
-
this.schema.namespaces[nameSpace].relationships![
|
|
424
|
-
modelConstructor.name
|
|
425
|
-
].relationTypes;
|
|
426
|
-
await this.deleteTraverse(
|
|
427
|
-
relations,
|
|
428
|
-
[model],
|
|
429
|
-
modelConstructor.name,
|
|
430
|
-
nameSpace,
|
|
431
|
-
deleteQueue
|
|
432
|
-
);
|
|
433
|
-
} else {
|
|
434
|
-
const relations =
|
|
435
|
-
this.schema.namespaces[nameSpace].relationships![
|
|
436
|
-
modelConstructor.name
|
|
437
|
-
].relationTypes;
|
|
438
|
-
|
|
439
|
-
await this.deleteTraverse(
|
|
440
|
-
relations,
|
|
441
|
-
[model],
|
|
442
|
-
modelConstructor.name,
|
|
443
|
-
nameSpace,
|
|
444
|
-
deleteQueue
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
await this.deleteItem(deleteQueue);
|
|
449
|
-
|
|
450
|
-
const deletedModels = deleteQueue.reduce(
|
|
451
|
-
(acc, { items }) => acc.concat(items),
|
|
452
|
-
<T[]>[]
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
return [[model], deletedModels];
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
private async deleteItem<T extends PersistentModel>(
|
|
221
|
+
protected async deleteItem<T extends PersistentModel>(
|
|
460
222
|
deleteQueue?: { storeName: string; items: T[] | IDBValidKey[] }[]
|
|
461
223
|
) {
|
|
462
224
|
for await (const deleteItem of deleteQueue!) {
|
|
@@ -474,243 +236,177 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
474
236
|
}
|
|
475
237
|
|
|
476
238
|
/**
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
* @param
|
|
239
|
+
* Gets related Has One record for `model`
|
|
240
|
+
*
|
|
241
|
+
* @param model
|
|
480
242
|
* @param srcModel
|
|
481
|
-
* @param
|
|
482
|
-
* @param
|
|
243
|
+
* @param namespace
|
|
244
|
+
* @param rel
|
|
245
|
+
* @returns
|
|
483
246
|
*/
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
models: T[],
|
|
247
|
+
protected async getHasOneChild<T extends PersistentModel>(
|
|
248
|
+
model: T,
|
|
487
249
|
srcModel: string,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// if we were unable to find an index via relationTypes
|
|
508
|
-
// i.e. for keyName connections, attempt to find one by the
|
|
509
|
-
// associatedWith property
|
|
510
|
-
getIndexFromAssociation(
|
|
511
|
-
this.schema.namespaces[nameSpace].relationships![modelName].indexes,
|
|
512
|
-
rel.associatedWith!
|
|
513
|
-
);
|
|
250
|
+
namespace: NAMESPACES,
|
|
251
|
+
rel: RelationType
|
|
252
|
+
) {
|
|
253
|
+
let hasOneIndex;
|
|
254
|
+
const { modelName, targetNames, associatedWith } = rel;
|
|
255
|
+
const storeName = getStorename(namespace, modelName);
|
|
256
|
+
const index: string | undefined =
|
|
257
|
+
getIndex(
|
|
258
|
+
this.schema.namespaces[namespace].relationships![modelName]
|
|
259
|
+
.relationTypes,
|
|
260
|
+
srcModel
|
|
261
|
+
) ||
|
|
262
|
+
// if we were unable to find an index via relationTypes
|
|
263
|
+
// i.e. for keyName connections, attempt to find one by the
|
|
264
|
+
// associatedWith property
|
|
265
|
+
getIndexFromAssociation(
|
|
266
|
+
this.schema.namespaces[namespace].relationships![modelName].indexes,
|
|
267
|
+
rel.associatedWith!
|
|
268
|
+
);
|
|
514
269
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
hasOneIndex = index.split(IDENTIFIER_KEY_SEPARATOR);
|
|
523
|
-
} else if (associatedWith) {
|
|
524
|
-
if (Array.isArray(associatedWith)) {
|
|
525
|
-
hasOneIndex = associatedWith;
|
|
526
|
-
} else {
|
|
527
|
-
hasOneIndex = [associatedWith];
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// iterate over targetNames array and see if each key is present in model object
|
|
532
|
-
// targetNames here being the keys for the CHILD model
|
|
533
|
-
const hasConnectedModelFields = targetNames.every(targetName =>
|
|
534
|
-
model.hasOwnProperty(targetName)
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
// PK / Composite key for the parent model
|
|
538
|
-
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
539
|
-
|
|
540
|
-
let values;
|
|
541
|
-
|
|
542
|
-
const isUnidirectionalConnection = hasOneIndex === associatedWith;
|
|
543
|
-
|
|
544
|
-
if (hasConnectedModelFields && isUnidirectionalConnection) {
|
|
545
|
-
// Values will be that of the child model
|
|
546
|
-
values = targetNames
|
|
547
|
-
.filter(targetName => model[targetName] ?? false)
|
|
548
|
-
.map(targetName => model[targetName]) as any;
|
|
549
|
-
} else {
|
|
550
|
-
// values will be that of the parent model
|
|
551
|
-
values = keyValuesPath.split(
|
|
552
|
-
DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (values.length === 0) break;
|
|
557
|
-
|
|
558
|
-
const allRecords = await this.db.getAll(storeName);
|
|
559
|
-
|
|
560
|
-
let recordToDelete;
|
|
561
|
-
|
|
562
|
-
// values === targetNames
|
|
563
|
-
if (hasConnectedModelFields) {
|
|
564
|
-
/**
|
|
565
|
-
* Retrieve record by finding the record where all
|
|
566
|
-
* targetNames are present on the connected model.
|
|
567
|
-
*
|
|
568
|
-
*/
|
|
569
|
-
// recordToDelete = allRecords.filter(childItem =>
|
|
570
|
-
// values.every(value => childItem[value] != null)
|
|
571
|
-
// ) as T[];
|
|
572
|
-
|
|
573
|
-
recordToDelete = allRecords.filter(childItem =>
|
|
574
|
-
hasOneIndex.every(index => values.includes(childItem[index]))
|
|
575
|
-
);
|
|
576
|
-
} else {
|
|
577
|
-
// values === keyValuePath
|
|
578
|
-
recordToDelete = allRecords.filter(
|
|
579
|
-
childItem => childItem[hasOneIndex] === values
|
|
580
|
-
) as T[];
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
await this.deleteTraverse<T>(
|
|
584
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
585
|
-
.relationTypes,
|
|
586
|
-
recordToDelete,
|
|
587
|
-
modelName,
|
|
588
|
-
nameSpace,
|
|
589
|
-
deleteQueue
|
|
590
|
-
);
|
|
591
|
-
} else {
|
|
592
|
-
const hasOneIndex = index || associatedWith;
|
|
593
|
-
const hasOneCustomField = targetName! in model;
|
|
594
|
-
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
595
|
-
const value = hasOneCustomField
|
|
596
|
-
? model[targetName!]
|
|
597
|
-
: keyValuesPath;
|
|
598
|
-
|
|
599
|
-
if (!value) break;
|
|
600
|
-
|
|
601
|
-
const allRecords = await this.db.getAll(storeName);
|
|
602
|
-
|
|
603
|
-
const recordsToDelete = allRecords.filter(
|
|
604
|
-
childItem => childItem[hasOneIndex as string] === value
|
|
605
|
-
) as T[];
|
|
606
|
-
|
|
607
|
-
// instantiate models before passing to deleteTraverse
|
|
608
|
-
// necessary for extracting PK values via getIndexKeyValuesFromModel
|
|
609
|
-
const modelsToDelete = recordsToDelete.length
|
|
610
|
-
? await this.load(nameSpace, modelName, recordsToDelete)
|
|
611
|
-
: [];
|
|
612
|
-
|
|
613
|
-
await this.deleteTraverse<T>(
|
|
614
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
615
|
-
.relationTypes,
|
|
616
|
-
modelsToDelete,
|
|
617
|
-
modelName,
|
|
618
|
-
nameSpace,
|
|
619
|
-
deleteQueue
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
break;
|
|
624
|
-
case 'HAS_MANY':
|
|
625
|
-
for await (const model of models) {
|
|
626
|
-
// Key values for the parent model:
|
|
627
|
-
const keyValues: string[] = this.getIndexKeyValuesFromModel(model);
|
|
628
|
-
|
|
629
|
-
const allRecords = await this.db.getAll(storeName);
|
|
630
|
-
|
|
631
|
-
const indices = index!.split(IDENTIFIER_KEY_SEPARATOR);
|
|
632
|
-
|
|
633
|
-
const childRecords = allRecords.filter(childItem =>
|
|
634
|
-
indices.every(index => keyValues.includes(childItem[index]))
|
|
635
|
-
) as T[];
|
|
636
|
-
|
|
637
|
-
// instantiate models before passing to deleteTraverse
|
|
638
|
-
// necessary for extracting PK values via getIndexKeyValuesFromModel
|
|
639
|
-
const childModels = await this.load(
|
|
640
|
-
nameSpace,
|
|
641
|
-
modelName,
|
|
642
|
-
childRecords
|
|
643
|
-
);
|
|
644
|
-
|
|
645
|
-
await this.deleteTraverse<T>(
|
|
646
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
647
|
-
.relationTypes,
|
|
648
|
-
childModels,
|
|
649
|
-
modelName,
|
|
650
|
-
nameSpace,
|
|
651
|
-
deleteQueue
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
break;
|
|
655
|
-
case 'BELONGS_TO':
|
|
656
|
-
// Intentionally blank
|
|
657
|
-
break;
|
|
658
|
-
default:
|
|
659
|
-
throw new Error(`Invalid relationType ${relationType}`);
|
|
270
|
+
if (index) {
|
|
271
|
+
hasOneIndex = index.split(IDENTIFIER_KEY_SEPARATOR);
|
|
272
|
+
} else if (associatedWith) {
|
|
273
|
+
if (Array.isArray(associatedWith)) {
|
|
274
|
+
hasOneIndex = associatedWith;
|
|
275
|
+
} else {
|
|
276
|
+
hasOneIndex = [associatedWith];
|
|
660
277
|
}
|
|
661
278
|
}
|
|
662
279
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
record
|
|
669
|
-
)
|
|
670
|
-
),
|
|
671
|
-
});
|
|
672
|
-
}
|
|
280
|
+
// iterate over targetNames array and see if each key is present in model object
|
|
281
|
+
// targetNames here being the keys for the CHILD model
|
|
282
|
+
const hasConnectedModelFields = targetNames!.every(targetName =>
|
|
283
|
+
model.hasOwnProperty(targetName)
|
|
284
|
+
);
|
|
673
285
|
|
|
674
|
-
|
|
675
|
-
|
|
286
|
+
// PK / Composite key for the parent model
|
|
287
|
+
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
676
288
|
|
|
677
|
-
|
|
678
|
-
this.initPromise = undefined!;
|
|
679
|
-
}
|
|
289
|
+
let values;
|
|
680
290
|
|
|
681
|
-
|
|
682
|
-
modelConstructor: PersistentModelConstructor<any>,
|
|
683
|
-
items: ModelInstanceMetadata[]
|
|
684
|
-
): Promise<[T, OpType][]> {
|
|
685
|
-
const { name: modelName } = modelConstructor;
|
|
686
|
-
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
687
|
-
const storeName = getStorename(namespaceName, modelName);
|
|
688
|
-
const keys = getIndexKeys(this.schema.namespaces[namespaceName], modelName);
|
|
689
|
-
const batch: ModelInstanceMetadata[] = [];
|
|
291
|
+
const isUnidirectionalConnection = hasOneIndex === associatedWith;
|
|
690
292
|
|
|
691
|
-
|
|
692
|
-
|
|
293
|
+
if (hasConnectedModelFields && isUnidirectionalConnection) {
|
|
294
|
+
// Values will be that of the child model
|
|
295
|
+
values = targetNames!
|
|
296
|
+
.filter(targetName => model[targetName] ?? false)
|
|
297
|
+
.map(targetName => model[targetName]);
|
|
298
|
+
} else {
|
|
299
|
+
// values will be that of the parent model
|
|
300
|
+
values = keyValuesPath.split(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
|
|
301
|
+
}
|
|
693
302
|
|
|
694
|
-
|
|
695
|
-
modelName,
|
|
696
|
-
model,
|
|
697
|
-
this.schema.namespaces[namespaceName],
|
|
698
|
-
this.modelInstanceCreator,
|
|
699
|
-
this.getModelConstructorByModelName
|
|
700
|
-
);
|
|
303
|
+
if (values.length === 0) return;
|
|
701
304
|
|
|
702
|
-
|
|
305
|
+
const allRecords = await this.db.getAll(storeName);
|
|
703
306
|
|
|
704
|
-
|
|
705
|
-
const instanceKeyValuesPath = this.getIndexKeyValuesPath(instance);
|
|
706
|
-
return keysEqual([instanceKeyValuesPath], [keyValuesPath]);
|
|
707
|
-
})!;
|
|
307
|
+
let recordToDelete;
|
|
708
308
|
|
|
709
|
-
|
|
309
|
+
// values === targetNames
|
|
310
|
+
if (hasConnectedModelFields) {
|
|
311
|
+
/**
|
|
312
|
+
* Retrieve record by finding the record where all
|
|
313
|
+
* targetNames are present on the connected model.
|
|
314
|
+
*
|
|
315
|
+
*/
|
|
316
|
+
|
|
317
|
+
recordToDelete = allRecords.find(childItem =>
|
|
318
|
+
hasOneIndex.every(index => values.includes(childItem[index]))
|
|
319
|
+
);
|
|
320
|
+
} else {
|
|
321
|
+
// values === keyValuePath
|
|
322
|
+
recordToDelete = allRecords.find(
|
|
323
|
+
childItem => childItem[hasOneIndex] === values
|
|
324
|
+
) as T[];
|
|
710
325
|
}
|
|
711
326
|
|
|
712
|
-
return
|
|
327
|
+
return recordToDelete;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Backwards compatability for pre-CPK codegen
|
|
332
|
+
* TODO - deprecate this in v6; will need to re-gen MIPR for older unit
|
|
333
|
+
* tests that hit this path
|
|
334
|
+
*/
|
|
335
|
+
protected async getHasOneChildLegacy<T extends PersistentModel>(
|
|
336
|
+
model: T,
|
|
337
|
+
srcModel: string,
|
|
338
|
+
namespace: NAMESPACES,
|
|
339
|
+
rel: RelationType
|
|
340
|
+
) {
|
|
341
|
+
const { modelName, targetName, associatedWith } = rel;
|
|
342
|
+
const storeName = getStorename(namespace, modelName);
|
|
343
|
+
const index: string | undefined =
|
|
344
|
+
getIndex(
|
|
345
|
+
this.schema.namespaces[namespace].relationships![modelName]
|
|
346
|
+
.relationTypes,
|
|
347
|
+
srcModel
|
|
348
|
+
) ||
|
|
349
|
+
// if we were unable to find an index via relationTypes
|
|
350
|
+
// i.e. for keyName connections, attempt to find one by the
|
|
351
|
+
// associatedWith property
|
|
352
|
+
getIndexFromAssociation(
|
|
353
|
+
this.schema.namespaces[namespace].relationships![modelName].indexes,
|
|
354
|
+
rel.associatedWith!
|
|
355
|
+
);
|
|
356
|
+
const hasOneIndex = index || associatedWith;
|
|
357
|
+
const hasOneCustomField = targetName! in model;
|
|
358
|
+
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
359
|
+
const value = hasOneCustomField ? model[targetName!] : keyValuesPath;
|
|
360
|
+
|
|
361
|
+
if (!value) return;
|
|
362
|
+
|
|
363
|
+
const allRecords = await this.db.getAll(storeName);
|
|
364
|
+
|
|
365
|
+
const recordToDelete = allRecords.find(
|
|
366
|
+
childItem => childItem[hasOneIndex as string] === value
|
|
367
|
+
) as T;
|
|
368
|
+
|
|
369
|
+
return recordToDelete;
|
|
713
370
|
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Gets related Has Many records by given `storeName`, `index`, and `keyValues`
|
|
374
|
+
*
|
|
375
|
+
* @param storeName
|
|
376
|
+
* @param index
|
|
377
|
+
* @param keyValues
|
|
378
|
+
* @returns
|
|
379
|
+
*/
|
|
380
|
+
protected async getHasManyChildren<T extends PersistentModel>(
|
|
381
|
+
storeName: string,
|
|
382
|
+
index: string,
|
|
383
|
+
keyValues: string[]
|
|
384
|
+
): Promise<T[]> {
|
|
385
|
+
const allRecords = await this.db.getAll(storeName);
|
|
386
|
+
const indices = index!.split(IDENTIFIER_KEY_SEPARATOR);
|
|
387
|
+
|
|
388
|
+
const childRecords = allRecords.filter(childItem =>
|
|
389
|
+
indices.every(index => keyValues.includes(childItem[index]))
|
|
390
|
+
) as T[];
|
|
391
|
+
|
|
392
|
+
return childRecords;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
//#region platform-specific helper methods
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Retrieves concatenated primary key values from a model
|
|
399
|
+
*
|
|
400
|
+
* @param model
|
|
401
|
+
* @returns
|
|
402
|
+
*/
|
|
403
|
+
private getIndexKeyValuesPath<T extends PersistentModel>(model: T): string {
|
|
404
|
+
return this.getIndexKeyValuesFromModel(model).join(
|
|
405
|
+
DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
//#endregion
|
|
714
410
|
}
|
|
715
411
|
|
|
716
412
|
export default new AsyncStorageAdapter();
|