@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
package/src/predicates/index.ts
CHANGED
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
ProducerModelPredicate,
|
|
9
9
|
SchemaModel,
|
|
10
10
|
} from '../types';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
exhaustiveCheck,
|
|
13
|
+
extractPrimaryKeyFieldNames,
|
|
14
|
+
extractPrimaryKeyValues,
|
|
15
|
+
} from '../util';
|
|
12
16
|
|
|
13
17
|
export { ModelSortPredicateCreator } from './sort';
|
|
14
18
|
|
|
@@ -85,7 +89,7 @@ export class ModelPredicateCreator {
|
|
|
85
89
|
|
|
86
90
|
// Push the group to the top-level recorder
|
|
87
91
|
ModelPredicateCreator.predicateGroupsMap
|
|
88
|
-
.get(receiver)
|
|
92
|
+
.get(receiver)!
|
|
89
93
|
.predicates.push(group);
|
|
90
94
|
|
|
91
95
|
return receiver;
|
|
@@ -109,7 +113,7 @@ export class ModelPredicateCreator {
|
|
|
109
113
|
operand: any
|
|
110
114
|
) => {
|
|
111
115
|
ModelPredicateCreator.predicateGroupsMap
|
|
112
|
-
.get(receiver)
|
|
116
|
+
.get(receiver)!
|
|
113
117
|
.predicates.push({ field, operator, operand });
|
|
114
118
|
return receiver;
|
|
115
119
|
};
|
|
@@ -147,7 +151,7 @@ export class ModelPredicateCreator {
|
|
|
147
151
|
// transforms cb-style predicate into Proxy
|
|
148
152
|
static createFromExisting<T extends PersistentModel>(
|
|
149
153
|
modelDefinition: SchemaModel,
|
|
150
|
-
existing
|
|
154
|
+
existing?: ProducerModelPredicate<T>
|
|
151
155
|
) {
|
|
152
156
|
if (!existing || !modelDefinition) {
|
|
153
157
|
return undefined;
|
|
@@ -158,13 +162,31 @@ export class ModelPredicateCreator {
|
|
|
158
162
|
);
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
static
|
|
165
|
+
static createForSingleField<T extends PersistentModel>(
|
|
162
166
|
modelDefinition: SchemaModel,
|
|
163
|
-
|
|
167
|
+
fieldName: string,
|
|
168
|
+
value: string
|
|
164
169
|
) {
|
|
165
|
-
return ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
return ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition)[
|
|
171
|
+
fieldName
|
|
172
|
+
](<any>'eq', <any>value);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static createForPk<T extends PersistentModel>(
|
|
176
|
+
modelDefinition: SchemaModel,
|
|
177
|
+
model: T
|
|
178
|
+
) {
|
|
179
|
+
const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
|
|
180
|
+
const keyValues = extractPrimaryKeyValues(model, keyFields);
|
|
181
|
+
|
|
182
|
+
let modelPredicate =
|
|
183
|
+
ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition);
|
|
184
|
+
|
|
185
|
+
keyFields.forEach((field, idx) => {
|
|
186
|
+
const operand = keyValues[idx];
|
|
187
|
+
modelPredicate = modelPredicate[field](<any>'eq', <any>operand);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return modelPredicate;
|
|
169
191
|
}
|
|
170
192
|
}
|
|
@@ -33,6 +33,8 @@ import {
|
|
|
33
33
|
|
|
34
34
|
const logger = new Logger('DataStore');
|
|
35
35
|
|
|
36
|
+
const DEFAULT_PRIMARY_KEY_SEPARATOR = '#';
|
|
37
|
+
|
|
36
38
|
export class AsyncStorageAdapter implements Adapter {
|
|
37
39
|
private schema: InternalSchema;
|
|
38
40
|
private namespaceResolver: NamespaceResolver;
|
|
@@ -61,6 +63,55 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
61
63
|
return storeName;
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
// Returns primary keys for a model
|
|
67
|
+
private getIndexKeys(namespaceName: string, modelName: string): string[] {
|
|
68
|
+
const keyPath =
|
|
69
|
+
this.schema.namespaces[namespaceName]?.keys[modelName]?.primaryKey;
|
|
70
|
+
|
|
71
|
+
if (keyPath) {
|
|
72
|
+
return keyPath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return ['id'];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Retrieves concatenated primary key values from a model
|
|
79
|
+
private getIndexKeyValuesPath<T extends PersistentModel>(model: T): string {
|
|
80
|
+
const modelConstructor = Object.getPrototypeOf(model)
|
|
81
|
+
.constructor as PersistentModelConstructor<T>;
|
|
82
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
83
|
+
const keys = this.getIndexKeys(namespaceName, modelConstructor.name);
|
|
84
|
+
|
|
85
|
+
// Retrieve key values from model
|
|
86
|
+
const keyValues = keys.map(field => model[field]);
|
|
87
|
+
|
|
88
|
+
// Return concatenated key values
|
|
89
|
+
return keyValues.join(DEFAULT_PRIMARY_KEY_SEPARATOR);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Retrieves concatenated primary key values from a model
|
|
93
|
+
private getIndexKeyValues<T extends PersistentModel>(model: T): string[] {
|
|
94
|
+
const modelConstructor = Object.getPrototypeOf(model)
|
|
95
|
+
.constructor as PersistentModelConstructor<T>;
|
|
96
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
97
|
+
const keys = this.getIndexKeys(namespaceName, modelConstructor.name);
|
|
98
|
+
|
|
99
|
+
// Retrieve key values from model
|
|
100
|
+
return keys.map(field => model[field]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private keysEqual(keysA, keysB): boolean {
|
|
104
|
+
if (keysA.length !== keysB.length) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (keysA.length === 1) {
|
|
109
|
+
return keysA[0] === keysB[0];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return keysA.every((key, idx) => key === keysB[idx]);
|
|
113
|
+
}
|
|
114
|
+
|
|
64
115
|
async setUp(
|
|
65
116
|
theSchema: InternalSchema,
|
|
66
117
|
namespaceResolver: NamespaceResolver,
|
|
@@ -101,23 +152,29 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
101
152
|
const modelConstructor = Object.getPrototypeOf(model)
|
|
102
153
|
.constructor as PersistentModelConstructor<T>;
|
|
103
154
|
const storeName = this.getStorenameForModel(modelConstructor);
|
|
155
|
+
|
|
156
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
157
|
+
|
|
104
158
|
const connectedModels = traverseModel(
|
|
105
159
|
modelConstructor.name,
|
|
106
160
|
model,
|
|
107
|
-
this.schema.namespaces[
|
|
161
|
+
this.schema.namespaces[namespaceName],
|
|
108
162
|
this.modelInstanceCreator,
|
|
109
163
|
this.getModelConstructorByModelName
|
|
110
164
|
);
|
|
111
|
-
|
|
165
|
+
|
|
112
166
|
const set = new Set<string>();
|
|
113
167
|
const connectionStoreNames = Object.values(connectedModels).map(
|
|
114
168
|
({ modelName, item, instance }) => {
|
|
115
169
|
const storeName = this.getStorename(namespaceName, modelName);
|
|
116
170
|
set.add(storeName);
|
|
117
|
-
|
|
171
|
+
const keys = this.getIndexKeys(namespaceName, modelName);
|
|
172
|
+
return { storeName, item, instance, keys };
|
|
118
173
|
}
|
|
119
174
|
);
|
|
120
|
-
const
|
|
175
|
+
const keyValuesPath = this.getIndexKeyValuesPath(model);
|
|
176
|
+
|
|
177
|
+
const fromDB = await this.db.get(keyValuesPath, storeName);
|
|
121
178
|
|
|
122
179
|
if (condition && fromDB) {
|
|
123
180
|
const predicates = ModelPredicateCreator.getPredicates(condition);
|
|
@@ -136,14 +193,22 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
136
193
|
const result: [T, OpType.INSERT | OpType.UPDATE][] = [];
|
|
137
194
|
|
|
138
195
|
for await (const resItem of connectionStoreNames) {
|
|
139
|
-
const { storeName, item, instance } = resItem;
|
|
140
|
-
|
|
196
|
+
const { storeName, item, instance, keys } = resItem;
|
|
197
|
+
|
|
198
|
+
/* Find the key values in the item, and concatenate them */
|
|
199
|
+
const itemKeyValues: string[] = keys.map(key => item[key]);
|
|
200
|
+
const itemKeyValuesPath: string = itemKeyValues.join(
|
|
201
|
+
DEFAULT_PRIMARY_KEY_SEPARATOR
|
|
202
|
+
);
|
|
141
203
|
|
|
142
|
-
const fromDB = <T>await this.db.get(
|
|
204
|
+
const fromDB = <T>await this.db.get(itemKeyValuesPath, storeName);
|
|
143
205
|
const opType: OpType = fromDB ? OpType.UPDATE : OpType.INSERT;
|
|
206
|
+
const modelKeyValues = this.getIndexKeyValues(model);
|
|
207
|
+
const keysEqual = this.keysEqual(itemKeyValues, modelKeyValues);
|
|
144
208
|
|
|
145
|
-
|
|
146
|
-
|
|
209
|
+
// If item key values and model key values are equal, save to db
|
|
210
|
+
if (keysEqual || opType === OpType.INSERT) {
|
|
211
|
+
await this.db.save(item, storeName, keys, itemKeyValuesPath);
|
|
147
212
|
|
|
148
213
|
result.push([instance, opType]);
|
|
149
214
|
}
|
|
@@ -187,10 +252,9 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
187
252
|
const getByfield = recordItem[targetName] ? targetName : fieldName;
|
|
188
253
|
if (!recordItem[getByfield]) break;
|
|
189
254
|
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
);
|
|
255
|
+
const key = recordItem[getByfield];
|
|
256
|
+
|
|
257
|
+
const connectionRecord = await this.db.get(key, storeName);
|
|
194
258
|
|
|
195
259
|
recordItem[fieldName] =
|
|
196
260
|
connectionRecord &&
|
|
@@ -201,10 +265,9 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
201
265
|
case 'BELONGS_TO':
|
|
202
266
|
for await (const recordItem of records) {
|
|
203
267
|
if (recordItem[targetName]) {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
268
|
+
const key = recordItem[targetName];
|
|
269
|
+
|
|
270
|
+
const connectionRecord = await this.db.get(key, storeName);
|
|
208
271
|
|
|
209
272
|
recordItem[fieldName] =
|
|
210
273
|
connectionRecord &&
|
|
@@ -238,13 +301,16 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
238
301
|
|
|
239
302
|
const predicates =
|
|
240
303
|
predicate && ModelPredicateCreator.getPredicates(predicate);
|
|
241
|
-
const
|
|
304
|
+
const keys = this.getIndexKeys(namespaceName, modelConstructor.name);
|
|
305
|
+
const queryByKey =
|
|
306
|
+
predicates && this.keyValueFromPredicate(predicates, keys);
|
|
307
|
+
|
|
242
308
|
const hasSort = pagination && pagination.sort;
|
|
243
309
|
const hasPagination = pagination && pagination.limit;
|
|
244
310
|
|
|
245
311
|
const records: T[] = await (async () => {
|
|
246
|
-
if (
|
|
247
|
-
const record = await this.
|
|
312
|
+
if (queryByKey) {
|
|
313
|
+
const record = await this.getByKey(storeName, queryByKey);
|
|
248
314
|
return record ? [record] : [];
|
|
249
315
|
}
|
|
250
316
|
|
|
@@ -264,11 +330,11 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
264
330
|
return await this.load(namespaceName, modelConstructor.name, records);
|
|
265
331
|
}
|
|
266
332
|
|
|
267
|
-
private async
|
|
333
|
+
private async getByKey<T extends PersistentModel>(
|
|
268
334
|
storeName: string,
|
|
269
|
-
|
|
335
|
+
keyValuePath: string
|
|
270
336
|
): Promise<T> {
|
|
271
|
-
const record = <T>await this.db.get(
|
|
337
|
+
const record = <T>await this.db.get(keyValuePath, storeName);
|
|
272
338
|
return record;
|
|
273
339
|
}
|
|
274
340
|
|
|
@@ -278,17 +344,29 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
278
344
|
return await this.db.getAll(storeName);
|
|
279
345
|
}
|
|
280
346
|
|
|
281
|
-
private
|
|
282
|
-
predicates: PredicatesGroup<T
|
|
283
|
-
|
|
347
|
+
private keyValueFromPredicate<T extends PersistentModel>(
|
|
348
|
+
predicates: PredicatesGroup<T>,
|
|
349
|
+
keys: string[]
|
|
350
|
+
): string | undefined {
|
|
284
351
|
const { predicates: predicateObjs } = predicates;
|
|
285
|
-
const idPredicate =
|
|
286
|
-
predicateObjs.length === 1 &&
|
|
287
|
-
(predicateObjs.find(
|
|
288
|
-
p => isPredicateObj(p) && p.field === 'id' && p.operator === 'eq'
|
|
289
|
-
) as PredicateObject<T>);
|
|
290
352
|
|
|
291
|
-
|
|
353
|
+
if (predicateObjs.length !== keys.length) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const keyValues = [];
|
|
358
|
+
|
|
359
|
+
for (const key of keys) {
|
|
360
|
+
const predicateObj = predicateObjs.find(
|
|
361
|
+
p => isPredicateObj(p) && p.field === key && p.operator === 'eq'
|
|
362
|
+
) as PredicateObject<T>;
|
|
363
|
+
|
|
364
|
+
predicateObj && keyValues.push(predicateObj.operand);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return keyValues.length === keys.length
|
|
368
|
+
? keyValues.join(DEFAULT_PRIMARY_KEY_SEPARATOR)
|
|
369
|
+
: undefined;
|
|
292
370
|
}
|
|
293
371
|
|
|
294
372
|
private async filterOnPredicate<T extends PersistentModel>(
|
|
@@ -328,6 +406,7 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
328
406
|
|
|
329
407
|
return records.slice(start, end);
|
|
330
408
|
}
|
|
409
|
+
|
|
331
410
|
return records;
|
|
332
411
|
}
|
|
333
412
|
|
|
@@ -337,6 +416,7 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
337
416
|
): Promise<T | undefined> {
|
|
338
417
|
const storeName = this.getStorenameForModel(modelConstructor);
|
|
339
418
|
const result = <T>await this.db.getOne(firstOrLast, storeName);
|
|
419
|
+
|
|
340
420
|
return result && this.modelInstanceCreator(modelConstructor, result);
|
|
341
421
|
}
|
|
342
422
|
|
|
@@ -372,6 +452,7 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
372
452
|
(acc, { items }) => acc.concat(items),
|
|
373
453
|
<T[]>[]
|
|
374
454
|
);
|
|
455
|
+
|
|
375
456
|
return [models, deletedModels];
|
|
376
457
|
} else {
|
|
377
458
|
await this.deleteTraverse(
|
|
@@ -396,11 +477,14 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
396
477
|
|
|
397
478
|
const modelConstructor = Object.getPrototypeOf(model)
|
|
398
479
|
.constructor as PersistentModelConstructor<T>;
|
|
399
|
-
const
|
|
480
|
+
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
400
481
|
|
|
401
482
|
const storeName = this.getStorenameForModel(modelConstructor);
|
|
483
|
+
|
|
402
484
|
if (condition) {
|
|
403
|
-
const
|
|
485
|
+
const keyValuePath = this.getIndexKeyValuesPath(model);
|
|
486
|
+
|
|
487
|
+
const fromDB = await this.db.get(keyValuePath, storeName);
|
|
404
488
|
|
|
405
489
|
if (fromDB === undefined) {
|
|
406
490
|
const msg = 'Model instance not found in storage';
|
|
@@ -421,25 +505,28 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
421
505
|
}
|
|
422
506
|
|
|
423
507
|
const relations =
|
|
424
|
-
this.schema.namespaces[
|
|
425
|
-
.
|
|
508
|
+
this.schema.namespaces[namespaceName].relationships[
|
|
509
|
+
modelConstructor.name
|
|
510
|
+
].relationTypes;
|
|
511
|
+
|
|
426
512
|
await this.deleteTraverse(
|
|
427
513
|
relations,
|
|
428
514
|
[model],
|
|
429
515
|
modelConstructor.name,
|
|
430
|
-
|
|
516
|
+
namespaceName,
|
|
431
517
|
deleteQueue
|
|
432
518
|
);
|
|
433
519
|
} else {
|
|
434
520
|
const relations =
|
|
435
|
-
this.schema.namespaces[
|
|
436
|
-
.
|
|
521
|
+
this.schema.namespaces[namespaceName].relationships[
|
|
522
|
+
modelConstructor.name
|
|
523
|
+
].relationTypes;
|
|
437
524
|
|
|
438
525
|
await this.deleteTraverse(
|
|
439
526
|
relations,
|
|
440
527
|
[model],
|
|
441
528
|
modelConstructor.name,
|
|
442
|
-
|
|
529
|
+
namespaceName,
|
|
443
530
|
deleteQueue
|
|
444
531
|
);
|
|
445
532
|
}
|
|
@@ -464,8 +551,8 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
464
551
|
for await (const item of items) {
|
|
465
552
|
if (item) {
|
|
466
553
|
if (typeof item === 'object') {
|
|
467
|
-
const
|
|
468
|
-
await this.db.delete(
|
|
554
|
+
const keyValuesPath: string = this.getIndexKeyValuesPath(item as T);
|
|
555
|
+
await this.db.delete(keyValuesPath, storeName);
|
|
469
556
|
}
|
|
470
557
|
}
|
|
471
558
|
}
|
|
@@ -487,10 +574,16 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
487
574
|
deleteQueue: { storeName: string; items: T[] }[]
|
|
488
575
|
): Promise<void> {
|
|
489
576
|
for await (const rel of relations) {
|
|
490
|
-
const {
|
|
577
|
+
const {
|
|
578
|
+
relationType,
|
|
579
|
+
modelName,
|
|
580
|
+
targetName,
|
|
581
|
+
targetNames,
|
|
582
|
+
associatedWith,
|
|
583
|
+
} = rel;
|
|
491
584
|
const storeName = this.getStorename(nameSpace, modelName);
|
|
492
585
|
|
|
493
|
-
const index:
|
|
586
|
+
const index: any =
|
|
494
587
|
getIndex(
|
|
495
588
|
this.schema.namespaces[nameSpace].relationships[modelName]
|
|
496
589
|
.relationTypes,
|
|
@@ -507,18 +600,20 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
507
600
|
switch (relationType) {
|
|
508
601
|
case 'HAS_ONE':
|
|
509
602
|
for await (const model of models) {
|
|
510
|
-
const hasOneIndex = index ||
|
|
511
|
-
|
|
603
|
+
const hasOneIndex = index || associatedWith;
|
|
512
604
|
const hasOneCustomField = targetName in model;
|
|
513
|
-
const
|
|
605
|
+
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
606
|
+
const value = hasOneCustomField ? model[targetName] : keyValuesPath;
|
|
607
|
+
|
|
514
608
|
if (!value) break;
|
|
515
609
|
|
|
516
610
|
const allRecords = await this.db.getAll(storeName);
|
|
611
|
+
|
|
517
612
|
const recordToDelete = allRecords.filter(
|
|
518
613
|
childItem => childItem[hasOneIndex] === value
|
|
519
|
-
);
|
|
614
|
+
) as T[];
|
|
520
615
|
|
|
521
|
-
await this.deleteTraverse(
|
|
616
|
+
await this.deleteTraverse<T>(
|
|
522
617
|
this.schema.namespaces[nameSpace].relationships[modelName]
|
|
523
618
|
.relationTypes,
|
|
524
619
|
recordToDelete,
|
|
@@ -530,12 +625,14 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
530
625
|
break;
|
|
531
626
|
case 'HAS_MANY':
|
|
532
627
|
for await (const model of models) {
|
|
628
|
+
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
629
|
+
|
|
533
630
|
const allRecords = await this.db.getAll(storeName);
|
|
534
631
|
const childrenArray = allRecords.filter(
|
|
535
|
-
childItem => childItem[index] ===
|
|
536
|
-
);
|
|
632
|
+
childItem => childItem[index] === keyValuesPath
|
|
633
|
+
) as T[];
|
|
537
634
|
|
|
538
|
-
await this.deleteTraverse(
|
|
635
|
+
await this.deleteTraverse<T>(
|
|
539
636
|
this.schema.namespaces[nameSpace].relationships[modelName]
|
|
540
637
|
.relationTypes,
|
|
541
638
|
childrenArray,
|
|
@@ -579,28 +676,31 @@ export class AsyncStorageAdapter implements Adapter {
|
|
|
579
676
|
const { name: modelName } = modelConstructor;
|
|
580
677
|
const namespaceName = this.namespaceResolver(modelConstructor);
|
|
581
678
|
const storeName = this.getStorename(namespaceName, modelName);
|
|
582
|
-
|
|
679
|
+
const keys = this.getIndexKeys(namespaceName, modelName);
|
|
583
680
|
const batch: ModelInstanceMetadata[] = [];
|
|
584
681
|
|
|
585
682
|
for (const item of items) {
|
|
586
|
-
const
|
|
683
|
+
const model = this.modelInstanceCreator(modelConstructor, item);
|
|
587
684
|
|
|
588
685
|
const connectedModels = traverseModel(
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
this.schema.namespaces[
|
|
686
|
+
modelName,
|
|
687
|
+
model,
|
|
688
|
+
this.schema.namespaces[namespaceName],
|
|
592
689
|
this.modelInstanceCreator,
|
|
593
690
|
this.getModelConstructorByModelName
|
|
594
691
|
);
|
|
595
692
|
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
)
|
|
693
|
+
const keyValuesPath = this.getIndexKeyValuesPath(model);
|
|
694
|
+
|
|
695
|
+
const { instance } = connectedModels.find(({ instance }) => {
|
|
696
|
+
const instanceKeyValuesPath = this.getIndexKeyValuesPath(instance);
|
|
697
|
+
return this.keysEqual([instanceKeyValuesPath], [keyValuesPath]);
|
|
698
|
+
});
|
|
599
699
|
|
|
600
700
|
batch.push(instance);
|
|
601
701
|
}
|
|
602
702
|
|
|
603
|
-
return await this.db.batchSave(storeName, batch);
|
|
703
|
+
return await this.db.batchSave(storeName, batch, keys);
|
|
604
704
|
}
|
|
605
705
|
}
|
|
606
706
|
|
|
@@ -15,6 +15,8 @@ const DATA = 'Data';
|
|
|
15
15
|
|
|
16
16
|
const monotonicFactoriesMap = new Map<string, ULID>();
|
|
17
17
|
|
|
18
|
+
const DEFAULT_PRIMARY_KEY_SEPARATOR = ':::';
|
|
19
|
+
|
|
18
20
|
class AsyncStorageDatabase {
|
|
19
21
|
/**
|
|
20
22
|
* Maps storeNames to a map of ulid->id
|
|
@@ -23,14 +25,17 @@ class AsyncStorageDatabase {
|
|
|
23
25
|
|
|
24
26
|
private storage = createInMemoryStore();
|
|
25
27
|
|
|
28
|
+
// Collection index is map of stores (i.e. sync, metadata, mutation event, and data)
|
|
26
29
|
private getCollectionIndex(storeName: string) {
|
|
27
30
|
if (!this._collectionInMemoryIndex.has(storeName)) {
|
|
28
31
|
this._collectionInMemoryIndex.set(storeName, new Map());
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
// Map of ulid->id
|
|
31
35
|
return this._collectionInMemoryIndex.get(storeName);
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
// Return ULID for store if it exists, otherwise create a new one
|
|
34
39
|
private getMonotonicFactory(storeName: string): ULID {
|
|
35
40
|
if (!monotonicFactoriesMap.has(storeName)) {
|
|
36
41
|
monotonicFactoriesMap.set(storeName, monotonicUlidFactory());
|
|
@@ -85,39 +90,61 @@ class AsyncStorageDatabase {
|
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
async save<T extends PersistentModel>(
|
|
93
|
+
async save<T extends PersistentModel>(
|
|
94
|
+
item: T,
|
|
95
|
+
storeName: string,
|
|
96
|
+
keys: string[],
|
|
97
|
+
keyValuesPath: string
|
|
98
|
+
) {
|
|
89
99
|
const ulid =
|
|
90
|
-
this.getCollectionIndex(storeName).get(
|
|
100
|
+
this.getCollectionIndex(storeName).get(keys[0]) ||
|
|
91
101
|
this.getMonotonicFactory(storeName)();
|
|
92
102
|
|
|
93
|
-
|
|
103
|
+
// Retrieve db key for item
|
|
104
|
+
const itemKey = this.getKeyForItem(storeName, keyValuesPath, ulid);
|
|
94
105
|
|
|
95
|
-
|
|
106
|
+
// Set key in collection index
|
|
107
|
+
this.getCollectionIndex(storeName).set(keyValuesPath, ulid);
|
|
96
108
|
|
|
109
|
+
// Save item in db
|
|
97
110
|
await this.storage.setItem(itemKey, JSON.stringify(item));
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
async batchSave<T extends PersistentModel>(
|
|
101
114
|
storeName: string,
|
|
102
|
-
items: ModelInstanceMetadata[]
|
|
115
|
+
items: ModelInstanceMetadata[],
|
|
116
|
+
keys: string[]
|
|
103
117
|
): Promise<[T, OpType][]> {
|
|
104
118
|
if (items.length === 0) {
|
|
105
119
|
return [];
|
|
106
120
|
}
|
|
107
121
|
|
|
108
122
|
const result: [T, OpType][] = [];
|
|
109
|
-
|
|
110
123
|
const collection = this.getCollectionIndex(storeName);
|
|
111
124
|
|
|
112
125
|
const keysToDelete = new Set<string>();
|
|
113
126
|
const keysToSave = new Set<string>();
|
|
114
127
|
const allItemsKeys = [];
|
|
115
128
|
const itemsMap: Record<string, { ulid: string; model: T }> = {};
|
|
129
|
+
|
|
130
|
+
/* Populate allItemKeys, keysToDelete, and keysToSave */
|
|
116
131
|
for (const item of items) {
|
|
117
|
-
|
|
118
|
-
const
|
|
132
|
+
// Extract keys from concatenated key path, map to item values
|
|
133
|
+
const keyValues = keys.map(field => item[field]);
|
|
119
134
|
|
|
120
|
-
const
|
|
135
|
+
const { _deleted } = item;
|
|
136
|
+
|
|
137
|
+
// If id is in the store, retrieve, otherwise generate new ULID
|
|
138
|
+
const ulid =
|
|
139
|
+
collection.get(keyValues.join(DEFAULT_PRIMARY_KEY_SEPARATOR)) ||
|
|
140
|
+
this.getMonotonicFactory(storeName)();
|
|
141
|
+
|
|
142
|
+
// Generate the "longer key" for the item
|
|
143
|
+
const key = this.getKeyForItem(
|
|
144
|
+
storeName,
|
|
145
|
+
keyValues.join(DEFAULT_PRIMARY_KEY_SEPARATOR),
|
|
146
|
+
ulid
|
|
147
|
+
);
|
|
121
148
|
|
|
122
149
|
allItemsKeys.push(key);
|
|
123
150
|
itemsMap[key] = { ulid, model: <T>(<unknown>item) };
|
|
@@ -136,6 +163,7 @@ class AsyncStorageDatabase {
|
|
|
136
163
|
.filter(([, v]) => !!v)
|
|
137
164
|
.reduce((set, [k]) => set.add(k), new Set<string>());
|
|
138
165
|
|
|
166
|
+
// Delete
|
|
139
167
|
await new Promise((resolve, reject) => {
|
|
140
168
|
if (keysToDelete.size === 0) {
|
|
141
169
|
resolve();
|
|
@@ -144,9 +172,15 @@ class AsyncStorageDatabase {
|
|
|
144
172
|
|
|
145
173
|
const keysToDeleteArray = Array.from(keysToDelete);
|
|
146
174
|
|
|
147
|
-
keysToDeleteArray.forEach(key =>
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
keysToDeleteArray.forEach(key => {
|
|
176
|
+
// key: full db key
|
|
177
|
+
// keys: PK and/or SK keys
|
|
178
|
+
const primaryKeyValues: string = keys
|
|
179
|
+
.map(field => itemsMap[key].model[field])
|
|
180
|
+
.join(DEFAULT_PRIMARY_KEY_SEPARATOR);
|
|
181
|
+
|
|
182
|
+
collection.delete(primaryKeyValues);
|
|
183
|
+
});
|
|
150
184
|
|
|
151
185
|
this.storage.multiRemove(keysToDeleteArray, (errors?: Error[]) => {
|
|
152
186
|
if (errors && errors.length > 0) {
|
|
@@ -157,6 +191,7 @@ class AsyncStorageDatabase {
|
|
|
157
191
|
});
|
|
158
192
|
});
|
|
159
193
|
|
|
194
|
+
// Save
|
|
160
195
|
await new Promise((resolve, reject) => {
|
|
161
196
|
if (keysToSave.size === 0) {
|
|
162
197
|
resolve();
|
|
@@ -169,12 +204,14 @@ class AsyncStorageDatabase {
|
|
|
169
204
|
]);
|
|
170
205
|
|
|
171
206
|
keysToSave.forEach(key => {
|
|
172
|
-
const {
|
|
173
|
-
model: { id },
|
|
174
|
-
ulid,
|
|
175
|
-
} = itemsMap[key];
|
|
207
|
+
const { model, ulid } = itemsMap[key];
|
|
176
208
|
|
|
177
|
-
|
|
209
|
+
// Retrieve values from model, use as key for collection index
|
|
210
|
+
const keyValues: string = keys
|
|
211
|
+
.map(field => model[field])
|
|
212
|
+
.join(DEFAULT_PRIMARY_KEY_SEPARATOR);
|
|
213
|
+
|
|
214
|
+
collection.set(keyValues, ulid);
|
|
178
215
|
});
|
|
179
216
|
|
|
180
217
|
this.storage.multiSet(entriesToSet, (errors?: Error[]) => {
|
|
@@ -201,11 +238,11 @@ class AsyncStorageDatabase {
|
|
|
201
238
|
}
|
|
202
239
|
|
|
203
240
|
async get<T extends PersistentModel>(
|
|
204
|
-
|
|
241
|
+
keyValuePath: string,
|
|
205
242
|
storeName: string
|
|
206
243
|
): Promise<T> {
|
|
207
|
-
const ulid = this.getCollectionIndex(storeName).get(
|
|
208
|
-
const itemKey = this.getKeyForItem(storeName,
|
|
244
|
+
const ulid = this.getCollectionIndex(storeName).get(keyValuePath);
|
|
245
|
+
const itemKey = this.getKeyForItem(storeName, keyValuePath, ulid);
|
|
209
246
|
const recordAsString = await this.storage.getItem(itemKey);
|
|
210
247
|
const record = recordAsString && JSON.parse(recordAsString);
|
|
211
248
|
return record;
|
|
@@ -227,6 +264,7 @@ class AsyncStorageDatabase {
|
|
|
227
264
|
return [id, ulid];
|
|
228
265
|
})();
|
|
229
266
|
const itemKey = this.getKeyForItem(storeName, itemId, ulid);
|
|
267
|
+
|
|
230
268
|
const itemString = itemKey && (await this.storage.getItem(itemKey));
|
|
231
269
|
|
|
232
270
|
const result = itemString ? JSON.parse(itemString) || undefined : undefined;
|
|
@@ -272,11 +310,10 @@ class AsyncStorageDatabase {
|
|
|
272
310
|
return records;
|
|
273
311
|
}
|
|
274
312
|
|
|
275
|
-
async delete(
|
|
276
|
-
const ulid = this.getCollectionIndex(storeName).get(
|
|
277
|
-
const itemKey = this.getKeyForItem(storeName,
|
|
278
|
-
|
|
279
|
-
this.getCollectionIndex(storeName).delete(id);
|
|
313
|
+
async delete(key: string, storeName: string) {
|
|
314
|
+
const ulid = this.getCollectionIndex(storeName).get(key);
|
|
315
|
+
const itemKey = this.getKeyForItem(storeName, key, ulid);
|
|
316
|
+
this.getCollectionIndex(storeName).delete(key);
|
|
280
317
|
await this.storage.removeItem(itemKey);
|
|
281
318
|
}
|
|
282
319
|
|