@aws-amplify/datastore 4.0.12 → 4.0.13-push-notification-dryrun.43
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/lib/datastore/datastore.d.ts +29 -3
- package/lib/datastore/datastore.js +308 -147
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/predicates/index.d.ts +77 -7
- package/lib/predicates/index.js +142 -122
- package/lib/predicates/index.js.map +1 -1
- package/lib/predicates/next.d.ts +51 -10
- package/lib/predicates/next.js +111 -91
- package/lib/predicates/next.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
- package/lib/storage/adapter/AsyncStorageAdapter.js +135 -532
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +28 -29
- package/lib/storage/adapter/IndexedDBAdapter.js +490 -885
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/StorageAdapterBase.d.ts +134 -0
- package/lib/storage/adapter/StorageAdapterBase.js +439 -0
- package/lib/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/lib/storage/relationship.d.ts +9 -0
- package/lib/storage/relationship.js +9 -0
- package/lib/storage/relationship.js.map +1 -1
- package/lib/storage/storage.d.ts +1 -1
- package/lib/storage/storage.js +4 -3
- package/lib/storage/storage.js.map +1 -1
- package/lib/sync/index.d.ts +15 -1
- package/lib/sync/index.js +80 -13
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/outbox.js +14 -7
- package/lib/sync/outbox.js.map +1 -1
- package/lib/sync/processors/mutation.d.ts +10 -1
- package/lib/sync/processors/mutation.js +33 -12
- package/lib/sync/processors/mutation.js.map +1 -1
- package/lib/sync/processors/subscription.d.ts +7 -1
- package/lib/sync/processors/subscription.js +196 -135
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.d.ts +1 -1
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/sync/utils.d.ts +66 -2
- package/lib/sync/utils.js +264 -16
- package/lib/sync/utils.js.map +1 -1
- package/lib/types.d.ts +9 -1
- package/lib/types.js.map +1 -1
- package/lib/util.d.ts +16 -0
- package/lib/util.js +31 -2
- package/lib/util.js.map +1 -1
- package/lib-esm/datastore/datastore.d.ts +29 -3
- package/lib-esm/datastore/datastore.js +310 -149
- package/lib-esm/datastore/datastore.js.map +1 -1
- package/lib-esm/predicates/index.d.ts +77 -7
- package/lib-esm/predicates/index.js +143 -123
- package/lib-esm/predicates/index.js.map +1 -1
- package/lib-esm/predicates/next.d.ts +51 -10
- package/lib-esm/predicates/next.js +111 -91
- package/lib-esm/predicates/next.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +28 -30
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +138 -535
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +28 -29
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +489 -884
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +134 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js +437 -0
- package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -0
- package/lib-esm/storage/relationship.d.ts +9 -0
- package/lib-esm/storage/relationship.js +9 -0
- package/lib-esm/storage/relationship.js.map +1 -1
- package/lib-esm/storage/storage.d.ts +1 -1
- package/lib-esm/storage/storage.js +4 -3
- package/lib-esm/storage/storage.js.map +1 -1
- package/lib-esm/sync/index.d.ts +15 -1
- package/lib-esm/sync/index.js +82 -15
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/outbox.js +14 -7
- package/lib-esm/sync/outbox.js.map +1 -1
- package/lib-esm/sync/processors/mutation.d.ts +10 -1
- package/lib-esm/sync/processors/mutation.js +33 -12
- package/lib-esm/sync/processors/mutation.js.map +1 -1
- package/lib-esm/sync/processors/subscription.d.ts +7 -1
- package/lib-esm/sync/processors/subscription.js +197 -136
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.d.ts +1 -1
- package/lib-esm/sync/processors/sync.js.map +1 -1
- package/lib-esm/sync/utils.d.ts +66 -2
- package/lib-esm/sync/utils.js +261 -18
- package/lib-esm/sync/utils.js.map +1 -1
- package/lib-esm/types.d.ts +9 -1
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +16 -0
- package/lib-esm/util.js +32 -3
- package/lib-esm/util.js.map +1 -1
- package/package.json +12 -11
- package/src/datastore/datastore.ts +288 -159
- package/src/predicates/index.ts +145 -175
- package/src/predicates/next.ts +114 -81
- package/src/storage/adapter/AsyncStorageAdapter.ts +97 -563
- package/src/storage/adapter/AsyncStorageDatabase.ts +2 -2
- package/src/storage/adapter/IndexedDBAdapter.ts +318 -770
- package/src/storage/adapter/StorageAdapterBase.ts +545 -0
- package/src/storage/relationship.ts +9 -0
- package/src/storage/storage.ts +12 -9
- package/src/sync/index.ts +108 -20
- package/src/sync/outbox.ts +17 -11
- package/src/sync/processors/mutation.ts +35 -4
- package/src/sync/processors/subscription.ts +124 -10
- package/src/sync/processors/sync.ts +4 -1
- package/src/sync/utils.ts +285 -15
- package/src/types.ts +15 -2
- package/src/util.ts +40 -1
- package/CHANGELOG.md +0 -904
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { ConsoleLogger as Logger } from '@aws-amplify/core';
|
|
2
2
|
import * as idb from 'idb';
|
|
3
|
-
import { ModelInstanceCreator } from '../../datastore/datastore';
|
|
4
|
-
import { ModelPredicateCreator } from '../../predicates';
|
|
5
3
|
import {
|
|
6
|
-
InternalSchema,
|
|
7
4
|
isPredicateObj,
|
|
8
5
|
isPredicateGroup,
|
|
9
6
|
ModelInstanceMetadata,
|
|
10
7
|
ModelPredicate,
|
|
11
|
-
NamespaceResolver,
|
|
12
8
|
OpType,
|
|
13
9
|
PaginationInput,
|
|
14
10
|
PersistentModel,
|
|
@@ -16,24 +12,17 @@ import {
|
|
|
16
12
|
PredicateObject,
|
|
17
13
|
PredicatesGroup,
|
|
18
14
|
QueryOne,
|
|
19
|
-
RelationType,
|
|
20
15
|
} from '../../types';
|
|
21
16
|
import {
|
|
22
|
-
getIndex,
|
|
23
|
-
getIndexFromAssociation,
|
|
24
|
-
isModelConstructor,
|
|
25
17
|
isPrivateMode,
|
|
26
18
|
traverseModel,
|
|
27
19
|
validatePredicate,
|
|
28
20
|
inMemoryPagination,
|
|
29
|
-
NAMESPACES,
|
|
30
21
|
keysEqual,
|
|
31
22
|
getStorename,
|
|
32
|
-
getIndexKeys,
|
|
33
|
-
extractPrimaryKeyValues,
|
|
34
23
|
isSafariCompatabilityMode,
|
|
35
24
|
} from '../../util';
|
|
36
|
-
import {
|
|
25
|
+
import { StorageAdapterBase } from './StorageAdapterBase';
|
|
37
26
|
|
|
38
27
|
const logger = new Logger('DataStore');
|
|
39
28
|
|
|
@@ -59,229 +48,135 @@ const logger = new Logger('DataStore');
|
|
|
59
48
|
*
|
|
60
49
|
*/
|
|
61
50
|
const MULTI_OR_CONDITION_SCAN_BREAKPOINT = 7;
|
|
51
|
+
//
|
|
52
|
+
const DB_VERSION = 3;
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
private schema!: InternalSchema;
|
|
66
|
-
private namespaceResolver!: NamespaceResolver;
|
|
67
|
-
private modelInstanceCreator!: ModelInstanceCreator;
|
|
68
|
-
private getModelConstructorByModelName?: (
|
|
69
|
-
namsespaceName: NAMESPACES,
|
|
70
|
-
modelName: string
|
|
71
|
-
) => PersistentModelConstructor<any>;
|
|
72
|
-
private db!: idb.IDBPDatabase;
|
|
73
|
-
private initPromise!: Promise<void>;
|
|
74
|
-
private resolve!: (value?: any) => void;
|
|
75
|
-
private reject!: (value?: any) => void;
|
|
76
|
-
private dbName: string = DB_NAME;
|
|
54
|
+
class IndexedDBAdapter extends StorageAdapterBase {
|
|
55
|
+
protected db!: idb.IDBPDatabase;
|
|
77
56
|
private safariCompatabilityMode: boolean = false;
|
|
78
57
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const { name: modelName } = modelConstructor;
|
|
84
|
-
|
|
85
|
-
return getStorename(namespace, modelName);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Retrieves primary key values from a model
|
|
89
|
-
private getIndexKeyValuesFromModel<T extends PersistentModel>(
|
|
90
|
-
model: T
|
|
91
|
-
): string[] {
|
|
92
|
-
const modelConstructor = Object.getPrototypeOf(model)
|
|
93
|
-
.constructor as PersistentModelConstructor<T>;
|
|
94
|
-
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
95
|
-
|
|
96
|
-
const keys = getIndexKeys(
|
|
97
|
-
this.schema.namespaces[namespaceName],
|
|
98
|
-
modelConstructor.name
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
return extractPrimaryKeyValues(model, keys);
|
|
58
|
+
// checks are called by StorageAdapterBase class
|
|
59
|
+
protected async preSetUpChecks() {
|
|
60
|
+
await this.checkPrivate();
|
|
61
|
+
await this.setSafariCompatabilityMode();
|
|
102
62
|
}
|
|
103
63
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return isPrivate;
|
|
107
|
-
});
|
|
108
|
-
if (isPrivate) {
|
|
109
|
-
logger.error("IndexedDB not supported in this browser's private mode");
|
|
110
|
-
return Promise.reject(
|
|
111
|
-
"IndexedDB not supported in this browser's private mode"
|
|
112
|
-
);
|
|
113
|
-
} else {
|
|
114
|
-
return Promise.resolve();
|
|
115
|
-
}
|
|
64
|
+
protected async preOpCheck() {
|
|
65
|
+
await this.checkPrivate();
|
|
116
66
|
}
|
|
117
67
|
|
|
118
68
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
69
|
+
* Initialize IndexedDB database
|
|
70
|
+
* Create new DB if one doesn't exist
|
|
71
|
+
* Upgrade outdated DB
|
|
121
72
|
*
|
|
122
|
-
*
|
|
123
|
-
* as scalars.
|
|
73
|
+
* Called by `StorageAdapterBase.setUp()`
|
|
124
74
|
*
|
|
125
|
-
*
|
|
126
|
-
* https://github.com/aws-amplify/amplify-js/pull/10527
|
|
75
|
+
* @returns IDB Database instance
|
|
127
76
|
*/
|
|
128
|
-
|
|
129
|
-
this.
|
|
77
|
+
protected async initDb(): Promise<idb.IDBPDatabase> {
|
|
78
|
+
return await idb.openDB(this.dbName, DB_VERSION, {
|
|
79
|
+
upgrade: async (db, oldVersion, newVersion, txn) => {
|
|
80
|
+
// create new database
|
|
81
|
+
if (oldVersion === 0) {
|
|
82
|
+
Object.keys(this.schema.namespaces).forEach(namespaceName => {
|
|
83
|
+
const namespace = this.schema.namespaces[namespaceName];
|
|
84
|
+
|
|
85
|
+
Object.keys(namespace.models).forEach(modelName => {
|
|
86
|
+
const storeName = getStorename(namespaceName, modelName);
|
|
87
|
+
this.createObjectStoreForModel(
|
|
88
|
+
db,
|
|
89
|
+
namespaceName,
|
|
90
|
+
storeName,
|
|
91
|
+
modelName
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
130
95
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
135
98
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
};
|
|
142
|
-
}
|
|
99
|
+
// migrate existing database to latest schema
|
|
100
|
+
if ((oldVersion === 1 || oldVersion === 2) && newVersion === 3) {
|
|
101
|
+
try {
|
|
102
|
+
for (const storeName of txn.objectStoreNames) {
|
|
103
|
+
const origStore = txn.objectStore(storeName);
|
|
143
104
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
modelInstanceCreator: ModelInstanceCreator,
|
|
148
|
-
getModelConstructorByModelName: (
|
|
149
|
-
namsespaceName: NAMESPACES,
|
|
150
|
-
modelName: string
|
|
151
|
-
) => PersistentModelConstructor<any>,
|
|
152
|
-
sessionId?: string
|
|
153
|
-
) {
|
|
154
|
-
await this.checkPrivate();
|
|
155
|
-
await this.setSafariCompatabilityMode();
|
|
105
|
+
// rename original store
|
|
106
|
+
const tmpName = `tmp_${storeName}`;
|
|
107
|
+
origStore.name = tmpName;
|
|
156
108
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
this.resolve = res;
|
|
160
|
-
this.reject = rej;
|
|
161
|
-
});
|
|
162
|
-
} else {
|
|
163
|
-
await this.initPromise;
|
|
164
|
-
}
|
|
165
|
-
if (sessionId) {
|
|
166
|
-
this.dbName = `${DB_NAME}-${sessionId}`;
|
|
167
|
-
}
|
|
168
|
-
this.schema = theSchema;
|
|
169
|
-
this.namespaceResolver = namespaceResolver;
|
|
170
|
-
this.modelInstanceCreator = modelInstanceCreator;
|
|
171
|
-
this.getModelConstructorByModelName = getModelConstructorByModelName;
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
if (!this.db) {
|
|
175
|
-
const VERSION = 3;
|
|
176
|
-
this.db = await idb.openDB(this.dbName, VERSION, {
|
|
177
|
-
upgrade: async (db, oldVersion, newVersion, txn) => {
|
|
178
|
-
if (oldVersion === 0) {
|
|
179
|
-
Object.keys(theSchema.namespaces).forEach(namespaceName => {
|
|
180
|
-
const namespace = theSchema.namespaces[namespaceName];
|
|
181
|
-
|
|
182
|
-
Object.keys(namespace.models).forEach(modelName => {
|
|
183
|
-
const storeName = getStorename(namespaceName, modelName);
|
|
184
|
-
this.createObjectStoreForModel(
|
|
185
|
-
db,
|
|
186
|
-
namespaceName,
|
|
187
|
-
storeName,
|
|
188
|
-
modelName
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
109
|
+
const { namespaceName, modelName } =
|
|
110
|
+
this.getNamespaceAndModelFromStorename(storeName);
|
|
192
111
|
|
|
193
|
-
|
|
194
|
-
|
|
112
|
+
const modelInCurrentSchema =
|
|
113
|
+
modelName in this.schema.namespaces[namespaceName].models;
|
|
114
|
+
|
|
115
|
+
if (!modelInCurrentSchema) {
|
|
116
|
+
// delete original
|
|
117
|
+
db.deleteObjectStore(tmpName);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
195
120
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
121
|
+
const newStore = this.createObjectStoreForModel(
|
|
122
|
+
db,
|
|
123
|
+
namespaceName,
|
|
124
|
+
storeName,
|
|
125
|
+
modelName
|
|
126
|
+
);
|
|
200
127
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
origStore.name = tmpName;
|
|
128
|
+
let cursor = await origStore.openCursor();
|
|
129
|
+
let count = 0;
|
|
204
130
|
|
|
205
|
-
|
|
206
|
-
|
|
131
|
+
// Copy data from original to new
|
|
132
|
+
while (cursor && cursor.value) {
|
|
133
|
+
// we don't pass key, since they are all new entries in the new store
|
|
134
|
+
await newStore.put(cursor.value);
|
|
207
135
|
|
|
208
|
-
|
|
209
|
-
|
|
136
|
+
cursor = await cursor.continue();
|
|
137
|
+
count++;
|
|
138
|
+
}
|
|
210
139
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
db.deleteObjectStore(tmpName);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
140
|
+
// delete original
|
|
141
|
+
db.deleteObjectStore(tmpName);
|
|
216
142
|
|
|
217
|
-
|
|
143
|
+
logger.debug(`${count} ${storeName} records migrated`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// add new models created after IndexedDB, but before migration
|
|
147
|
+
// this case may happen when a user has not opened an app for
|
|
148
|
+
// some time and a new model is added during that time
|
|
149
|
+
Object.keys(this.schema.namespaces).forEach(namespaceName => {
|
|
150
|
+
const namespace = this.schema.namespaces[namespaceName];
|
|
151
|
+
const objectStoreNames = new Set(txn.objectStoreNames);
|
|
152
|
+
|
|
153
|
+
Object.keys(namespace.models)
|
|
154
|
+
.map(modelName => {
|
|
155
|
+
return [modelName, getStorename(namespaceName, modelName)];
|
|
156
|
+
})
|
|
157
|
+
.filter(([, storeName]) => !objectStoreNames.has(storeName))
|
|
158
|
+
.forEach(([modelName, storeName]) => {
|
|
159
|
+
this.createObjectStoreForModel(
|
|
218
160
|
db,
|
|
219
161
|
namespaceName,
|
|
220
162
|
storeName,
|
|
221
163
|
modelName
|
|
222
164
|
);
|
|
223
|
-
|
|
224
|
-
let cursor = await origStore.openCursor();
|
|
225
|
-
let count = 0;
|
|
226
|
-
|
|
227
|
-
// Copy data from original to new
|
|
228
|
-
while (cursor && cursor.value) {
|
|
229
|
-
// we don't pass key, since they are all new entries in the new store
|
|
230
|
-
await newStore.put(cursor.value);
|
|
231
|
-
|
|
232
|
-
cursor = await cursor.continue();
|
|
233
|
-
count++;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// delete original
|
|
237
|
-
db.deleteObjectStore(tmpName);
|
|
238
|
-
|
|
239
|
-
logger.debug(`${count} ${storeName} records migrated`);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// add new models created after IndexedDB, but before migration
|
|
243
|
-
// this case may happen when a user has not opened an app for
|
|
244
|
-
// some time and a new model is added during that time
|
|
245
|
-
Object.keys(theSchema.namespaces).forEach(namespaceName => {
|
|
246
|
-
const namespace = theSchema.namespaces[namespaceName];
|
|
247
|
-
const objectStoreNames = new Set(txn.objectStoreNames);
|
|
248
|
-
|
|
249
|
-
Object.keys(namespace.models)
|
|
250
|
-
.map(modelName => {
|
|
251
|
-
return [
|
|
252
|
-
modelName,
|
|
253
|
-
getStorename(namespaceName, modelName),
|
|
254
|
-
];
|
|
255
|
-
})
|
|
256
|
-
.filter(([, storeName]) => !objectStoreNames.has(storeName))
|
|
257
|
-
.forEach(([modelName, storeName]) => {
|
|
258
|
-
this.createObjectStoreForModel(
|
|
259
|
-
db,
|
|
260
|
-
namespaceName,
|
|
261
|
-
storeName,
|
|
262
|
-
modelName
|
|
263
|
-
);
|
|
264
|
-
});
|
|
265
165
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
});
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.error('Error migrating IndexedDB data', error);
|
|
169
|
+
txn.abort();
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
276
172
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
});
|
|
282
177
|
}
|
|
283
178
|
|
|
284
|
-
|
|
179
|
+
protected async _get<T>(
|
|
285
180
|
storeOrStoreName: idb.IDBPObjectStore | string,
|
|
286
181
|
keyArr: string[]
|
|
287
182
|
): Promise<T> {
|
|
@@ -297,7 +192,17 @@ class IndexedDBAdapter implements Adapter {
|
|
|
297
192
|
|
|
298
193
|
const result = await index.get(this.canonicalKeyPath(keyArr));
|
|
299
194
|
|
|
300
|
-
return result;
|
|
195
|
+
return <T>result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async clear(): Promise<void> {
|
|
199
|
+
await this.checkPrivate();
|
|
200
|
+
|
|
201
|
+
this.db?.close();
|
|
202
|
+
await idb.deleteDB(this.dbName);
|
|
203
|
+
|
|
204
|
+
this.db = undefined!;
|
|
205
|
+
this.initPromise = undefined!;
|
|
301
206
|
}
|
|
302
207
|
|
|
303
208
|
async save<T extends PersistentModel>(
|
|
@@ -305,77 +210,30 @@ class IndexedDBAdapter implements Adapter {
|
|
|
305
210
|
condition?: ModelPredicate<T>
|
|
306
211
|
): Promise<[T, OpType.INSERT | OpType.UPDATE][]> {
|
|
307
212
|
await this.checkPrivate();
|
|
308
|
-
const modelConstructor = Object.getPrototypeOf(model)
|
|
309
|
-
.constructor as PersistentModelConstructor<T>;
|
|
310
|
-
const storeName = this.getStorenameForModel(modelConstructor);
|
|
311
|
-
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
312
213
|
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
model,
|
|
316
|
-
this.schema.namespaces[namespaceName],
|
|
317
|
-
this.modelInstanceCreator,
|
|
318
|
-
this.getModelConstructorByModelName!
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
const set = new Set<string>();
|
|
322
|
-
const connectionStoreNames = Object.values(connectedModels).map(
|
|
323
|
-
({ modelName, item, instance }) => {
|
|
324
|
-
const storeName = getStorename(namespaceName, modelName);
|
|
325
|
-
set.add(storeName);
|
|
326
|
-
const keys = getIndexKeys(
|
|
327
|
-
this.schema.namespaces[namespaceName],
|
|
328
|
-
modelName
|
|
329
|
-
);
|
|
330
|
-
return { storeName, item, instance, keys };
|
|
331
|
-
}
|
|
332
|
-
);
|
|
214
|
+
const { storeName, set, connectionStoreNames, modelKeyValues } =
|
|
215
|
+
this.saveMetadata(model);
|
|
333
216
|
|
|
334
217
|
const tx = this.db.transaction(
|
|
335
218
|
[storeName, ...Array.from(set.values())],
|
|
336
219
|
'readwrite'
|
|
337
220
|
);
|
|
338
|
-
const store = tx.objectStore(storeName);
|
|
339
|
-
|
|
340
|
-
const keyValues = this.getIndexKeyValuesFromModel(model);
|
|
341
|
-
|
|
342
|
-
const fromDB = await this._get(store, keyValues);
|
|
343
221
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const { predicates: predicateObjs, type } = predicates || {};
|
|
347
|
-
|
|
348
|
-
const isValid = validatePredicate(
|
|
349
|
-
fromDB as any,
|
|
350
|
-
type as any,
|
|
351
|
-
predicateObjs as any
|
|
352
|
-
);
|
|
353
|
-
|
|
354
|
-
if (!isValid) {
|
|
355
|
-
const msg = 'Conditional update failed';
|
|
356
|
-
logger.error(msg, { model: fromDB, condition: predicateObjs });
|
|
222
|
+
const store = tx.objectStore(storeName);
|
|
223
|
+
const fromDB = await this._get(store, modelKeyValues);
|
|
357
224
|
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
}
|
|
225
|
+
this.validateSaveCondition(condition, fromDB);
|
|
361
226
|
|
|
362
227
|
const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
|
|
363
228
|
for await (const resItem of connectionStoreNames) {
|
|
364
229
|
const { storeName, item, instance, keys } = resItem;
|
|
365
230
|
const store = tx.objectStore(storeName);
|
|
366
231
|
|
|
367
|
-
const itemKeyValues = keys.map(key =>
|
|
368
|
-
const value = item[key];
|
|
369
|
-
return value;
|
|
370
|
-
});
|
|
232
|
+
const itemKeyValues: string[] = keys.map(key => item[key]);
|
|
371
233
|
|
|
372
234
|
const fromDB = <T>await this._get(store, itemKeyValues);
|
|
373
|
-
const opType: OpType =
|
|
374
|
-
fromDB === undefined ? OpType.INSERT : OpType.UPDATE;
|
|
235
|
+
const opType: OpType = fromDB ? OpType.UPDATE : OpType.INSERT;
|
|
375
236
|
|
|
376
|
-
const modelKeyValues = this.getIndexKeyValuesFromModel(model);
|
|
377
|
-
|
|
378
|
-
// Even if the parent is an INSERT, the child might not be, so we need to get its key
|
|
379
237
|
if (
|
|
380
238
|
keysEqual(itemKeyValues, modelKeyValues) ||
|
|
381
239
|
opType === OpType.INSERT
|
|
@@ -387,62 +245,38 @@ class IndexedDBAdapter implements Adapter {
|
|
|
387
245
|
result.push([instance, opType]);
|
|
388
246
|
}
|
|
389
247
|
}
|
|
390
|
-
|
|
391
248
|
await tx.done;
|
|
392
249
|
|
|
393
250
|
return result;
|
|
394
251
|
}
|
|
395
252
|
|
|
396
|
-
private async load<T>(
|
|
397
|
-
namespaceName: NAMESPACES,
|
|
398
|
-
srcModelName: string,
|
|
399
|
-
records: T[]
|
|
400
|
-
): Promise<T[]> {
|
|
401
|
-
const namespace = this.schema.namespaces[namespaceName];
|
|
402
|
-
const relations = namespace.relationships![srcModelName].relationTypes;
|
|
403
|
-
const connectionStoreNames = relations.map(({ modelName }) => {
|
|
404
|
-
return getStorename(namespaceName, modelName);
|
|
405
|
-
});
|
|
406
|
-
const modelConstructor = this.getModelConstructorByModelName!(
|
|
407
|
-
namespaceName,
|
|
408
|
-
srcModelName
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
if (connectionStoreNames.length === 0) {
|
|
412
|
-
return records.map(record =>
|
|
413
|
-
this.modelInstanceCreator(modelConstructor, record)
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return records.map(record =>
|
|
418
|
-
this.modelInstanceCreator(modelConstructor, record)
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
253
|
async query<T extends PersistentModel>(
|
|
423
254
|
modelConstructor: PersistentModelConstructor<T>,
|
|
424
255
|
predicate?: ModelPredicate<T>,
|
|
425
256
|
pagination?: PaginationInput<T>
|
|
426
257
|
): Promise<T[]> {
|
|
427
258
|
await this.checkPrivate();
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
this.schema.namespaces[namespaceName],
|
|
437
|
-
modelConstructor.name
|
|
438
|
-
);
|
|
439
|
-
const queryByKey =
|
|
440
|
-
predicates && this.keyValueFromPredicate(predicates, keyPath);
|
|
441
|
-
|
|
442
|
-
const hasSort = pagination && pagination.sort;
|
|
443
|
-
const hasPagination = pagination && pagination.limit;
|
|
259
|
+
const {
|
|
260
|
+
storeName,
|
|
261
|
+
namespaceName,
|
|
262
|
+
queryByKey,
|
|
263
|
+
predicates,
|
|
264
|
+
hasSort,
|
|
265
|
+
hasPagination,
|
|
266
|
+
} = this.queryMetadata(modelConstructor, predicate, pagination);
|
|
444
267
|
|
|
445
268
|
const records: T[] = (await (async () => {
|
|
269
|
+
//
|
|
270
|
+
// NOTE: @svidgen explored removing this and letting query() take care of automatic
|
|
271
|
+
// index leveraging. This would eliminate some amount of very similar code.
|
|
272
|
+
// But, getAll is slightly slower than get()
|
|
273
|
+
//
|
|
274
|
+
// On Chrome:
|
|
275
|
+
// ~700ms vs ~1175ms per 10k reads.
|
|
276
|
+
//
|
|
277
|
+
// You can (and should) check my work here:
|
|
278
|
+
// https://gist.github.com/svidgen/74e55d573b19c3e5432b1b5bdf0f4d96
|
|
279
|
+
//
|
|
446
280
|
if (queryByKey) {
|
|
447
281
|
const record = await this.getByKey(storeName, queryByKey);
|
|
448
282
|
return record ? [record] : [];
|
|
@@ -468,40 +302,194 @@ class IndexedDBAdapter implements Adapter {
|
|
|
468
302
|
return await this.load(namespaceName, modelConstructor.name, records);
|
|
469
303
|
}
|
|
470
304
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
): Promise<T> {
|
|
475
|
-
|
|
305
|
+
async queryOne<T extends PersistentModel>(
|
|
306
|
+
modelConstructor: PersistentModelConstructor<T>,
|
|
307
|
+
firstOrLast: QueryOne = QueryOne.FIRST
|
|
308
|
+
): Promise<T | undefined> {
|
|
309
|
+
await this.checkPrivate();
|
|
310
|
+
const storeName = this.getStorenameForModel(modelConstructor);
|
|
311
|
+
|
|
312
|
+
const cursor = await this.db
|
|
313
|
+
.transaction([storeName], 'readonly')
|
|
314
|
+
.objectStore(storeName)
|
|
315
|
+
.openCursor(undefined, firstOrLast === QueryOne.FIRST ? 'next' : 'prev');
|
|
316
|
+
|
|
317
|
+
const result = cursor ? <T>cursor.value : undefined;
|
|
318
|
+
|
|
319
|
+
return result && this.modelInstanceCreator(modelConstructor, result);
|
|
476
320
|
}
|
|
477
321
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
322
|
+
async batchSave<T extends PersistentModel>(
|
|
323
|
+
modelConstructor: PersistentModelConstructor<any>,
|
|
324
|
+
items: ModelInstanceMetadata[]
|
|
325
|
+
): Promise<[T, OpType][]> {
|
|
326
|
+
await this.checkPrivate();
|
|
327
|
+
|
|
328
|
+
if (items.length === 0) {
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const modelName = modelConstructor.name;
|
|
333
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
334
|
+
const storeName = this.getStorenameForModel(modelConstructor);
|
|
335
|
+
const result: [T, OpType][] = [];
|
|
336
|
+
|
|
337
|
+
const txn = this.db.transaction(storeName, 'readwrite');
|
|
338
|
+
const store = txn.store;
|
|
339
|
+
|
|
340
|
+
for (const item of items) {
|
|
341
|
+
const model = this.modelInstanceCreator(modelConstructor, item);
|
|
342
|
+
|
|
343
|
+
const connectedModels = traverseModel(
|
|
344
|
+
modelName,
|
|
345
|
+
model,
|
|
346
|
+
this.schema.namespaces[namespaceName],
|
|
347
|
+
this.modelInstanceCreator,
|
|
348
|
+
this.getModelConstructorByModelName!
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const keyValues = this.getIndexKeyValuesFromModel(model);
|
|
352
|
+
const { _deleted } = item;
|
|
353
|
+
|
|
354
|
+
const index = store.index('byPk');
|
|
355
|
+
|
|
356
|
+
const key = await index.getKey(this.canonicalKeyPath(keyValues));
|
|
357
|
+
|
|
358
|
+
if (!_deleted) {
|
|
359
|
+
const { instance } = connectedModels.find(({ instance }) => {
|
|
360
|
+
const instanceKeyValues = this.getIndexKeyValuesFromModel(instance);
|
|
361
|
+
return keysEqual(instanceKeyValues, keyValues);
|
|
362
|
+
})!;
|
|
363
|
+
|
|
364
|
+
result.push([
|
|
365
|
+
<T>(<unknown>instance),
|
|
366
|
+
key ? OpType.UPDATE : OpType.INSERT,
|
|
367
|
+
]);
|
|
368
|
+
await store.put(instance, key);
|
|
369
|
+
} else {
|
|
370
|
+
result.push([<T>(<unknown>item), OpType.DELETE]);
|
|
371
|
+
|
|
372
|
+
if (key) {
|
|
373
|
+
await store.delete(key);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
await txn.done;
|
|
379
|
+
|
|
380
|
+
return result;
|
|
482
381
|
}
|
|
483
382
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
383
|
+
protected async deleteItem<T extends PersistentModel>(
|
|
384
|
+
deleteQueue: {
|
|
385
|
+
storeName: string;
|
|
386
|
+
items: T[] | IDBValidKey[];
|
|
387
|
+
}[]
|
|
388
|
+
) {
|
|
389
|
+
const connectionStoreNames = deleteQueue!.map(({ storeName }) => {
|
|
390
|
+
return storeName;
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const tx = this.db.transaction([...connectionStoreNames], 'readwrite');
|
|
394
|
+
for await (const deleteItem of deleteQueue!) {
|
|
395
|
+
const { storeName, items } = deleteItem;
|
|
396
|
+
const store = tx.objectStore(storeName);
|
|
489
397
|
|
|
490
|
-
|
|
491
|
-
|
|
398
|
+
for await (const item of items) {
|
|
399
|
+
if (item) {
|
|
400
|
+
let key: IDBValidKey | undefined;
|
|
401
|
+
|
|
402
|
+
if (typeof item === 'object') {
|
|
403
|
+
const keyValues = this.getIndexKeyValuesFromModel(item as T);
|
|
404
|
+
key = await store
|
|
405
|
+
.index('byPk')
|
|
406
|
+
.getKey(this.canonicalKeyPath(keyValues));
|
|
407
|
+
} else {
|
|
408
|
+
const itemKey = item.toString();
|
|
409
|
+
key = await store.index('byPk').getKey(itemKey);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (key !== undefined) {
|
|
413
|
+
await store.delete(key);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
492
417
|
}
|
|
418
|
+
}
|
|
493
419
|
|
|
494
|
-
|
|
420
|
+
//#region platform-specific helper methods
|
|
495
421
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
422
|
+
private async checkPrivate() {
|
|
423
|
+
const isPrivate = await isPrivateMode().then(isPrivate => {
|
|
424
|
+
return isPrivate;
|
|
425
|
+
});
|
|
426
|
+
if (isPrivate) {
|
|
427
|
+
logger.error("IndexedDB not supported in this browser's private mode");
|
|
428
|
+
return Promise.reject(
|
|
429
|
+
"IndexedDB not supported in this browser's private mode"
|
|
430
|
+
);
|
|
431
|
+
} else {
|
|
432
|
+
return Promise.resolve();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
500
435
|
|
|
501
|
-
|
|
436
|
+
/**
|
|
437
|
+
* Whether the browser's implementation of IndexedDB is coercing single-field
|
|
438
|
+
* indexes to a scalar key.
|
|
439
|
+
*
|
|
440
|
+
* If this returns `true`, we need to treat indexes containing a single field
|
|
441
|
+
* as scalars.
|
|
442
|
+
*
|
|
443
|
+
* See PR description for reference:
|
|
444
|
+
* https://github.com/aws-amplify/amplify-js/pull/10527
|
|
445
|
+
*/
|
|
446
|
+
private async setSafariCompatabilityMode() {
|
|
447
|
+
this.safariCompatabilityMode = await isSafariCompatabilityMode();
|
|
448
|
+
|
|
449
|
+
if (this.safariCompatabilityMode === true) {
|
|
450
|
+
logger.debug('IndexedDB Adapter is running in Safari Compatability Mode');
|
|
502
451
|
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private getNamespaceAndModelFromStorename(storeName: string) {
|
|
455
|
+
const [namespaceName, ...modelNameArr] = storeName.split('_');
|
|
456
|
+
return {
|
|
457
|
+
namespaceName,
|
|
458
|
+
modelName: modelNameArr.join('_'),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private createObjectStoreForModel(
|
|
463
|
+
db: idb.IDBPDatabase,
|
|
464
|
+
namespaceName: string,
|
|
465
|
+
storeName: string,
|
|
466
|
+
modelName: string
|
|
467
|
+
): idb.IDBPObjectStore {
|
|
468
|
+
const store = db.createObjectStore(storeName, {
|
|
469
|
+
autoIncrement: true,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const { indexes } =
|
|
473
|
+
this.schema.namespaces[namespaceName].relationships![modelName];
|
|
474
|
+
|
|
475
|
+
indexes.forEach(([idxName, keyPath, options]) => {
|
|
476
|
+
store.createIndex(idxName, keyPath, options);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
return store;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private async getByKey<T extends PersistentModel>(
|
|
483
|
+
storeName: string,
|
|
484
|
+
keyValue: string[]
|
|
485
|
+
): Promise<T> {
|
|
486
|
+
return <T>await this._get(storeName, keyValue);
|
|
487
|
+
}
|
|
503
488
|
|
|
504
|
-
|
|
489
|
+
private async getAll<T extends PersistentModel>(
|
|
490
|
+
storeName: string
|
|
491
|
+
): Promise<T[]> {
|
|
492
|
+
return await this.db.getAll(storeName);
|
|
505
493
|
}
|
|
506
494
|
|
|
507
495
|
/**
|
|
@@ -530,12 +518,12 @@ class IndexedDBAdapter implements Adapter {
|
|
|
530
518
|
for (const name of store.indexNames) {
|
|
531
519
|
const idx = store.index(name);
|
|
532
520
|
const keypath = Array.isArray(idx.keyPath) ? idx.keyPath : [idx.keyPath];
|
|
533
|
-
const matchingPredicateValues: string[] = [];
|
|
521
|
+
const matchingPredicateValues: (string | number)[] = [];
|
|
534
522
|
|
|
535
523
|
for (const field of keypath) {
|
|
536
524
|
const p = predicateIndex.get(field);
|
|
537
|
-
if (p) {
|
|
538
|
-
matchingPredicateValues.push(
|
|
525
|
+
if (p && p.operand !== null && p.operand !== undefined) {
|
|
526
|
+
matchingPredicateValues.push(p.operand);
|
|
539
527
|
} else {
|
|
540
528
|
break;
|
|
541
529
|
}
|
|
@@ -769,447 +757,6 @@ class IndexedDBAdapter implements Adapter {
|
|
|
769
757
|
return result;
|
|
770
758
|
}
|
|
771
759
|
|
|
772
|
-
async queryOne<T extends PersistentModel>(
|
|
773
|
-
modelConstructor: PersistentModelConstructor<T>,
|
|
774
|
-
firstOrLast: QueryOne = QueryOne.FIRST
|
|
775
|
-
): Promise<T | undefined> {
|
|
776
|
-
await this.checkPrivate();
|
|
777
|
-
const storeName = this.getStorenameForModel(modelConstructor);
|
|
778
|
-
|
|
779
|
-
const cursor = await this.db
|
|
780
|
-
.transaction([storeName], 'readonly')
|
|
781
|
-
.objectStore(storeName)
|
|
782
|
-
.openCursor(undefined, firstOrLast === QueryOne.FIRST ? 'next' : 'prev');
|
|
783
|
-
|
|
784
|
-
const result = cursor ? <T>cursor.value : undefined;
|
|
785
|
-
|
|
786
|
-
return result && this.modelInstanceCreator(modelConstructor, result);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
async delete<T extends PersistentModel>(
|
|
790
|
-
modelOrModelConstructor: T | PersistentModelConstructor<T>,
|
|
791
|
-
condition?: ModelPredicate<T>
|
|
792
|
-
): Promise<[T[], T[]]> {
|
|
793
|
-
await this.checkPrivate();
|
|
794
|
-
const deleteQueue: { storeName: string; items: T[] }[] = [];
|
|
795
|
-
|
|
796
|
-
if (isModelConstructor(modelOrModelConstructor)) {
|
|
797
|
-
const modelConstructor =
|
|
798
|
-
modelOrModelConstructor as PersistentModelConstructor<T>;
|
|
799
|
-
const nameSpace = this.namespaceResolver(modelConstructor) as NAMESPACES;
|
|
800
|
-
|
|
801
|
-
const storeName = this.getStorenameForModel(modelConstructor);
|
|
802
|
-
|
|
803
|
-
const models = await this.query(modelConstructor, condition);
|
|
804
|
-
const relations =
|
|
805
|
-
this.schema.namespaces![nameSpace].relationships![modelConstructor.name]
|
|
806
|
-
.relationTypes;
|
|
807
|
-
|
|
808
|
-
if (condition !== undefined) {
|
|
809
|
-
await this.deleteTraverse(
|
|
810
|
-
relations,
|
|
811
|
-
models,
|
|
812
|
-
modelConstructor.name,
|
|
813
|
-
nameSpace,
|
|
814
|
-
deleteQueue
|
|
815
|
-
);
|
|
816
|
-
|
|
817
|
-
await this.deleteItem(deleteQueue);
|
|
818
|
-
|
|
819
|
-
const deletedModels = deleteQueue.reduce(
|
|
820
|
-
(acc, { items }) => acc.concat(items),
|
|
821
|
-
<T[]>[]
|
|
822
|
-
);
|
|
823
|
-
|
|
824
|
-
return [models, deletedModels];
|
|
825
|
-
} else {
|
|
826
|
-
await this.deleteTraverse(
|
|
827
|
-
relations,
|
|
828
|
-
models,
|
|
829
|
-
modelConstructor.name,
|
|
830
|
-
nameSpace,
|
|
831
|
-
deleteQueue
|
|
832
|
-
);
|
|
833
|
-
|
|
834
|
-
// Delete all
|
|
835
|
-
await this.db
|
|
836
|
-
.transaction([storeName], 'readwrite')
|
|
837
|
-
.objectStore(storeName)
|
|
838
|
-
.clear();
|
|
839
|
-
|
|
840
|
-
const deletedModels = deleteQueue.reduce(
|
|
841
|
-
(acc, { items }) => acc.concat(items),
|
|
842
|
-
<T[]>[]
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
return [models, deletedModels];
|
|
846
|
-
}
|
|
847
|
-
} else {
|
|
848
|
-
const model = modelOrModelConstructor as T;
|
|
849
|
-
|
|
850
|
-
const modelConstructor = Object.getPrototypeOf(model)
|
|
851
|
-
.constructor as PersistentModelConstructor<T>;
|
|
852
|
-
const namespaceName = this.namespaceResolver(
|
|
853
|
-
modelConstructor
|
|
854
|
-
) as NAMESPACES;
|
|
855
|
-
|
|
856
|
-
const storeName = this.getStorenameForModel(modelConstructor);
|
|
857
|
-
|
|
858
|
-
if (condition) {
|
|
859
|
-
const tx = this.db.transaction([storeName], 'readwrite');
|
|
860
|
-
const store = tx.objectStore(storeName);
|
|
861
|
-
const keyValues = this.getIndexKeyValuesFromModel(model);
|
|
862
|
-
|
|
863
|
-
const fromDB = await this._get(store, keyValues);
|
|
864
|
-
|
|
865
|
-
if (fromDB === undefined) {
|
|
866
|
-
const msg = 'Model instance not found in storage';
|
|
867
|
-
logger.warn(msg, { model });
|
|
868
|
-
|
|
869
|
-
return [[model], []];
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
const predicates = ModelPredicateCreator.getPredicates(condition);
|
|
873
|
-
const { predicates: predicateObjs, type } =
|
|
874
|
-
predicates as PredicatesGroup<T>;
|
|
875
|
-
|
|
876
|
-
const isValid = validatePredicate(fromDB as T, type, predicateObjs);
|
|
877
|
-
|
|
878
|
-
if (!isValid) {
|
|
879
|
-
const msg = 'Conditional update failed';
|
|
880
|
-
logger.error(msg, { model: fromDB, condition: predicateObjs });
|
|
881
|
-
|
|
882
|
-
throw new Error(msg);
|
|
883
|
-
}
|
|
884
|
-
await tx.done;
|
|
885
|
-
|
|
886
|
-
const relations =
|
|
887
|
-
this.schema.namespaces[namespaceName].relationships![
|
|
888
|
-
modelConstructor.name
|
|
889
|
-
].relationTypes;
|
|
890
|
-
|
|
891
|
-
await this.deleteTraverse(
|
|
892
|
-
relations,
|
|
893
|
-
[model],
|
|
894
|
-
modelConstructor.name,
|
|
895
|
-
namespaceName,
|
|
896
|
-
deleteQueue
|
|
897
|
-
);
|
|
898
|
-
} else {
|
|
899
|
-
const relations =
|
|
900
|
-
this.schema.namespaces[namespaceName].relationships![
|
|
901
|
-
modelConstructor.name
|
|
902
|
-
].relationTypes;
|
|
903
|
-
|
|
904
|
-
await this.deleteTraverse(
|
|
905
|
-
relations,
|
|
906
|
-
[model],
|
|
907
|
-
modelConstructor.name,
|
|
908
|
-
namespaceName,
|
|
909
|
-
deleteQueue
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
await this.deleteItem(deleteQueue);
|
|
914
|
-
|
|
915
|
-
const deletedModels = deleteQueue.reduce(
|
|
916
|
-
(acc, { items }) => acc.concat(items),
|
|
917
|
-
<T[]>[]
|
|
918
|
-
);
|
|
919
|
-
|
|
920
|
-
return [[model], deletedModels];
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
private async deleteItem<T extends PersistentModel>(
|
|
925
|
-
deleteQueue?: {
|
|
926
|
-
storeName: string;
|
|
927
|
-
items: T[] | IDBValidKey[];
|
|
928
|
-
}[]
|
|
929
|
-
) {
|
|
930
|
-
const connectionStoreNames = deleteQueue!.map(({ storeName }) => {
|
|
931
|
-
return storeName;
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
const tx = this.db.transaction([...connectionStoreNames], 'readwrite');
|
|
935
|
-
for await (const deleteItem of deleteQueue!) {
|
|
936
|
-
const { storeName, items } = deleteItem;
|
|
937
|
-
const store = tx.objectStore(storeName);
|
|
938
|
-
|
|
939
|
-
for await (const item of items) {
|
|
940
|
-
if (item) {
|
|
941
|
-
let key: IDBValidKey | undefined;
|
|
942
|
-
|
|
943
|
-
if (typeof item === 'object') {
|
|
944
|
-
const keyValues = this.getIndexKeyValuesFromModel(item as T);
|
|
945
|
-
key = await store
|
|
946
|
-
.index('byPk')
|
|
947
|
-
.getKey(this.canonicalKeyPath(keyValues));
|
|
948
|
-
} else {
|
|
949
|
-
const itemKey = item.toString();
|
|
950
|
-
key = await store.index('byPk').getKey(itemKey);
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
if (key !== undefined) {
|
|
954
|
-
await store.delete(key);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
private async deleteTraverse<T extends PersistentModel>(
|
|
962
|
-
relations: RelationType[],
|
|
963
|
-
models: T[],
|
|
964
|
-
srcModel: string,
|
|
965
|
-
nameSpace: NAMESPACES,
|
|
966
|
-
deleteQueue: { storeName: string; items: T[] }[]
|
|
967
|
-
): Promise<void> {
|
|
968
|
-
for await (const rel of relations) {
|
|
969
|
-
const {
|
|
970
|
-
relationType,
|
|
971
|
-
modelName,
|
|
972
|
-
targetName,
|
|
973
|
-
targetNames,
|
|
974
|
-
associatedWith,
|
|
975
|
-
} = rel;
|
|
976
|
-
|
|
977
|
-
const storeName = getStorename(nameSpace, modelName);
|
|
978
|
-
|
|
979
|
-
switch (relationType) {
|
|
980
|
-
case 'HAS_ONE':
|
|
981
|
-
for await (const model of models) {
|
|
982
|
-
const hasOneIndex = 'byPk';
|
|
983
|
-
|
|
984
|
-
if (targetNames?.length) {
|
|
985
|
-
// CPK codegen
|
|
986
|
-
const values = targetNames
|
|
987
|
-
.filter(targetName => model[targetName] ?? false)
|
|
988
|
-
.map(targetName => model[targetName]);
|
|
989
|
-
|
|
990
|
-
if (values.length === 0) break;
|
|
991
|
-
|
|
992
|
-
const recordToDelete = <T>(
|
|
993
|
-
await this.db
|
|
994
|
-
.transaction(storeName, 'readwrite')
|
|
995
|
-
.objectStore(storeName)
|
|
996
|
-
.index(hasOneIndex)
|
|
997
|
-
.get(this.canonicalKeyPath(values))
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
await this.deleteTraverse(
|
|
1001
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
1002
|
-
.relationTypes,
|
|
1003
|
-
recordToDelete ? [recordToDelete] : [],
|
|
1004
|
-
modelName,
|
|
1005
|
-
nameSpace,
|
|
1006
|
-
deleteQueue
|
|
1007
|
-
);
|
|
1008
|
-
break;
|
|
1009
|
-
} else {
|
|
1010
|
-
// PRE-CPK codegen
|
|
1011
|
-
let index;
|
|
1012
|
-
let values: string[];
|
|
1013
|
-
|
|
1014
|
-
if (targetName && targetName in model) {
|
|
1015
|
-
index = hasOneIndex;
|
|
1016
|
-
const value = model[targetName];
|
|
1017
|
-
if (value === null) break;
|
|
1018
|
-
values = [value];
|
|
1019
|
-
} else {
|
|
1020
|
-
// backwards compatability for older versions of codegen that did not emit targetName for HAS_ONE relations
|
|
1021
|
-
// TODO: can we deprecate this? it's been ~2 years since codegen started including targetName for HAS_ONE
|
|
1022
|
-
// If we deprecate, we'll need to re-gen the MIPR in __tests__/schema.ts > newSchema
|
|
1023
|
-
// otherwise some unit tests will fail
|
|
1024
|
-
index = getIndex(
|
|
1025
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
1026
|
-
.relationTypes,
|
|
1027
|
-
srcModel
|
|
1028
|
-
);
|
|
1029
|
-
values = this.getIndexKeyValuesFromModel(model);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
if (!values || !index) break;
|
|
1033
|
-
|
|
1034
|
-
const recordToDelete = <T>(
|
|
1035
|
-
await this.db
|
|
1036
|
-
.transaction(storeName, 'readwrite')
|
|
1037
|
-
.objectStore(storeName)
|
|
1038
|
-
.index(index)
|
|
1039
|
-
.get(this.canonicalKeyPath(values))
|
|
1040
|
-
);
|
|
1041
|
-
|
|
1042
|
-
// instantiate models before passing to deleteTraverse
|
|
1043
|
-
// necessary for extracting PK values via getIndexKeyValuesFromModel
|
|
1044
|
-
const modelsToDelete = recordToDelete
|
|
1045
|
-
? await this.load(nameSpace, modelName, [recordToDelete])
|
|
1046
|
-
: [];
|
|
1047
|
-
|
|
1048
|
-
await this.deleteTraverse(
|
|
1049
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
1050
|
-
.relationTypes,
|
|
1051
|
-
modelsToDelete,
|
|
1052
|
-
modelName,
|
|
1053
|
-
nameSpace,
|
|
1054
|
-
deleteQueue
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
break;
|
|
1059
|
-
case 'HAS_MANY':
|
|
1060
|
-
for await (const model of models) {
|
|
1061
|
-
const index =
|
|
1062
|
-
// explicit bi-directional @hasMany and @manyToMany
|
|
1063
|
-
getIndex(
|
|
1064
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
1065
|
-
.relationTypes,
|
|
1066
|
-
srcModel
|
|
1067
|
-
) ||
|
|
1068
|
-
// uni and/or implicit @hasMany
|
|
1069
|
-
getIndexFromAssociation(
|
|
1070
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
1071
|
-
.indexes,
|
|
1072
|
-
associatedWith!
|
|
1073
|
-
);
|
|
1074
|
-
const keyValues = this.getIndexKeyValuesFromModel(model);
|
|
1075
|
-
|
|
1076
|
-
const childRecords = await this.db
|
|
1077
|
-
.transaction(storeName, 'readwrite')
|
|
1078
|
-
.objectStore(storeName)
|
|
1079
|
-
.index(index as string)
|
|
1080
|
-
.getAll(this.canonicalKeyPath(keyValues));
|
|
1081
|
-
|
|
1082
|
-
// instantiate models before passing to deleteTraverse
|
|
1083
|
-
// necessary for extracting PK values via getIndexKeyValuesFromModel
|
|
1084
|
-
const childModels = await this.load(
|
|
1085
|
-
nameSpace,
|
|
1086
|
-
modelName,
|
|
1087
|
-
childRecords
|
|
1088
|
-
);
|
|
1089
|
-
|
|
1090
|
-
await this.deleteTraverse(
|
|
1091
|
-
this.schema.namespaces[nameSpace].relationships![modelName]
|
|
1092
|
-
.relationTypes,
|
|
1093
|
-
childModels,
|
|
1094
|
-
modelName,
|
|
1095
|
-
nameSpace,
|
|
1096
|
-
deleteQueue
|
|
1097
|
-
);
|
|
1098
|
-
}
|
|
1099
|
-
break;
|
|
1100
|
-
case 'BELONGS_TO':
|
|
1101
|
-
// Intentionally blank
|
|
1102
|
-
break;
|
|
1103
|
-
default:
|
|
1104
|
-
throw new Error(`Invalid relation type ${relationType}`);
|
|
1105
|
-
break;
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
deleteQueue.push({
|
|
1110
|
-
storeName: getStorename(nameSpace, srcModel),
|
|
1111
|
-
items: models.map(record =>
|
|
1112
|
-
this.modelInstanceCreator(
|
|
1113
|
-
this.getModelConstructorByModelName!(nameSpace, srcModel),
|
|
1114
|
-
record
|
|
1115
|
-
)
|
|
1116
|
-
),
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
async clear(): Promise<void> {
|
|
1121
|
-
await this.checkPrivate();
|
|
1122
|
-
|
|
1123
|
-
this.db?.close();
|
|
1124
|
-
|
|
1125
|
-
await idb.deleteDB(this.dbName);
|
|
1126
|
-
|
|
1127
|
-
this.db = undefined!;
|
|
1128
|
-
this.initPromise = undefined!;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
async batchSave<T extends PersistentModel>(
|
|
1132
|
-
modelConstructor: PersistentModelConstructor<any>,
|
|
1133
|
-
items: ModelInstanceMetadata[]
|
|
1134
|
-
): Promise<[T, OpType][]> {
|
|
1135
|
-
if (items.length === 0) {
|
|
1136
|
-
return [];
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
await this.checkPrivate();
|
|
1140
|
-
|
|
1141
|
-
const result: [T, OpType][] = [];
|
|
1142
|
-
|
|
1143
|
-
const storeName = this.getStorenameForModel(modelConstructor);
|
|
1144
|
-
|
|
1145
|
-
const txn = this.db.transaction(storeName, 'readwrite');
|
|
1146
|
-
const store = txn.store;
|
|
1147
|
-
|
|
1148
|
-
for (const item of items) {
|
|
1149
|
-
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
1150
|
-
const modelName = modelConstructor.name;
|
|
1151
|
-
const model = this.modelInstanceCreator(modelConstructor, item);
|
|
1152
|
-
|
|
1153
|
-
const connectedModels = traverseModel(
|
|
1154
|
-
modelName,
|
|
1155
|
-
model,
|
|
1156
|
-
this.schema.namespaces[namespaceName],
|
|
1157
|
-
this.modelInstanceCreator,
|
|
1158
|
-
this.getModelConstructorByModelName!
|
|
1159
|
-
);
|
|
1160
|
-
|
|
1161
|
-
const keyValues = this.getIndexKeyValuesFromModel(model);
|
|
1162
|
-
const { _deleted } = item;
|
|
1163
|
-
|
|
1164
|
-
const index = store.index('byPk');
|
|
1165
|
-
|
|
1166
|
-
const key = await index.getKey(this.canonicalKeyPath(keyValues));
|
|
1167
|
-
|
|
1168
|
-
if (!_deleted) {
|
|
1169
|
-
const { instance } = connectedModels.find(({ instance }) => {
|
|
1170
|
-
const instanceKeyValues = this.getIndexKeyValuesFromModel(instance);
|
|
1171
|
-
return keysEqual(instanceKeyValues, keyValues);
|
|
1172
|
-
})!;
|
|
1173
|
-
|
|
1174
|
-
result.push([
|
|
1175
|
-
<T>(<unknown>instance),
|
|
1176
|
-
key ? OpType.UPDATE : OpType.INSERT,
|
|
1177
|
-
]);
|
|
1178
|
-
await store.put(instance, key);
|
|
1179
|
-
} else {
|
|
1180
|
-
result.push([<T>(<unknown>item), OpType.DELETE]);
|
|
1181
|
-
|
|
1182
|
-
if (key) {
|
|
1183
|
-
await store.delete(key);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
await txn.done;
|
|
1189
|
-
|
|
1190
|
-
return result;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
private createObjectStoreForModel(
|
|
1194
|
-
db: idb.IDBPDatabase,
|
|
1195
|
-
namespaceName: string,
|
|
1196
|
-
storeName: string,
|
|
1197
|
-
modelName: string
|
|
1198
|
-
) {
|
|
1199
|
-
const store = db.createObjectStore(storeName, {
|
|
1200
|
-
autoIncrement: true,
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
const { indexes } =
|
|
1204
|
-
this.schema.namespaces[namespaceName].relationships![modelName];
|
|
1205
|
-
|
|
1206
|
-
indexes.forEach(([idxName, keyPath, options]) => {
|
|
1207
|
-
store.createIndex(idxName, keyPath, options);
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
return store;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
760
|
/**
|
|
1214
761
|
* Checks the given path against the browser's IndexedDB implementation for
|
|
1215
762
|
* necessary compatibility transformations, applying those transforms if needed.
|
|
@@ -1218,12 +765,13 @@ class IndexedDBAdapter implements Adapter {
|
|
|
1218
765
|
* @returns An array or string, depending on and given key,
|
|
1219
766
|
* that is ensured to be compatible with the IndexedDB implementation's nuances.
|
|
1220
767
|
*/
|
|
1221
|
-
private canonicalKeyPath = (keyArr: string[]) => {
|
|
768
|
+
private canonicalKeyPath = (keyArr: (string | number)[]) => {
|
|
1222
769
|
if (this.safariCompatabilityMode) {
|
|
1223
770
|
return keyArr.length > 1 ? keyArr : keyArr[0];
|
|
1224
771
|
}
|
|
1225
772
|
return keyArr;
|
|
1226
773
|
};
|
|
774
|
+
//#endregion
|
|
1227
775
|
}
|
|
1228
776
|
|
|
1229
777
|
export default new IndexedDBAdapter();
|