@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.
Files changed (137) hide show
  1. package/CHANGELOG.md +144 -17
  2. package/README.md +154 -0
  3. package/dist/aws-amplify-datastore.js +3344 -1405
  4. package/dist/aws-amplify-datastore.js.map +1 -1
  5. package/dist/aws-amplify-datastore.min.js +25 -95
  6. package/dist/aws-amplify-datastore.min.js.map +1 -1
  7. package/lib/authModeStrategies/multiAuthStrategy.d.ts +2 -2
  8. package/lib/authModeStrategies/multiAuthStrategy.js +32 -26
  9. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  10. package/lib/datastore/datastore.d.ts +17 -16
  11. package/lib/datastore/datastore.js +250 -78
  12. package/lib/datastore/datastore.js.map +1 -1
  13. package/lib/index.d.ts +3 -19
  14. package/lib/predicates/index.d.ts +3 -2
  15. package/lib/predicates/index.js +12 -2
  16. package/lib/predicates/index.js.map +1 -1
  17. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
  18. package/lib/storage/adapter/AsyncStorageAdapter.js +140 -66
  19. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  20. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
  21. package/lib/storage/adapter/AsyncStorageDatabase.js +57 -27
  22. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  23. package/lib/storage/adapter/InMemoryStore.js +2 -2
  24. package/lib/storage/adapter/InMemoryStore.js.map +1 -1
  25. package/lib/storage/adapter/IndexedDBAdapter.d.ts +7 -2
  26. package/lib/storage/adapter/IndexedDBAdapter.js +460 -253
  27. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  28. package/lib/storage/adapter/index.d.ts +1 -1
  29. package/lib/storage/storage.d.ts +3 -3
  30. package/lib/storage/storage.js +106 -28
  31. package/lib/storage/storage.js.map +1 -1
  32. package/lib/sync/index.d.ts +23 -7
  33. package/lib/sync/index.js +18 -17
  34. package/lib/sync/index.js.map +1 -1
  35. package/lib/sync/merger.d.ts +3 -3
  36. package/lib/sync/merger.js +8 -6
  37. package/lib/sync/merger.js.map +1 -1
  38. package/lib/sync/outbox.d.ts +2 -2
  39. package/lib/sync/outbox.js +12 -9
  40. package/lib/sync/outbox.js.map +1 -1
  41. package/lib/sync/processors/errorMaps.d.ts +17 -0
  42. package/lib/sync/processors/errorMaps.js +125 -0
  43. package/lib/sync/processors/errorMaps.js.map +1 -0
  44. package/lib/sync/processors/mutation.d.ts +22 -4
  45. package/lib/sync/processors/mutation.js +119 -51
  46. package/lib/sync/processors/mutation.js.map +1 -1
  47. package/lib/sync/processors/subscription.d.ts +4 -2
  48. package/lib/sync/processors/subscription.js +76 -41
  49. package/lib/sync/processors/subscription.js.map +1 -1
  50. package/lib/sync/processors/sync.d.ts +4 -2
  51. package/lib/sync/processors/sync.js +62 -27
  52. package/lib/sync/processors/sync.js.map +1 -1
  53. package/lib/sync/utils.d.ts +3 -2
  54. package/lib/sync/utils.js +54 -7
  55. package/lib/sync/utils.js.map +1 -1
  56. package/lib/types.d.ts +85 -33
  57. package/lib/types.js +31 -2
  58. package/lib/types.js.map +1 -1
  59. package/lib/util.d.ts +43 -33
  60. package/lib/util.js +161 -21
  61. package/lib/util.js.map +1 -1
  62. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +2 -2
  63. package/lib-esm/authModeStrategies/multiAuthStrategy.js +32 -26
  64. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  65. package/lib-esm/datastore/datastore.d.ts +17 -16
  66. package/lib-esm/datastore/datastore.js +252 -80
  67. package/lib-esm/datastore/datastore.js.map +1 -1
  68. package/lib-esm/index.d.ts +3 -19
  69. package/lib-esm/predicates/index.d.ts +3 -2
  70. package/lib-esm/predicates/index.js +13 -3
  71. package/lib-esm/predicates/index.js.map +1 -1
  72. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +6 -2
  73. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +140 -66
  74. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  75. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +4 -4
  76. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +57 -27
  77. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  78. package/lib-esm/storage/adapter/InMemoryStore.js +2 -2
  79. package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
  80. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +7 -2
  81. package/lib-esm/storage/adapter/IndexedDBAdapter.js +460 -253
  82. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  83. package/lib-esm/storage/adapter/index.d.ts +1 -1
  84. package/lib-esm/storage/storage.d.ts +3 -3
  85. package/lib-esm/storage/storage.js +106 -28
  86. package/lib-esm/storage/storage.js.map +1 -1
  87. package/lib-esm/sync/index.d.ts +23 -7
  88. package/lib-esm/sync/index.js +20 -19
  89. package/lib-esm/sync/index.js.map +1 -1
  90. package/lib-esm/sync/merger.d.ts +3 -3
  91. package/lib-esm/sync/merger.js +8 -6
  92. package/lib-esm/sync/merger.js.map +1 -1
  93. package/lib-esm/sync/outbox.d.ts +2 -2
  94. package/lib-esm/sync/outbox.js +13 -10
  95. package/lib-esm/sync/outbox.js.map +1 -1
  96. package/lib-esm/sync/processors/errorMaps.d.ts +17 -0
  97. package/lib-esm/sync/processors/errorMaps.js +119 -0
  98. package/lib-esm/sync/processors/errorMaps.js.map +1 -0
  99. package/lib-esm/sync/processors/mutation.d.ts +22 -4
  100. package/lib-esm/sync/processors/mutation.js +122 -54
  101. package/lib-esm/sync/processors/mutation.js.map +1 -1
  102. package/lib-esm/sync/processors/subscription.d.ts +4 -2
  103. package/lib-esm/sync/processors/subscription.js +77 -42
  104. package/lib-esm/sync/processors/subscription.js.map +1 -1
  105. package/lib-esm/sync/processors/sync.d.ts +4 -2
  106. package/lib-esm/sync/processors/sync.js +62 -27
  107. package/lib-esm/sync/processors/sync.js.map +1 -1
  108. package/lib-esm/sync/utils.d.ts +3 -2
  109. package/lib-esm/sync/utils.js +54 -8
  110. package/lib-esm/sync/utils.js.map +1 -1
  111. package/lib-esm/types.d.ts +85 -33
  112. package/lib-esm/types.js +29 -3
  113. package/lib-esm/types.js.map +1 -1
  114. package/lib-esm/util.d.ts +43 -33
  115. package/lib-esm/util.js +151 -18
  116. package/lib-esm/util.js.map +1 -1
  117. package/package.json +10 -9
  118. package/src/authModeStrategies/multiAuthStrategy.ts +24 -21
  119. package/src/datastore/datastore.ts +396 -143
  120. package/src/predicates/index.ts +32 -10
  121. package/src/storage/adapter/AsyncStorageAdapter.ts +161 -61
  122. package/src/storage/adapter/AsyncStorageDatabase.ts +62 -25
  123. package/src/storage/adapter/InMemoryStore.ts +2 -2
  124. package/src/storage/adapter/IndexedDBAdapter.ts +355 -101
  125. package/src/storage/adapter/index.ts +1 -1
  126. package/src/storage/storage.ts +85 -24
  127. package/src/sync/index.ts +74 -58
  128. package/src/sync/merger.ts +16 -4
  129. package/src/sync/outbox.ts +22 -8
  130. package/src/sync/processors/errorMaps.ts +93 -0
  131. package/src/sync/processors/mutation.ts +143 -84
  132. package/src/sync/processors/subscription.ts +67 -36
  133. package/src/sync/processors/sync.ts +37 -12
  134. package/src/sync/utils.ts +63 -15
  135. package/src/types.ts +242 -49
  136. package/src/util.ts +212 -35
  137. package/ssr/package.json +1 -1
