@aws-amplify/datastore 3.7.3-webpack5.5 → 3.7.4-custom-pk.80
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 +153 -15
- package/README.md +154 -0
- package/dist/aws-amplify-datastore.js +4638 -2685
- package/dist/aws-amplify-datastore.js.map +1 -1
- package/dist/aws-amplify-datastore.min.js +25 -95
- package/dist/aws-amplify-datastore.min.js.map +1 -1
- package/lib/authModeStrategies/multiAuthStrategy.d.ts +2 -2
- package/lib/authModeStrategies/multiAuthStrategy.js +32 -26
- package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib/datastore/datastore.d.ts +17 -16
- package/lib/datastore/datastore.js +249 -78
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/index.d.ts +3 -19
- package/lib/predicates/index.d.ts +3 -2
- package/lib/predicates/index.js +12 -2
- package/lib/predicates/index.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
- package/lib/storage/adapter/AsyncStorageAdapter.js +140 -66
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
- package/lib/storage/adapter/AsyncStorageDatabase.js +57 -27
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/InMemoryStore.js +2 -2
- package/lib/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +7 -2
- package/lib/storage/adapter/IndexedDBAdapter.js +454 -252
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/index.d.ts +1 -1
- package/lib/storage/storage.d.ts +3 -3
- package/lib/storage/storage.js +106 -28
- package/lib/storage/storage.js.map +1 -1
- package/lib/sync/index.d.ts +23 -7
- package/lib/sync/index.js +18 -17
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/merger.d.ts +3 -3
- package/lib/sync/merger.js +8 -6
- package/lib/sync/merger.js.map +1 -1
- package/lib/sync/outbox.d.ts +2 -2
- package/lib/sync/outbox.js +12 -9
- package/lib/sync/outbox.js.map +1 -1
- package/lib/sync/processors/errorMaps.d.ts +17 -0
- package/lib/sync/processors/errorMaps.js +125 -0
- package/lib/sync/processors/errorMaps.js.map +1 -0
- package/lib/sync/processors/mutation.d.ts +22 -4
- package/lib/sync/processors/mutation.js +119 -51
- package/lib/sync/processors/mutation.js.map +1 -1
- package/lib/sync/processors/subscription.d.ts +4 -2
- package/lib/sync/processors/subscription.js +76 -41
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.d.ts +4 -2
- package/lib/sync/processors/sync.js +62 -27
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/sync/utils.d.ts +3 -2
- package/lib/sync/utils.js +58 -7
- package/lib/sync/utils.js.map +1 -1
- package/lib/types.d.ts +90 -34
- package/lib/types.js +31 -2
- package/lib/types.js.map +1 -1
- package/lib/util.d.ts +44 -33
- package/lib/util.js +210 -57
- package/lib/util.js.map +1 -1
- package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +2 -2
- package/lib-esm/authModeStrategies/multiAuthStrategy.js +32 -26
- package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib-esm/datastore/datastore.d.ts +17 -16
- package/lib-esm/datastore/datastore.js +251 -80
- package/lib-esm/datastore/datastore.js.map +1 -1
- package/lib-esm/index.d.ts +3 -19
- package/lib-esm/predicates/index.d.ts +3 -2
- package/lib-esm/predicates/index.js +13 -3
- package/lib-esm/predicates/index.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +140 -66
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js +57 -27
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/InMemoryStore.js +2 -2
- package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +7 -2
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +454 -252
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/index.d.ts +1 -1
- package/lib-esm/storage/storage.d.ts +3 -3
- package/lib-esm/storage/storage.js +106 -28
- package/lib-esm/storage/storage.js.map +1 -1
- package/lib-esm/sync/index.d.ts +23 -7
- package/lib-esm/sync/index.js +20 -19
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/merger.d.ts +3 -3
- package/lib-esm/sync/merger.js +8 -6
- package/lib-esm/sync/merger.js.map +1 -1
- package/lib-esm/sync/outbox.d.ts +2 -2
- package/lib-esm/sync/outbox.js +13 -10
- package/lib-esm/sync/outbox.js.map +1 -1
- package/lib-esm/sync/processors/errorMaps.d.ts +17 -0
- package/lib-esm/sync/processors/errorMaps.js +119 -0
- package/lib-esm/sync/processors/errorMaps.js.map +1 -0
- package/lib-esm/sync/processors/mutation.d.ts +22 -4
- package/lib-esm/sync/processors/mutation.js +122 -54
- package/lib-esm/sync/processors/mutation.js.map +1 -1
- package/lib-esm/sync/processors/subscription.d.ts +4 -2
- package/lib-esm/sync/processors/subscription.js +77 -42
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.d.ts +4 -2
- package/lib-esm/sync/processors/sync.js +62 -27
- package/lib-esm/sync/processors/sync.js.map +1 -1
- package/lib-esm/sync/utils.d.ts +3 -2
- package/lib-esm/sync/utils.js +58 -8
- package/lib-esm/sync/utils.js.map +1 -1
- package/lib-esm/types.d.ts +90 -34
- package/lib-esm/types.js +29 -3
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +44 -33
- package/lib-esm/util.js +199 -54
- package/lib-esm/util.js.map +1 -1
- package/package.json +10 -9
- package/src/authModeStrategies/multiAuthStrategy.ts +24 -21
- package/src/datastore/datastore.ts +395 -143
- package/src/predicates/index.ts +32 -10
- package/src/storage/adapter/AsyncStorageAdapter.ts +161 -61
- package/src/storage/adapter/AsyncStorageDatabase.ts +62 -25
- package/src/storage/adapter/InMemoryStore.ts +2 -2
- package/src/storage/adapter/IndexedDBAdapter.ts +345 -101
- package/src/storage/adapter/index.ts +1 -1
- package/src/storage/storage.ts +85 -24
- package/src/sync/index.ts +74 -58
- package/src/sync/merger.ts +16 -4
- package/src/sync/outbox.ts +22 -8
- package/src/sync/processors/errorMaps.ts +93 -0
- package/src/sync/processors/mutation.ts +143 -84
- package/src/sync/processors/subscription.ts +67 -36
- package/src/sync/processors/sync.ts +37 -12
- package/src/sync/utils.ts +67 -16
- package/src/types.ts +252 -50
- package/src/util.ts +241 -42
- package/ssr/package.json +1 -1
|
@@ -29,13 +29,13 @@ import {
|
|
|
29
29
|
traverseModel,
|
|
30
30
|
validatePredicate,
|
|
31
31
|
sortCompareFunction,
|
|
32
|
+
extractTargetNamesFromSrc,
|
|
32
33
|
} from '../../util';
|
|
33
34
|
import { Adapter } from './index';
|
|
34
35
|
|
|
35
36
|
const logger = new Logger('DataStore');
|
|
36
37
|
|
|
37
38
|
const DB_NAME = 'amplify-datastore';
|
|
38
|
-
|
|
39
39
|
class IndexedDBAdapter implements Adapter {
|
|
40
40
|
private schema: InternalSchema;
|
|
41
41
|
private namespaceResolver: NamespaceResolver;
|
|
@@ -79,6 +79,45 @@ class IndexedDBAdapter implements Adapter {
|
|
|
79
79
|
return storeName;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
private getNamespaceAndModelFromStorename(storeName: string) {
|
|
83
|
+
const [namespaceName, ...modelNameArr] = storeName.split('_');
|
|
84
|
+
return {
|
|
85
|
+
namespaceName,
|
|
86
|
+
modelName: modelNameArr.join('_'),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private getIndexKeyPath(namespaceName: string, modelName: string): string[] {
|
|
91
|
+
const namespace = this.schema.namespaces[namespaceName];
|
|
92
|
+
|
|
93
|
+
const keyPath = namespace?.keys[modelName]?.primaryKey;
|
|
94
|
+
|
|
95
|
+
if (keyPath) {
|
|
96
|
+
return keyPath;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return ['id'];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private getIndexKeyValues<T extends PersistentModel>(model: T): string[] {
|
|
103
|
+
const modelConstructor = Object.getPrototypeOf(model)
|
|
104
|
+
.constructor as PersistentModelConstructor<T>;
|
|
105
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
106
|
+
|
|
107
|
+
const keys = this.getIndexKeyPath(namespaceName, modelConstructor.name);
|
|
108
|
+
|
|
109
|
+
const keyValues = keys.map(field => model[field]);
|
|
110
|
+
return keyValues;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private keysEqual(keysA, keysB): boolean {
|
|
114
|
+
if (keysA.length !== keysB.length) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return keysA.every((key, idx) => key === keysB[idx]);
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
async setUp(
|
|
83
122
|
theSchema: InternalSchema,
|
|
84
123
|
namespaceResolver: NamespaceResolver,
|
|
@@ -90,6 +129,7 @@ class IndexedDBAdapter implements Adapter {
|
|
|
90
129
|
sessionId?: string
|
|
91
130
|
) {
|
|
92
131
|
await this.checkPrivate();
|
|
132
|
+
|
|
93
133
|
if (!this.initPromise) {
|
|
94
134
|
this.initPromise = new Promise((res, rej) => {
|
|
95
135
|
this.resolve = res;
|
|
@@ -108,7 +148,7 @@ class IndexedDBAdapter implements Adapter {
|
|
|
108
148
|
|
|
109
149
|
try {
|
|
110
150
|
if (!this.db) {
|
|
111
|
-
const VERSION =
|
|
151
|
+
const VERSION = 3;
|
|
112
152
|
this.db = await idb.openDB(this.dbName, VERSION, {
|
|
113
153
|
upgrade: async (db, oldVersion, newVersion, txn) => {
|
|
114
154
|
if (oldVersion === 0) {
|
|
@@ -117,24 +157,19 @@ class IndexedDBAdapter implements Adapter {
|
|
|
117
157
|
|
|
118
158
|
Object.keys(namespace.models).forEach(modelName => {
|
|
119
159
|
const storeName = this.getStorename(namespaceName, modelName);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
modelName
|
|
127
|
-
].indexes;
|
|
128
|
-
indexes.forEach(index => store.createIndex(index, index));
|
|
129
|
-
|
|
130
|
-
store.createIndex('byId', 'id', { unique: true });
|
|
160
|
+
this.createObjectStoreForModel(
|
|
161
|
+
db,
|
|
162
|
+
namespaceName,
|
|
163
|
+
storeName,
|
|
164
|
+
modelName
|
|
165
|
+
);
|
|
131
166
|
});
|
|
132
167
|
});
|
|
133
168
|
|
|
134
169
|
return;
|
|
135
170
|
}
|
|
136
171
|
|
|
137
|
-
if (oldVersion === 1 && newVersion ===
|
|
172
|
+
if ((oldVersion === 1 || oldVersion === 2) && newVersion === 3) {
|
|
138
173
|
try {
|
|
139
174
|
for (const storeName of txn.objectStoreNames) {
|
|
140
175
|
const origStore = txn.objectStore(storeName);
|
|
@@ -149,7 +184,26 @@ class IndexedDBAdapter implements Adapter {
|
|
|
149
184
|
autoIncrement: true,
|
|
150
185
|
});
|
|
151
186
|
|
|
152
|
-
|
|
187
|
+
const origIndexNames = Object.values(origStore.indexNames);
|
|
188
|
+
for (const idxName of origIndexNames) {
|
|
189
|
+
if (idxName === 'byId') {
|
|
190
|
+
// don't migrate this index. It'll be replaced with byPk
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const idx = origStore.index(idxName);
|
|
194
|
+
const { keyPath, unique } = idx;
|
|
195
|
+
|
|
196
|
+
newStore.createIndex(idxName, keyPath, { unique });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { namespaceName, modelName } =
|
|
200
|
+
this.getNamespaceAndModelFromStorename(storeName);
|
|
201
|
+
|
|
202
|
+
const keyPath = this.getIndexKeyPath(
|
|
203
|
+
namespaceName,
|
|
204
|
+
modelName
|
|
205
|
+
);
|
|
206
|
+
newStore.createIndex('byPk', keyPath, { unique: true });
|
|
153
207
|
|
|
154
208
|
let cursor = await origStore.openCursor();
|
|
155
209
|
let count = 0;
|
|
@@ -168,6 +222,31 @@ class IndexedDBAdapter implements Adapter {
|
|
|
168
222
|
|
|
169
223
|
logger.debug(`${count} ${storeName} records migrated`);
|
|
170
224
|
}
|
|
225
|
+
|
|
226
|
+
// add new models created after IndexedDB, but before migration
|
|
227
|
+
// this case may happen when a user has not opened an app for
|
|
228
|
+
// some time and a new model is added during that time
|
|
229
|
+
Object.keys(theSchema.namespaces).forEach(namespaceName => {
|
|
230
|
+
const namespace = theSchema.namespaces[namespaceName];
|
|
231
|
+
const objectStoreNames = new Set(txn.objectStoreNames);
|
|
232
|
+
|
|
233
|
+
Object.keys(namespace.models)
|
|
234
|
+
.map(modelName => {
|
|
235
|
+
return [
|
|
236
|
+
modelName,
|
|
237
|
+
this.getStorename(namespaceName, modelName),
|
|
238
|
+
];
|
|
239
|
+
})
|
|
240
|
+
.filter(([, storeName]) => !objectStoreNames.has(storeName))
|
|
241
|
+
.forEach(([modelName, storeName]) => {
|
|
242
|
+
this.createObjectStoreForModel(
|
|
243
|
+
db,
|
|
244
|
+
namespaceName,
|
|
245
|
+
storeName,
|
|
246
|
+
modelName
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
171
250
|
} catch (error) {
|
|
172
251
|
logger.error('Error migrating IndexedDB data', error);
|
|
173
252
|
txn.abort();
|
|
@@ -188,19 +267,19 @@ class IndexedDBAdapter implements Adapter {
|
|
|
188
267
|
|
|
189
268
|
private async _get<T>(
|
|
190
269
|
storeOrStoreName: idb.IDBPObjectStore | string,
|
|
191
|
-
|
|
270
|
+
keyArr: any[]
|
|
192
271
|
): Promise<T> {
|
|
193
272
|
let index: idb.IDBPIndex;
|
|
194
273
|
|
|
195
274
|
if (typeof storeOrStoreName === 'string') {
|
|
196
275
|
const storeName = storeOrStoreName;
|
|
197
|
-
index = this.db.transaction(storeName, 'readonly').store.index('
|
|
276
|
+
index = this.db.transaction(storeName, 'readonly').store.index('byPk');
|
|
198
277
|
} else {
|
|
199
278
|
const store = storeOrStoreName;
|
|
200
|
-
index = store.index('
|
|
279
|
+
index = store.index('byPk');
|
|
201
280
|
}
|
|
202
281
|
|
|
203
|
-
const result = await index.get(
|
|
282
|
+
const result = await index.get(keyArr);
|
|
204
283
|
|
|
205
284
|
return result;
|
|
206
285
|
}
|
|
@@ -213,30 +292,35 @@ class IndexedDBAdapter implements Adapter {
|
|
|
213
292
|
const modelConstructor = Object.getPrototypeOf(model)
|
|
214
293
|
.constructor as PersistentModelConstructor<T>;
|
|
215
294
|
const storeName = this.getStorenameForModel(modelConstructor);
|
|
295
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
296
|
+
|
|
216
297
|
const connectedModels = traverseModel(
|
|
217
298
|
modelConstructor.name,
|
|
218
299
|
model,
|
|
219
|
-
this.schema.namespaces[
|
|
300
|
+
this.schema.namespaces[namespaceName],
|
|
220
301
|
this.modelInstanceCreator,
|
|
221
302
|
this.getModelConstructorByModelName
|
|
222
303
|
);
|
|
223
|
-
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
224
304
|
|
|
225
305
|
const set = new Set<string>();
|
|
226
306
|
const connectionStoreNames = Object.values(connectedModels).map(
|
|
227
307
|
({ modelName, item, instance }) => {
|
|
228
308
|
const storeName = this.getStorename(namespaceName, modelName);
|
|
229
309
|
set.add(storeName);
|
|
230
|
-
|
|
310
|
+
const keys = this.getIndexKeyPath(namespaceName, modelName);
|
|
311
|
+
return { storeName, item, instance, keys };
|
|
231
312
|
}
|
|
232
313
|
);
|
|
314
|
+
|
|
233
315
|
const tx = this.db.transaction(
|
|
234
316
|
[storeName, ...Array.from(set.values())],
|
|
235
317
|
'readwrite'
|
|
236
318
|
);
|
|
237
319
|
const store = tx.objectStore(storeName);
|
|
238
320
|
|
|
239
|
-
const
|
|
321
|
+
const keyValues = this.getIndexKeyValues(model);
|
|
322
|
+
|
|
323
|
+
const fromDB = await this._get(store, keyValues);
|
|
240
324
|
|
|
241
325
|
if (condition && fromDB) {
|
|
242
326
|
const predicates = ModelPredicateCreator.getPredicates(condition);
|
|
@@ -255,17 +339,24 @@ class IndexedDBAdapter implements Adapter {
|
|
|
255
339
|
const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
|
|
256
340
|
|
|
257
341
|
for await (const resItem of connectionStoreNames) {
|
|
258
|
-
const { storeName, item, instance } = resItem;
|
|
342
|
+
const { storeName, item, instance, keys } = resItem;
|
|
259
343
|
const store = tx.objectStore(storeName);
|
|
260
|
-
const { id } = item;
|
|
261
344
|
|
|
262
|
-
const
|
|
345
|
+
const itemKeyValues = keys.map(key => {
|
|
346
|
+
const value = item[key];
|
|
347
|
+
return value;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const fromDB = <T>await this._get(store, itemKeyValues);
|
|
263
351
|
const opType: OpType =
|
|
264
352
|
fromDB === undefined ? OpType.INSERT : OpType.UPDATE;
|
|
265
353
|
|
|
354
|
+
const modelKeyValues = this.getIndexKeyValues(model);
|
|
355
|
+
const keysEqual = this.keysEqual(itemKeyValues, modelKeyValues);
|
|
356
|
+
|
|
266
357
|
// Even if the parent is an INSERT, the child might not be, so we need to get its key
|
|
267
|
-
if (
|
|
268
|
-
const key = await store.index('
|
|
358
|
+
if (keysEqual || opType === OpType.INSERT) {
|
|
359
|
+
const key = await store.index('byPk').getKey(itemKeyValues);
|
|
269
360
|
await store.put(item, key);
|
|
270
361
|
|
|
271
362
|
result.push([instance, opType]);
|
|
@@ -301,7 +392,8 @@ class IndexedDBAdapter implements Adapter {
|
|
|
301
392
|
const tx = this.db.transaction([...connectionStoreNames], 'readonly');
|
|
302
393
|
|
|
303
394
|
for await (const relation of relations) {
|
|
304
|
-
|
|
395
|
+
// target name, metadata, set by init
|
|
396
|
+
const { fieldName, modelName, targetName, targetNames } = relation;
|
|
305
397
|
const storeName = this.getStorename(namespaceName, modelName);
|
|
306
398
|
const store = tx.objectStore(storeName);
|
|
307
399
|
const modelConstructor = this.getModelConstructorByModelName(
|
|
@@ -312,35 +404,91 @@ class IndexedDBAdapter implements Adapter {
|
|
|
312
404
|
switch (relation.relationType) {
|
|
313
405
|
case 'HAS_ONE':
|
|
314
406
|
for await (const recordItem of records) {
|
|
315
|
-
|
|
316
|
-
if (
|
|
407
|
+
// POST CPK codegen changes:
|
|
408
|
+
if (targetNames?.length) {
|
|
409
|
+
let getByFields = [];
|
|
410
|
+
let allPresent;
|
|
411
|
+
// iterate through all targetnames to make sure they are all present in the recordItem
|
|
412
|
+
allPresent = targetNames.every(targetName => {
|
|
413
|
+
return recordItem[targetName] != null;
|
|
414
|
+
});
|
|
317
415
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
416
|
+
if (!allPresent) {
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
322
419
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
420
|
+
getByFields = targetNames as any;
|
|
421
|
+
|
|
422
|
+
// keys are the key values
|
|
423
|
+
const keys = getByFields.map(
|
|
424
|
+
getByField => recordItem[getByField]
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const connectionRecord = await this._get(store, keys);
|
|
428
|
+
|
|
429
|
+
recordItem[fieldName] =
|
|
430
|
+
connectionRecord &&
|
|
431
|
+
this.modelInstanceCreator(modelConstructor, connectionRecord);
|
|
432
|
+
} else {
|
|
433
|
+
// If single target name, using old codegen
|
|
434
|
+
const getByfield = recordItem[targetName]
|
|
435
|
+
? targetName
|
|
436
|
+
: fieldName;
|
|
437
|
+
|
|
438
|
+
// We break here, because the recordItem does not have 'team', the `getByField`
|
|
439
|
+
// extract the keys on the related model.
|
|
440
|
+
if (!recordItem[getByfield]) break;
|
|
441
|
+
|
|
442
|
+
const key = [recordItem[getByfield]];
|
|
327
443
|
|
|
444
|
+
const connectionRecord = await this._get(store, key);
|
|
445
|
+
|
|
446
|
+
recordItem[fieldName] =
|
|
447
|
+
connectionRecord &&
|
|
448
|
+
this.modelInstanceCreator(modelConstructor, connectionRecord);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
328
451
|
break;
|
|
329
452
|
case 'BELONGS_TO':
|
|
330
453
|
for await (const recordItem of records) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
454
|
+
// POST CPK codegen changes:
|
|
455
|
+
if (targetNames?.length) {
|
|
456
|
+
let allPresent;
|
|
457
|
+
// iterate through all targetnames to make sure they are all present in the recordItem
|
|
458
|
+
allPresent = targetNames.every(targetName => {
|
|
459
|
+
return recordItem[targetName] != null;
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// If not present, there is not yet a connected record
|
|
463
|
+
if (!allPresent) {
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const keys = targetNames.map(
|
|
468
|
+
targetName => recordItem[targetName]
|
|
335
469
|
);
|
|
336
470
|
|
|
471
|
+
// Retrieve the connected record
|
|
472
|
+
const connectionRecord = await this._get(store, keys);
|
|
473
|
+
|
|
474
|
+
recordItem[fieldName] =
|
|
475
|
+
connectionRecord &&
|
|
476
|
+
this.modelInstanceCreator(modelConstructor, connectionRecord);
|
|
477
|
+
|
|
478
|
+
targetNames?.map(targetName => {
|
|
479
|
+
delete recordItem[targetName];
|
|
480
|
+
});
|
|
481
|
+
} else if (recordItem[targetName]) {
|
|
482
|
+
const key = [recordItem[targetName]];
|
|
483
|
+
|
|
484
|
+
const connectionRecord = await this._get(store, key);
|
|
485
|
+
|
|
337
486
|
recordItem[fieldName] =
|
|
338
487
|
connectionRecord &&
|
|
339
488
|
this.modelInstanceCreator(modelConstructor, connectionRecord);
|
|
340
489
|
delete recordItem[targetName];
|
|
341
490
|
}
|
|
342
491
|
}
|
|
343
|
-
|
|
344
492
|
break;
|
|
345
493
|
case 'HAS_MANY':
|
|
346
494
|
// TODO: Lazy loading
|
|
@@ -367,13 +515,16 @@ class IndexedDBAdapter implements Adapter {
|
|
|
367
515
|
|
|
368
516
|
const predicates =
|
|
369
517
|
predicate && ModelPredicateCreator.getPredicates(predicate);
|
|
370
|
-
const
|
|
518
|
+
const keyPath = this.getIndexKeyPath(namespaceName, modelConstructor.name);
|
|
519
|
+
const queryByKey =
|
|
520
|
+
predicates && this.keyValueFromPredicate(predicates, keyPath);
|
|
521
|
+
|
|
371
522
|
const hasSort = pagination && pagination.sort;
|
|
372
523
|
const hasPagination = pagination && pagination.limit;
|
|
373
524
|
|
|
374
525
|
const records: T[] = await (async () => {
|
|
375
|
-
if (
|
|
376
|
-
const record = await this.
|
|
526
|
+
if (queryByKey) {
|
|
527
|
+
const record = await this.getByKey(storeName, queryByKey);
|
|
377
528
|
return record ? [record] : [];
|
|
378
529
|
}
|
|
379
530
|
|
|
@@ -397,31 +548,41 @@ class IndexedDBAdapter implements Adapter {
|
|
|
397
548
|
return await this.load(namespaceName, modelConstructor.name, records);
|
|
398
549
|
}
|
|
399
550
|
|
|
400
|
-
private async
|
|
551
|
+
private async getByKey<T extends PersistentModel>(
|
|
401
552
|
storeName: string,
|
|
402
|
-
|
|
553
|
+
keyValue: string[]
|
|
403
554
|
): Promise<T> {
|
|
404
|
-
const record = <T>await this._get(storeName,
|
|
555
|
+
const record = <T>await this._get(storeName, keyValue);
|
|
405
556
|
return record;
|
|
406
557
|
}
|
|
407
558
|
|
|
408
559
|
private async getAll<T extends PersistentModel>(
|
|
409
|
-
storeName:
|
|
560
|
+
storeName: any
|
|
410
561
|
): Promise<T[]> {
|
|
411
562
|
return await this.db.getAll(storeName);
|
|
412
563
|
}
|
|
413
564
|
|
|
414
|
-
private
|
|
415
|
-
predicates: PredicatesGroup<T
|
|
416
|
-
|
|
565
|
+
private keyValueFromPredicate<T extends PersistentModel>(
|
|
566
|
+
predicates: PredicatesGroup<T>,
|
|
567
|
+
keyPath: string[]
|
|
568
|
+
): string[] | undefined {
|
|
417
569
|
const { predicates: predicateObjs } = predicates;
|
|
418
|
-
const idPredicate =
|
|
419
|
-
predicateObjs.length === 1 &&
|
|
420
|
-
(predicateObjs.find(
|
|
421
|
-
p => isPredicateObj(p) && p.field === 'id' && p.operator === 'eq'
|
|
422
|
-
) as PredicateObject<T>);
|
|
423
570
|
|
|
424
|
-
|
|
571
|
+
if (predicateObjs.length !== keyPath.length) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const keyValues = [];
|
|
576
|
+
|
|
577
|
+
for (const key of keyPath) {
|
|
578
|
+
const predicateObj = predicateObjs.find(
|
|
579
|
+
p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
|
|
580
|
+
) as PredicateObject<T>;
|
|
581
|
+
|
|
582
|
+
predicateObj && keyValues.push(predicateObj.operand);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return keyValues.length === keyPath.length ? keyValues : undefined;
|
|
425
586
|
}
|
|
426
587
|
|
|
427
588
|
private async filterOnPredicate<T extends PersistentModel>(
|
|
@@ -584,15 +745,16 @@ class IndexedDBAdapter implements Adapter {
|
|
|
584
745
|
|
|
585
746
|
const modelConstructor = Object.getPrototypeOf(model)
|
|
586
747
|
.constructor as PersistentModelConstructor<T>;
|
|
587
|
-
const
|
|
748
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
588
749
|
|
|
589
750
|
const storeName = this.getStorenameForModel(modelConstructor);
|
|
590
751
|
|
|
591
752
|
if (condition) {
|
|
592
753
|
const tx = this.db.transaction([storeName], 'readwrite');
|
|
593
754
|
const store = tx.objectStore(storeName);
|
|
755
|
+
const keyValues = this.getIndexKeyValues(model);
|
|
594
756
|
|
|
595
|
-
const fromDB = await this._get(store,
|
|
757
|
+
const fromDB = await this._get(store, keyValues);
|
|
596
758
|
|
|
597
759
|
if (fromDB === undefined) {
|
|
598
760
|
const msg = 'Model instance not found in storage';
|
|
@@ -615,26 +777,28 @@ class IndexedDBAdapter implements Adapter {
|
|
|
615
777
|
await tx.done;
|
|
616
778
|
|
|
617
779
|
const relations =
|
|
618
|
-
this.schema.namespaces[
|
|
619
|
-
.
|
|
780
|
+
this.schema.namespaces[namespaceName].relationships[
|
|
781
|
+
modelConstructor.name
|
|
782
|
+
].relationTypes;
|
|
620
783
|
|
|
621
784
|
await this.deleteTraverse(
|
|
622
785
|
relations,
|
|
623
786
|
[model],
|
|
624
787
|
modelConstructor.name,
|
|
625
|
-
|
|
788
|
+
namespaceName,
|
|
626
789
|
deleteQueue
|
|
627
790
|
);
|
|
628
791
|
} else {
|
|
629
792
|
const relations =
|
|
630
|
-
this.schema.namespaces[
|
|
631
|
-
.
|
|
793
|
+
this.schema.namespaces[namespaceName].relationships[
|
|
794
|
+
modelConstructor.name
|
|
795
|
+
].relationTypes;
|
|
632
796
|
|
|
633
797
|
await this.deleteTraverse(
|
|
634
798
|
relations,
|
|
635
799
|
[model],
|
|
636
800
|
modelConstructor.name,
|
|
637
|
-
|
|
801
|
+
namespaceName,
|
|
638
802
|
deleteQueue
|
|
639
803
|
);
|
|
640
804
|
}
|
|
@@ -651,7 +815,10 @@ class IndexedDBAdapter implements Adapter {
|
|
|
651
815
|
}
|
|
652
816
|
|
|
653
817
|
private async deleteItem<T extends PersistentModel>(
|
|
654
|
-
deleteQueue?: {
|
|
818
|
+
deleteQueue?: {
|
|
819
|
+
storeName: string;
|
|
820
|
+
items: T[] | IDBValidKey[];
|
|
821
|
+
}[]
|
|
655
822
|
) {
|
|
656
823
|
const connectionStoreNames = deleteQueue.map(({ storeName }) => {
|
|
657
824
|
return storeName;
|
|
@@ -667,9 +834,11 @@ class IndexedDBAdapter implements Adapter {
|
|
|
667
834
|
let key: IDBValidKey;
|
|
668
835
|
|
|
669
836
|
if (typeof item === 'object') {
|
|
670
|
-
|
|
837
|
+
const keyValues = this.getIndexKeyValues(item as T);
|
|
838
|
+
key = await store.index('byPk').getKey(keyValues);
|
|
671
839
|
} else {
|
|
672
|
-
|
|
840
|
+
const itemKey = [item.toString()];
|
|
841
|
+
key = await store.index('byPk').getKey([itemKey]);
|
|
673
842
|
}
|
|
674
843
|
|
|
675
844
|
if (key !== undefined) {
|
|
@@ -688,10 +857,12 @@ class IndexedDBAdapter implements Adapter {
|
|
|
688
857
|
deleteQueue: { storeName: string; items: T[] }[]
|
|
689
858
|
): Promise<void> {
|
|
690
859
|
for await (const rel of relations) {
|
|
691
|
-
const { relationType, fieldName, modelName, targetName } =
|
|
860
|
+
const { relationType, fieldName, modelName, targetName, targetNames } =
|
|
861
|
+
rel;
|
|
862
|
+
|
|
692
863
|
const storeName = this.getStorename(nameSpace, modelName);
|
|
693
864
|
|
|
694
|
-
const index: string =
|
|
865
|
+
const index: string | string[] | undefined =
|
|
695
866
|
getIndex(
|
|
696
867
|
this.schema.namespaces[nameSpace].relationships[modelName]
|
|
697
868
|
.relationTypes,
|
|
@@ -705,40 +876,88 @@ class IndexedDBAdapter implements Adapter {
|
|
|
705
876
|
rel.associatedWith
|
|
706
877
|
);
|
|
707
878
|
|
|
879
|
+
// TODO: refactor
|
|
708
880
|
switch (relationType) {
|
|
709
881
|
case 'HAS_ONE':
|
|
710
882
|
for await (const model of models) {
|
|
711
|
-
const hasOneIndex = index || '
|
|
883
|
+
const hasOneIndex = index || 'byPk';
|
|
884
|
+
|
|
885
|
+
// CPK
|
|
886
|
+
if (targetNames && targetNames.length > 0) {
|
|
887
|
+
// iterate over targetNames array and see if each item is present in model object
|
|
888
|
+
// targetNames here being the keys for the CHILD model
|
|
889
|
+
const hasConnectedModelFields = targetNames.every(targetName =>
|
|
890
|
+
model.hasOwnProperty(targetName)
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
// PK / Composite key for the PARENT model
|
|
894
|
+
const keyValues = this.getIndexKeyValues(model);
|
|
895
|
+
|
|
896
|
+
let values = [];
|
|
897
|
+
|
|
898
|
+
if (hasConnectedModelFields) {
|
|
899
|
+
// Values will be that of the child model
|
|
900
|
+
values = targetNames.map(
|
|
901
|
+
targetName => model[targetName]
|
|
902
|
+
) as any;
|
|
903
|
+
} else {
|
|
904
|
+
// values will be that of the parent model
|
|
905
|
+
values = keyValues as any;
|
|
906
|
+
}
|
|
712
907
|
|
|
713
|
-
|
|
714
|
-
const value = hasOneCustomField ? model[targetName] : model.id;
|
|
715
|
-
if (!value) break;
|
|
908
|
+
if (values.length === 0) break;
|
|
716
909
|
|
|
717
|
-
|
|
718
|
-
await this.db
|
|
910
|
+
const recordToDelete = <T>await this.db
|
|
719
911
|
.transaction(storeName, 'readwrite')
|
|
720
912
|
.objectStore(storeName)
|
|
721
|
-
.index(hasOneIndex)
|
|
722
|
-
.get(
|
|
723
|
-
|
|
913
|
+
.index(hasOneIndex as string)
|
|
914
|
+
.get(values);
|
|
915
|
+
|
|
916
|
+
await this.deleteTraverse(
|
|
917
|
+
this.schema.namespaces[nameSpace].relationships[modelName]
|
|
918
|
+
.relationTypes,
|
|
919
|
+
recordToDelete ? [recordToDelete] : [],
|
|
920
|
+
modelName,
|
|
921
|
+
nameSpace,
|
|
922
|
+
deleteQueue
|
|
923
|
+
);
|
|
924
|
+
break;
|
|
925
|
+
} else {
|
|
926
|
+
// Backwards compatibility (TODO: combine with above)
|
|
927
|
+
const hasOneIndex = index || 'byPk';
|
|
724
928
|
|
|
725
|
-
|
|
726
|
-
this.
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
929
|
+
const hasOneCustomField = targetName in model;
|
|
930
|
+
const keyValues = this.getIndexKeyValues(model);
|
|
931
|
+
const value = hasOneCustomField ? [model[targetName]] : keyValues;
|
|
932
|
+
|
|
933
|
+
if (!value) break;
|
|
934
|
+
|
|
935
|
+
const recordToDelete = <T>await this.db
|
|
936
|
+
.transaction(storeName, 'readwrite')
|
|
937
|
+
.objectStore(storeName)
|
|
938
|
+
.index(hasOneIndex as string)
|
|
939
|
+
.get(value);
|
|
940
|
+
|
|
941
|
+
await this.deleteTraverse(
|
|
942
|
+
this.schema.namespaces[nameSpace].relationships[modelName]
|
|
943
|
+
.relationTypes,
|
|
944
|
+
recordToDelete ? [recordToDelete] : [],
|
|
945
|
+
modelName,
|
|
946
|
+
nameSpace,
|
|
947
|
+
deleteQueue
|
|
948
|
+
);
|
|
949
|
+
}
|
|
733
950
|
}
|
|
734
951
|
break;
|
|
735
952
|
case 'HAS_MANY':
|
|
736
953
|
for await (const model of models) {
|
|
954
|
+
const keyValues = this.getIndexKeyValues(model);
|
|
955
|
+
|
|
737
956
|
const childrenArray = await this.db
|
|
738
957
|
.transaction(storeName, 'readwrite')
|
|
739
958
|
.objectStore(storeName)
|
|
740
|
-
.index(index)
|
|
741
|
-
.getAll(
|
|
959
|
+
.index(index as string)
|
|
960
|
+
.getAll(keyValues);
|
|
742
961
|
|
|
743
962
|
await this.deleteTraverse(
|
|
744
963
|
this.schema.namespaces[nameSpace].relationships[modelName]
|
|
@@ -799,22 +1018,29 @@ class IndexedDBAdapter implements Adapter {
|
|
|
799
1018
|
const store = txn.store;
|
|
800
1019
|
|
|
801
1020
|
for (const item of items) {
|
|
1021
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
1022
|
+
const modelName = modelConstructor.name;
|
|
1023
|
+
const model = this.modelInstanceCreator(modelConstructor, item);
|
|
1024
|
+
|
|
802
1025
|
const connectedModels = traverseModel(
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
this.schema.namespaces[
|
|
1026
|
+
modelName,
|
|
1027
|
+
model,
|
|
1028
|
+
this.schema.namespaces[namespaceName],
|
|
806
1029
|
this.modelInstanceCreator,
|
|
807
1030
|
this.getModelConstructorByModelName
|
|
808
1031
|
);
|
|
809
1032
|
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
|
|
1033
|
+
const keyValues = this.getIndexKeyValues(model);
|
|
1034
|
+
const { _deleted } = item;
|
|
1035
|
+
|
|
1036
|
+
const index = store.index('byPk');
|
|
1037
|
+
const key = await index.getKey(keyValues);
|
|
813
1038
|
|
|
814
1039
|
if (!_deleted) {
|
|
815
|
-
const { instance } = connectedModels.find(
|
|
816
|
-
|
|
817
|
-
|
|
1040
|
+
const { instance } = connectedModels.find(({ instance }) => {
|
|
1041
|
+
const instanceKeyValues = this.getIndexKeyValues(instance);
|
|
1042
|
+
return this.keysEqual(instanceKeyValues, keyValues);
|
|
1043
|
+
});
|
|
818
1044
|
|
|
819
1045
|
result.push([
|
|
820
1046
|
<T>(<unknown>instance),
|
|
@@ -834,6 +1060,24 @@ class IndexedDBAdapter implements Adapter {
|
|
|
834
1060
|
|
|
835
1061
|
return result;
|
|
836
1062
|
}
|
|
1063
|
+
|
|
1064
|
+
private async createObjectStoreForModel(
|
|
1065
|
+
db: idb.IDBPDatabase,
|
|
1066
|
+
namespaceName: string,
|
|
1067
|
+
storeName: string,
|
|
1068
|
+
modelName: string
|
|
1069
|
+
) {
|
|
1070
|
+
const store = db.createObjectStore(storeName, {
|
|
1071
|
+
autoIncrement: true,
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
const { indexes } =
|
|
1075
|
+
this.schema.namespaces[namespaceName].relationships[modelName];
|
|
1076
|
+
|
|
1077
|
+
indexes.forEach(([idxName, keyPath, options]) => {
|
|
1078
|
+
store.createIndex(idxName, keyPath, options);
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
837
1081
|
}
|
|
838
1082
|
|
|
839
1083
|
export default new IndexedDBAdapter();
|