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