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