@@ -8,7 +8,11 @@ import {
8
8
  ProducerModelPredicate,
9
9
  SchemaModel,
10
10
  } from '../types';
11
- import { exhaustiveCheck } from '../util';
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: ProducerModelPredicate<T>
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 createForId<T extends PersistentModel>(
165
+ static createForSingleField<T extends PersistentModel>(
162
166
  modelDefinition: SchemaModel,
163
- id: string
167
+ fieldName: string,
168
+ value: string
164
169
  ) {
165
- return ModelPredicateCreator.createPredicateBuilder<T>(modelDefinition).id(
166
- 'eq',
167
- <any>id
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[this.namespaceResolver(modelConstructor)],
161
+ this.schema.namespaces[namespaceName],
108
162
  this.modelInstanceCreator,
109
163
  this.getModelConstructorByModelName
110
164
  );
111
- const namespaceName = this.namespaceResolver(modelConstructor);
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
- return { storeName, item, instance };
171
+ const keys = this.getIndexKeys(namespaceName, modelName);
172
+ return { storeName, item, instance, keys };
118
173
  }
119
174
  );
120
- const fromDB = await this.db.get(model.id, storeName);
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
- const { id } = item;
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(id, storeName);
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
- if (id === model.id || opType === OpType.INSERT) {
146
- await this.db.save(item, storeName);
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 connectionRecord = await this.db.get(
191
- recordItem[getByfield],
192
- storeName
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 connectionRecord = await this.db.get(
205
- recordItem[targetName],
206
- storeName
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 queryById = predicates && this.idFromPredicate(predicates);
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 (queryById) {
247
- const record = await this.getById(storeName, queryById);
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 getById<T extends PersistentModel>(
333
+ private async getByKey<T extends PersistentModel>(
268
334
  storeName: string,
269
- id: string
335
+ keyValuePath: string
270
336
  ): Promise<T> {
271
- const record = <T>await this.db.get(id, storeName);
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 idFromPredicate<T extends PersistentModel>(
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
- return idPredicate && idPredicate.operand;
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 nameSpace = this.namespaceResolver(modelConstructor);
480
+ const namespaceName = this.namespaceResolver(modelConstructor);
400
481
 
401
482
  const storeName = this.getStorenameForModel(modelConstructor);
483
+
402
484
  if (condition) {
403
- const fromDB = await this.db.get(model.id, storeName);
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[nameSpace].relationships[modelConstructor.name]
425
- .relationTypes;
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
- nameSpace,
516
+ namespaceName,
431
517
  deleteQueue
432
518
  );
433
519
  } else {
434
520
  const relations =
435
- this.schema.namespaces[nameSpace].relationships[modelConstructor.name]
436
- .relationTypes;
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
- nameSpace,
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 id = item['id'];
468
- await this.db.delete(id, storeName);
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 { relationType, modelName, targetName } = rel;
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: string =
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 || 'byId';
511
-
603
+ const hasOneIndex = index || associatedWith;
512
604
  const hasOneCustomField = targetName in model;
513
- const value = hasOneCustomField ? model[targetName] : model.id;
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] === model.id
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 { id } = item;
683
+ const model = this.modelInstanceCreator(modelConstructor, item);
587
684
 
588
685
  const connectedModels = traverseModel(
589
- modelConstructor.name,
590
- this.modelInstanceCreator(modelConstructor, item),
591
- this.schema.namespaces[this.namespaceResolver(modelConstructor)],
686
+ modelName,
687
+ model,
688
+ this.schema.namespaces[namespaceName],
592
689
  this.modelInstanceCreator,
593
690
  this.getModelConstructorByModelName
594
691
  );
595
692
 
596
- const { instance } = connectedModels.find(
597
- ({ instance }) => instance.id === id
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>(item: T, storeName: string) {
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(item.id) ||
100
+ this.getCollectionIndex(storeName).get(keys[0]) ||
91
101
  this.getMonotonicFactory(storeName)();
92
102
 
93
- const itemKey = this.getKeyForItem(storeName, item.id, ulid);
103
+ // Retrieve db key for item
104
+ const itemKey = this.getKeyForItem(storeName, keyValuesPath, ulid);
94
105
 
95
- this.getCollectionIndex(storeName).set(item.id, ulid);
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
- const { id, _deleted } = item;
118
- const ulid = collection.get(id) || this.getMonotonicFactory(storeName)();
132
+ // Extract keys from concatenated key path, map to item values
133
+ const keyValues = keys.map(field => item[field]);
119
134
 
120
- const key = this.getKeyForItem(storeName, id, ulid);
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
- collection.delete(itemsMap[key].model.id)
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
- collection.set(id, ulid);
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
- id: string,
241
+ keyValuePath: string,
205
242
  storeName: string
206
243
  ): Promise<T> {
207
- const ulid = this.getCollectionIndex(storeName).get(id);
208
- const itemKey = this.getKeyForItem(storeName, id, ulid);
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(id: string, storeName: string) {
276
- const ulid = this.getCollectionIndex(storeName).get(id);
277
- const itemKey = this.getKeyForItem(storeName, id, ulid);
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