@aws-amplify/datastore 3.12.6-next.13 → 3.12.6-next.32

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 (162) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/lib/authModeStrategies/multiAuthStrategy.js +17 -64
  3. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  4. package/lib/datastore/datastore.js +682 -469
  5. package/lib/datastore/datastore.js.map +1 -1
  6. package/lib/index.js +2 -4
  7. package/lib/index.js.map +1 -1
  8. package/lib/predicates/index.js +12 -2
  9. package/lib/predicates/index.js.map +1 -1
  10. package/lib/storage/adapter/AsyncStorageAdapter.js +393 -298
  11. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  12. package/lib/storage/adapter/AsyncStorageDatabase.js +97 -122
  13. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  14. package/lib/storage/adapter/InMemoryStore.js +16 -67
  15. package/lib/storage/adapter/InMemoryStore.js.map +1 -1
  16. package/lib/storage/adapter/InMemoryStore.native.js +2 -4
  17. package/lib/storage/adapter/InMemoryStore.native.js.map +1 -1
  18. package/lib/storage/adapter/IndexedDBAdapter.js +497 -404
  19. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  20. package/lib/storage/adapter/getDefaultAdapter/index.js +3 -5
  21. package/lib/storage/adapter/getDefaultAdapter/index.js.map +1 -1
  22. package/lib/storage/adapter/getDefaultAdapter/index.native.js +2 -4
  23. package/lib/storage/adapter/getDefaultAdapter/index.native.js.map +1 -1
  24. package/lib/storage/storage.js +129 -151
  25. package/lib/storage/storage.js.map +1 -1
  26. package/lib/sync/datastoreConnectivity.js +13 -17
  27. package/lib/sync/datastoreConnectivity.js.map +1 -1
  28. package/lib/sync/datastoreReachability/index.native.js +2 -4
  29. package/lib/sync/datastoreReachability/index.native.js.map +1 -1
  30. package/lib/sync/index.js +544 -488
  31. package/lib/sync/index.js.map +1 -1
  32. package/lib/sync/merger.js +21 -80
  33. package/lib/sync/merger.js.map +1 -1
  34. package/lib/sync/outbox.js +95 -162
  35. package/lib/sync/outbox.js.map +1 -1
  36. package/lib/sync/processors/errorMaps.js +4 -34
  37. package/lib/sync/processors/errorMaps.js.map +1 -1
  38. package/lib/sync/processors/mutation.js +285 -312
  39. package/lib/sync/processors/mutation.js.map +1 -1
  40. package/lib/sync/processors/subscription.js +218 -259
  41. package/lib/sync/processors/subscription.js.map +1 -1
  42. package/lib/sync/processors/sync.js +141 -212
  43. package/lib/sync/processors/sync.js.map +1 -1
  44. package/lib/sync/utils.js +50 -61
  45. package/lib/sync/utils.js.map +1 -1
  46. package/lib/types.js +13 -39
  47. package/lib/types.js.map +1 -1
  48. package/lib/util.js +429 -242
  49. package/lib/util.js.map +1 -1
  50. package/lib-esm/authModeStrategies/multiAuthStrategy.d.ts +11 -0
  51. package/lib-esm/authModeStrategies/multiAuthStrategy.js +13 -57
  52. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  53. package/lib-esm/datastore/datastore.d.ts +107 -17
  54. package/lib-esm/datastore/datastore.js +649 -433
  55. package/lib-esm/datastore/datastore.js.map +1 -1
  56. package/lib-esm/index.d.ts +3 -19
  57. package/lib-esm/predicates/index.d.ts +3 -2
  58. package/lib-esm/predicates/index.js +13 -3
  59. package/lib-esm/predicates/index.js.map +1 -1
  60. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +4 -3
  61. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +356 -258
  62. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  63. package/lib-esm/storage/adapter/AsyncStorageDatabase.d.ts +14 -4
  64. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +67 -92
  65. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  66. package/lib-esm/storage/adapter/InMemoryStore.js +1 -52
  67. package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
  68. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +26 -4
  69. package/lib-esm/storage/adapter/IndexedDBAdapter.js +446 -346
  70. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  71. package/lib-esm/storage/adapter/index.d.ts +1 -1
  72. package/lib-esm/storage/storage.d.ts +1 -1
  73. package/lib-esm/storage/storage.js +94 -113
  74. package/lib-esm/storage/storage.js.map +1 -1
  75. package/lib-esm/sync/datastoreConnectivity.d.ts +1 -0
  76. package/lib-esm/sync/datastoreConnectivity.js +10 -11
  77. package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
  78. package/lib-esm/sync/index.d.ts +31 -5
  79. package/lib-esm/sync/index.js +525 -466
  80. package/lib-esm/sync/index.js.map +1 -1
  81. package/lib-esm/sync/merger.d.ts +9 -3
  82. package/lib-esm/sync/merger.js +14 -73
  83. package/lib-esm/sync/merger.js.map +1 -1
  84. package/lib-esm/sync/outbox.d.ts +2 -2
  85. package/lib-esm/sync/outbox.js +79 -146
  86. package/lib-esm/sync/outbox.js.map +1 -1
  87. package/lib-esm/sync/processors/errorMaps.js +1 -31
  88. package/lib-esm/sync/processors/errorMaps.js.map +1 -1
  89. package/lib-esm/sync/processors/mutation.d.ts +2 -0
  90. package/lib-esm/sync/processors/mutation.js +271 -295
  91. package/lib-esm/sync/processors/mutation.js.map +1 -1
  92. package/lib-esm/sync/processors/subscription.d.ts +2 -0
  93. package/lib-esm/sync/processors/subscription.js +214 -245
  94. package/lib-esm/sync/processors/subscription.js.map +1 -1
  95. package/lib-esm/sync/processors/sync.d.ts +2 -1
  96. package/lib-esm/sync/processors/sync.js +127 -195
  97. package/lib-esm/sync/processors/sync.js.map +1 -1
  98. package/lib-esm/sync/utils.d.ts +3 -2
  99. package/lib-esm/sync/utils.js +45 -57
  100. package/lib-esm/sync/utils.js.map +1 -1
  101. package/lib-esm/types.d.ts +65 -26
  102. package/lib-esm/types.js +10 -38
  103. package/lib-esm/types.js.map +1 -1
  104. package/lib-esm/util.d.ts +67 -24
  105. package/lib-esm/util.js +420 -233
  106. package/lib-esm/util.js.map +1 -1
  107. package/package.json +14 -7
  108. package/src/authModeStrategies/multiAuthStrategy.ts +12 -1
  109. package/src/datastore/datastore.ts +798 -397
  110. package/src/predicates/index.ts +32 -10
  111. package/src/storage/adapter/AsyncStorageAdapter.ts +309 -93
  112. package/src/storage/adapter/AsyncStorageDatabase.ts +74 -26
  113. package/src/storage/adapter/IndexedDBAdapter.ts +358 -134
  114. package/src/storage/adapter/index.ts +1 -1
  115. package/src/storage/storage.ts +69 -22
  116. package/src/sync/datastoreConnectivity.ts +6 -0
  117. package/src/sync/index.ts +521 -412
  118. package/src/sync/merger.ts +20 -4
  119. package/src/sync/outbox.ts +22 -9
  120. package/src/sync/processors/mutation.ts +188 -150
  121. package/src/sync/processors/subscription.ts +289 -253
  122. package/src/sync/processors/sync.ts +151 -138
  123. package/src/sync/utils.ts +67 -12
  124. package/src/types.ts +182 -30
  125. package/src/util.ts +505 -176
  126. package/build.js +0 -5
  127. package/dist/aws-amplify-datastore.js +0 -98255
  128. package/dist/aws-amplify-datastore.js.map +0 -1
  129. package/dist/aws-amplify-datastore.min.js +0 -66
  130. package/dist/aws-amplify-datastore.min.js.map +0 -1
  131. package/index.js +0 -7
  132. package/lib/authModeStrategies/defaultAuthStrategy.d.ts +0 -2
  133. package/lib/authModeStrategies/index.d.ts +0 -2
  134. package/lib/authModeStrategies/multiAuthStrategy.d.ts +0 -2
  135. package/lib/datastore/datastore.d.ts +0 -66
  136. package/lib/index.d.ts +0 -31
  137. package/lib/predicates/index.d.ts +0 -15
  138. package/lib/predicates/sort.d.ts +0 -8
  139. package/lib/ssr/index.d.ts +0 -3
  140. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +0 -40
  141. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +0 -29
  142. package/lib/storage/adapter/InMemoryStore.d.ts +0 -11
  143. package/lib/storage/adapter/InMemoryStore.native.d.ts +0 -1
  144. package/lib/storage/adapter/IndexedDBAdapter.d.ts +0 -37
  145. package/lib/storage/adapter/getDefaultAdapter/index.d.ts +0 -3
  146. package/lib/storage/adapter/getDefaultAdapter/index.native.d.ts +0 -3
  147. package/lib/storage/adapter/index.d.ts +0 -9
  148. package/lib/storage/storage.d.ts +0 -49
  149. package/lib/sync/datastoreConnectivity.d.ts +0 -15
  150. package/lib/sync/datastoreReachability/index.d.ts +0 -3
  151. package/lib/sync/datastoreReachability/index.native.d.ts +0 -3
  152. package/lib/sync/index.d.ts +0 -63
  153. package/lib/sync/merger.d.ts +0 -11
  154. package/lib/sync/outbox.d.ts +0 -27
  155. package/lib/sync/processors/errorMaps.d.ts +0 -17
  156. package/lib/sync/processors/mutation.d.ts +0 -56
  157. package/lib/sync/processors/subscription.d.ts +0 -31
  158. package/lib/sync/processors/sync.d.ts +0 -27
  159. package/lib/sync/utils.d.ts +0 -41
  160. package/lib/types.d.ts +0 -462
  161. package/lib/util.d.ts +0 -113
  162. package/webpack.config.dev.js +0 -6
package/src/util.ts CHANGED
@@ -26,9 +26,41 @@ import {
26
26
  NonModelTypeConstructor,
27
27
  DeferredCallbackResolverOptions,
28
28
  LimitTimerRaceResolvedValues,
29
+ SchemaModel,
30
+ ModelAttribute,
31
+ IndexesType,
32
+ ModelAssociation,
29
33
  } from './types';
30
34
  import { WordArray } from 'amazon-cognito-identity-js';
31
35
 
36
+ export const ID = 'id';
37
+
38
+ /**
39
+ * Used by the Async Storage Adapter to concatenate key values
40
+ * for a record. For instance, if a model has the following keys:
41
+ * `customId: ID! @primaryKey(sortKeyFields: ["createdAt"])`,
42
+ * we concatenate the `customId` and `createdAt` as:
43
+ * `12-234-5#2022-09-28T00:00:00.000Z`
44
+ */
45
+ export const DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR = '#';
46
+
47
+ /**
48
+ * Used for generating spinal-cased index name from an array of
49
+ * key field names.
50
+ * E.g. for keys `[id, title]` => 'id-title'
51
+ */
52
+ export const IDENTIFIER_KEY_SEPARATOR = '-';
53
+
54
+ export const errorMessages = {
55
+ idEmptyString: 'An index field cannot contain an empty string value',
56
+ queryByPkWithCompositeKeyPresent:
57
+ 'Models with composite primary keys cannot be queried by a single key value. Use object literal syntax for composite keys instead: https://docs.amplify.aws/lib/datastore/advanced-workflows/q/platform/js/#querying-records-with-custom-primary-keys',
58
+ deleteByPkWithCompositeKeyPresent:
59
+ 'Models with composite primary keys cannot be deleted by a single key value, unless using a predicate. Use object literal syntax for composite keys instead: https://docs.amplify.aws/lib/datastore/advanced-workflows/q/platform/js/#querying-records-with-custom-primary-keys',
60
+ observeWithObjectLiteral:
61
+ 'Object literal syntax cannot be used with observe. Use a predicate instead: https://docs.amplify.aws/lib/datastore/data-access/q/platform/js/#predicates',
62
+ };
63
+
32
64
  export enum NAMESPACES {
33
65
  DATASTORE = 'datastore',
34
66
  USER = 'user',
@@ -162,154 +194,6 @@ export const isNonModelConstructor = (
162
194
  return nonModelClasses.has(obj);
163
195
  };
164
196
 
165
- /*
166
- When we have GSI(s) with composite sort keys defined on a model
167
- There are some very particular rules regarding which fields must be included in the update mutation input
168
- The field selection becomes more complex as the number of GSIs with composite sort keys grows
169
-
170
- To summarize: any time we update a field that is part of the composite sort key of a GSI, we must include:
171
- 1. all of the other fields in that composite sort key
172
- 2. all of the fields from any other composite sort key that intersect with the fields from 1.
173
-
174
- E.g.,
175
- Model @model
176
- @key(name: 'key1' fields: ['hk', 'a', 'b', 'c'])
177
- @key(name: 'key2' fields: ['hk', 'a', 'b', 'd'])
178
- @key(name: 'key3' fields: ['hk', 'x', 'y', 'z'])
179
-
180
- Model.a is updated => include ['a', 'b', 'c', 'd']
181
- Model.c is updated => include ['a', 'b', 'c', 'd']
182
- Model.d is updated => include ['a', 'b', 'c', 'd']
183
- Model.x is updated => include ['x', 'y', 'z']
184
-
185
- This function accepts a model's attributes and returns grouped sets of composite key fields
186
- Using our example Model above, the function will return:
187
- [
188
- Set('a', 'b', 'c', 'd'),
189
- Set('x', 'y', 'z'),
190
- ]
191
-
192
- This gives us the opportunity to correctly include the required fields for composite keys
193
- When crafting the mutation input in Storage.getUpdateMutationInput
194
-
195
- See 'processCompositeKeys' test in util.test.ts for more examples
196
- */
197
- export const processCompositeKeys = (
198
- attributes: ModelAttributes
199
- ): Set<string>[] => {
200
- const extractCompositeSortKey = ({
201
- properties: {
202
- // ignore the HK (fields[0]) we only need to include the composite sort key fields[1...n]
203
- fields: [, ...sortKeyFields],
204
- },
205
- }) => sortKeyFields;
206
-
207
- const compositeKeyFields = attributes
208
- .filter(isModelAttributeCompositeKey)
209
- .map(extractCompositeSortKey);
210
-
211
- /*
212
- if 2 sets of fields have any intersecting fields => combine them into 1 union set
213
- e.g., ['a', 'b', 'c'] and ['a', 'b', 'd'] => ['a', 'b', 'c', 'd']
214
- */
215
- const combineIntersecting = (fields): Set<string>[] =>
216
- fields.reduce((combined, sortKeyFields) => {
217
- const sortKeyFieldsSet = new Set(sortKeyFields);
218
-
219
- if (combined.length === 0) {
220
- combined.push(sortKeyFieldsSet);
221
- return combined;
222
- }
223
-
224
- // does the current set share values with another set we've already added to `combined`?
225
- const intersectingSetIdx = combined.findIndex(existingSet => {
226
- return [...existingSet].some(f => sortKeyFieldsSet.has(f));
227
- });
228
-
229
- if (intersectingSetIdx > -1) {
230
- const union = new Set([
231
- ...combined[intersectingSetIdx],
232
- ...sortKeyFieldsSet,
233
- ]);
234
- // combine the current set with the intersecting set we found above
235
- combined[intersectingSetIdx] = union;
236
- } else {
237
- // none of the sets in `combined` have intersecting values with the current set
238
- combined.push(sortKeyFieldsSet);
239
- }
240
-
241
- return combined;
242
- }, []);
243
-
244
- const initial = combineIntersecting(compositeKeyFields);
245
- // a single pass pay not be enough to correctly combine all the fields
246
- // call the function once more to get a final merged list of sets
247
- const combined = combineIntersecting(initial);
248
-
249
- return combined;
250
- };
251
-
252
- export const establishRelationAndKeys = (
253
- namespace: SchemaNamespace
254
- ): [RelationshipType, ModelKeys] => {
255
- const relationship: RelationshipType = {};
256
- const keys: ModelKeys = {};
257
-
258
- Object.keys(namespace.models).forEach((mKey: string) => {
259
- relationship[mKey] = { indexes: [], relationTypes: [] };
260
- keys[mKey] = {};
261
-
262
- const model = namespace.models[mKey];
263
- Object.keys(model.fields).forEach((attr: string) => {
264
- const fieldAttribute = model.fields[attr];
265
- if (
266
- typeof fieldAttribute.type === 'object' &&
267
- 'model' in fieldAttribute.type
268
- ) {
269
- const connectionType = fieldAttribute.association.connectionType;
270
- relationship[mKey].relationTypes.push({
271
- fieldName: fieldAttribute.name,
272
- modelName: fieldAttribute.type.model,
273
- relationType: connectionType,
274
- targetName: fieldAttribute.association['targetName'],
275
- associatedWith: fieldAttribute.association['associatedWith'],
276
- });
277
-
278
- if (connectionType === 'BELONGS_TO') {
279
- relationship[mKey].indexes.push(
280
- fieldAttribute.association['targetName']
281
- );
282
- }
283
- }
284
- });
285
-
286
- if (model.attributes) {
287
- keys[mKey].compositeKeys = processCompositeKeys(model.attributes);
288
-
289
- for (const attribute of model.attributes) {
290
- if (!isModelAttributeKey(attribute)) {
291
- continue;
292
- }
293
-
294
- if (isModelAttributePrimaryKey(attribute)) {
295
- keys[mKey].primaryKey = attribute.properties.fields;
296
- }
297
-
298
- const { fields } = attribute.properties;
299
- for (const field of fields) {
300
- // only add index if it hasn't already been added
301
- const exists = relationship[mKey].indexes.includes(field);
302
- if (!exists) {
303
- relationship[mKey].indexes.push(field);
304
- }
305
- }
306
- }
307
- }
308
- });
309
-
310
- return [relationship, keys];
311
- };
312
-
313
197
  const topologicallySortedModels = new WeakMap<SchemaNamespace, string[]>();
314
198
 
315
199
  export const traverseModel = <T extends PersistentModel>(
@@ -323,12 +207,14 @@ export const traverseModel = <T extends PersistentModel>(
323
207
  ) => PersistentModelConstructor<any>
324
208
  ) => {
325
209
  const relationships = namespace.relationships;
210
+
326
211
  const modelConstructor = getModelConstructorByModelName(
327
212
  namespace.name,
328
213
  srcModelName
329
214
  );
330
215
 
331
216
  const relation = relationships[srcModelName];
217
+
332
218
  const result: {
333
219
  modelName: string;
334
220
  item: T;
@@ -362,15 +248,36 @@ export const traverseModel = <T extends PersistentModel>(
362
248
  instance: modelInstance,
363
249
  });
364
250
 
365
- // targetName will be defined for Has One if feature flag
251
+ const targetNames: string[] | undefined =
252
+ extractTargetNamesFromSrc(rItem);
253
+
254
+ // `targetName` will be defined for Has One if feature flag
366
255
  // https://docs.amplify.aws/cli/reference/feature-flags/#useAppsyncModelgenPlugin
367
256
  // is true (default as of 5/7/21)
368
257
  // Making this conditional for backward-compatibility
369
- if (rItem.targetName) {
370
- (<any>draftInstance)[rItem.targetName] = (<PersistentModel>(
371
- draftInstance[rItem.fieldName]
372
- )).id;
373
- delete draftInstance[rItem.fieldName];
258
+ if (targetNames) {
259
+ targetNames.forEach((targetName, idx) => {
260
+ // Get the connected record
261
+ const relatedRecordInProxy = <PersistentModel>(
262
+ draftInstance[rItem.fieldName]
263
+ );
264
+
265
+ // Previously, we used the hardcoded 'id' as they key,
266
+ // now we need the value of the key to get the PK (and SK)
267
+ // values from the related record
268
+
269
+ const { primaryKey } = namespace.keys[modelConstructor.name];
270
+ const keyField = primaryKey && primaryKey[idx];
271
+
272
+ // Get the value
273
+ const relatedRecordInProxyPkValue =
274
+ relatedRecordInProxy[keyField];
275
+
276
+ // Set the targetName value
277
+ (<any>draftInstance)[targetName] = relatedRecordInProxyPkValue;
278
+ });
279
+ // Delete the instance from the proxy
280
+ delete (<any>draftInstance)[rItem.fieldName];
374
281
  } else {
375
282
  (<any>draftInstance)[rItem.fieldName] = (<PersistentModel>(
376
283
  draftInstance[rItem.fieldName]
@@ -405,10 +312,33 @@ export const traverseModel = <T extends PersistentModel>(
405
312
  }
406
313
 
407
314
  if (draftInstance[rItem.fieldName]) {
408
- (<any>draftInstance)[rItem.targetName] = (<PersistentModel>(
409
- draftInstance[rItem.fieldName]
410
- )).id;
411
- delete draftInstance[rItem.fieldName];
315
+ const targetNames: string[] | undefined =
316
+ extractTargetNamesFromSrc(rItem);
317
+
318
+ if (targetNames) {
319
+ targetNames.forEach((targetName, idx) => {
320
+ // Get the connected record
321
+ const relatedRecordInProxy = <PersistentModel>(
322
+ draftInstance[rItem.fieldName]
323
+ );
324
+ // Previously, we used the hardcoded `id` for the key.
325
+ // Now, we need the value of the key to get the PK (and SK)
326
+ // values from the related record
327
+ const { primaryKey } = namespace.keys[modelConstructor.name];
328
+
329
+ // fall back to ID if
330
+ const keyField = primaryKey && primaryKey[idx];
331
+
332
+ // Get the value
333
+ const relatedRecordInProxyPkValue =
334
+ relatedRecordInProxy[keyField];
335
+
336
+ // Set the targetName value
337
+ (<any>draftInstance)[targetName] = relatedRecordInProxyPkValue;
338
+ });
339
+ // Delete the instance from the proxy
340
+ delete (<any>draftInstance)[rItem.fieldName];
341
+ }
412
342
  }
413
343
 
414
344
  break;
@@ -446,24 +376,6 @@ export const traverseModel = <T extends PersistentModel>(
446
376
  return result;
447
377
  };
448
378
 
449
- export const getIndex = (rel: RelationType[], src: string): string => {
450
- let index = '';
451
- rel.some((relItem: RelationType) => {
452
- if (relItem.modelName === src) {
453
- index = relItem.targetName;
454
- }
455
- });
456
- return index;
457
- };
458
-
459
- export const getIndexFromAssociation = (
460
- indexes: string[],
461
- src: string
462
- ): string => {
463
- const index = indexes.find(idx => idx === src);
464
- return index;
465
- };
466
-
467
379
  let privateModeCheckResult;
468
380
 
469
381
  export const isPrivateMode = () => {
@@ -505,6 +417,96 @@ export const isPrivateMode = () => {
505
417
  });
506
418
  };
507
419
 
420
+ let safariCompatabilityModeResult;
421
+
422
+ /**
423
+ * Whether the browser's implementation of IndexedDB breaks on array lookups
424
+ * against composite indexes whose keypath contains a single column.
425
+ *
426
+ * E.g., Whether `store.createIndex(indexName, ['id'])` followed by
427
+ * `store.index(indexName).get([1])` will *ever* return records.
428
+ *
429
+ * In all known, modern Safari browsers as of Q4 2022, the query against an index like
430
+ * this will *always* return `undefined`. So, the index needs to be created as a scalar.
431
+ */
432
+ export const isSafariCompatabilityMode: () => Promise<boolean> = async () => {
433
+ try {
434
+ const dbName = uuid();
435
+ const storeName = 'indexedDBFeatureProbeStore';
436
+ const indexName = 'idx';
437
+
438
+ if (indexedDB === null) return false;
439
+
440
+ if (safariCompatabilityModeResult !== undefined) {
441
+ return safariCompatabilityModeResult;
442
+ }
443
+
444
+ const db: IDBDatabase | false = await new Promise(resolve => {
445
+ const dbOpenRequest = indexedDB.open(dbName);
446
+ dbOpenRequest.onerror = () => resolve(false);
447
+
448
+ dbOpenRequest.onsuccess = () => {
449
+ const db = dbOpenRequest.result;
450
+ resolve(db);
451
+ };
452
+
453
+ dbOpenRequest.onupgradeneeded = (event: any) => {
454
+ const db = event?.target?.result;
455
+
456
+ db.onerror = () => resolve(false);
457
+
458
+ const store = db.createObjectStore(storeName, {
459
+ autoIncrement: true,
460
+ });
461
+
462
+ store.createIndex(indexName, ['id']);
463
+ };
464
+ });
465
+
466
+ if (!db) {
467
+ throw new Error('Could not open probe DB');
468
+ }
469
+
470
+ const rwTx = db.transaction(storeName, 'readwrite');
471
+ const rwStore = rwTx.objectStore(storeName);
472
+ rwStore.add({
473
+ id: 1,
474
+ });
475
+
476
+ (rwTx as any).commit();
477
+
478
+ const result = await new Promise(resolve => {
479
+ const tx = db.transaction(storeName, 'readonly');
480
+ const store = tx.objectStore(storeName);
481
+ const index = store.index(indexName);
482
+
483
+ const getRequest = index.get([1]);
484
+
485
+ getRequest.onerror = () => resolve(false);
486
+
487
+ getRequest.onsuccess = (event: any) => {
488
+ resolve(event?.target?.result);
489
+ };
490
+ });
491
+
492
+ if (db && typeof db.close === 'function') {
493
+ await db.close();
494
+ }
495
+
496
+ await indexedDB.deleteDatabase(dbName);
497
+
498
+ if (result === undefined) {
499
+ safariCompatabilityModeResult = true;
500
+ } else {
501
+ safariCompatabilityModeResult = false;
502
+ }
503
+ } catch (error) {
504
+ safariCompatabilityModeResult = false;
505
+ }
506
+
507
+ return safariCompatabilityModeResult;
508
+ };
509
+
508
510
  const randomBytes = (nBytes: number): Buffer => {
509
511
  return Buffer.from(new WordArray().random(nBytes).toString(), 'hex');
510
512
  };
@@ -811,3 +813,330 @@ export function mergePatches<T>(
811
813
  );
812
814
  return patches;
813
815
  }
816
+
817
+ export const getStorename = (namespace: string, modelName: string) => {
818
+ const storeName = `${namespace}_${modelName}`;
819
+
820
+ return storeName;
821
+ };
822
+
823
+ //#region Key Utils
824
+
825
+ /*
826
+ When we have GSI(s) with composite sort keys defined on a model
827
+ There are some very particular rules regarding which fields must be included in the update mutation input
828
+ The field selection becomes more complex as the number of GSIs with composite sort keys grows
829
+
830
+ To summarize: any time we update a field that is part of the composite sort key of a GSI, we must include:
831
+ 1. all of the other fields in that composite sort key
832
+ 2. all of the fields from any other composite sort key that intersect with the fields from 1.
833
+
834
+ E.g.,
835
+ Model @model
836
+ @key(name: 'key1' fields: ['hk', 'a', 'b', 'c'])
837
+ @key(name: 'key2' fields: ['hk', 'a', 'b', 'd'])
838
+ @key(name: 'key3' fields: ['hk', 'x', 'y', 'z'])
839
+
840
+ Model.a is updated => include ['a', 'b', 'c', 'd']
841
+ Model.c is updated => include ['a', 'b', 'c', 'd']
842
+ Model.d is updated => include ['a', 'b', 'c', 'd']
843
+ Model.x is updated => include ['x', 'y', 'z']
844
+
845
+ This function accepts a model's attributes and returns grouped sets of composite key fields
846
+ Using our example Model above, the function will return:
847
+ [
848
+ Set('a', 'b', 'c', 'd'),
849
+ Set('x', 'y', 'z'),
850
+ ]
851
+
852
+ This gives us the opportunity to correctly include the required fields for composite keys
853
+ When crafting the mutation input in Storage.getUpdateMutationInput
854
+
855
+ See 'processCompositeKeys' test in util.test.ts for more examples
856
+ */
857
+ export const processCompositeKeys = (
858
+ attributes: ModelAttributes
859
+ ): Set<string>[] => {
860
+ const extractCompositeSortKey = ({
861
+ properties: {
862
+ // ignore the HK (fields[0]) we only need to include the composite sort key fields[1...n]
863
+ fields: [, ...sortKeyFields],
864
+ },
865
+ }) => sortKeyFields;
866
+
867
+ const compositeKeyFields = attributes
868
+ .filter(isModelAttributeCompositeKey)
869
+ .map(extractCompositeSortKey);
870
+
871
+ /*
872
+ if 2 sets of fields have any intersecting fields => combine them into 1 union set
873
+ e.g., ['a', 'b', 'c'] and ['a', 'b', 'd'] => ['a', 'b', 'c', 'd']
874
+ */
875
+ const combineIntersecting = (fields): Set<string>[] =>
876
+ fields.reduce((combined, sortKeyFields) => {
877
+ const sortKeyFieldsSet = new Set(sortKeyFields);
878
+
879
+ if (combined.length === 0) {
880
+ combined.push(sortKeyFieldsSet);
881
+ return combined;
882
+ }
883
+
884
+ // does the current set share values with another set we've already added to `combined`?
885
+ const intersectingSetIdx = combined.findIndex(existingSet => {
886
+ return [...existingSet].some(f => sortKeyFieldsSet.has(f));
887
+ });
888
+
889
+ if (intersectingSetIdx > -1) {
890
+ const union = new Set([
891
+ ...combined[intersectingSetIdx],
892
+ ...sortKeyFieldsSet,
893
+ ]);
894
+ // combine the current set with the intersecting set we found above
895
+ combined[intersectingSetIdx] = union;
896
+ } else {
897
+ // none of the sets in `combined` have intersecting values with the current set
898
+ combined.push(sortKeyFieldsSet);
899
+ }
900
+
901
+ return combined;
902
+ }, []);
903
+
904
+ const initial = combineIntersecting(compositeKeyFields);
905
+ // a single pass pay not be enough to correctly combine all the fields
906
+ // call the function once more to get a final merged list of sets
907
+ const combined = combineIntersecting(initial);
908
+
909
+ return combined;
910
+ };
911
+
912
+ export const extractKeyIfExists = (
913
+ modelDefinition: SchemaModel
914
+ ): ModelAttribute | undefined => {
915
+ const keyAttribute = modelDefinition?.attributes?.find(isModelAttributeKey);
916
+
917
+ return keyAttribute;
918
+ };
919
+
920
+ export const extractPrimaryKeyFieldNames = (
921
+ modelDefinition: SchemaModel
922
+ ): string[] => {
923
+ const keyAttribute = extractKeyIfExists(modelDefinition);
924
+ if (keyAttribute && isModelAttributePrimaryKey(keyAttribute)) {
925
+ return keyAttribute.properties.fields;
926
+ }
927
+
928
+ return [ID];
929
+ };
930
+
931
+ export const extractPrimaryKeyValues = <T extends PersistentModel>(
932
+ model: T,
933
+ keyFields: string[]
934
+ ): string[] => {
935
+ return keyFields.map(key => model[key]);
936
+ };
937
+
938
+ export const extractPrimaryKeysAndValues = <T extends PersistentModel>(
939
+ model: T,
940
+ keyFields: string[]
941
+ ): any => {
942
+ const primaryKeysAndValues = {};
943
+ keyFields.forEach(key => (primaryKeysAndValues[key] = model[key]));
944
+ return primaryKeysAndValues;
945
+ };
946
+
947
+ // IdentifierFields<ManagedIdentifier>
948
+ // Default behavior without explicit @primaryKey defined
949
+ export const isIdManaged = (modelDefinition: SchemaModel): boolean => {
950
+ const keyAttribute = extractKeyIfExists(modelDefinition);
951
+
952
+ if (keyAttribute && isModelAttributePrimaryKey(keyAttribute)) {
953
+ return false;
954
+ }
955
+
956
+ return true;
957
+ };
958
+
959
+ // IdentifierFields<OptionallyManagedIdentifier>
960
+ // @primaryKey with explicit `id` in the PK. Single key or composite
961
+ export const isIdOptionallyManaged = (
962
+ modelDefinition: SchemaModel
963
+ ): boolean => {
964
+ const keyAttribute = extractKeyIfExists(modelDefinition);
965
+
966
+ if (keyAttribute && isModelAttributePrimaryKey(keyAttribute)) {
967
+ return keyAttribute.properties.fields[0] === ID;
968
+ }
969
+
970
+ return false;
971
+ };
972
+
973
+ export const establishRelationAndKeys = (
974
+ namespace: SchemaNamespace
975
+ ): [RelationshipType, ModelKeys] => {
976
+ const relationship: RelationshipType = {};
977
+ const keys: ModelKeys = {};
978
+
979
+ Object.keys(namespace.models).forEach((mKey: string) => {
980
+ relationship[mKey] = { indexes: [], relationTypes: [] };
981
+ keys[mKey] = {};
982
+
983
+ const model = namespace.models[mKey];
984
+ Object.keys(model.fields).forEach((attr: string) => {
985
+ const fieldAttribute = model.fields[attr];
986
+ if (
987
+ typeof fieldAttribute.type === 'object' &&
988
+ 'model' in fieldAttribute.type
989
+ ) {
990
+ const connectionType = fieldAttribute.association.connectionType;
991
+ relationship[mKey].relationTypes.push({
992
+ fieldName: fieldAttribute.name,
993
+ modelName: fieldAttribute.type.model,
994
+ relationType: connectionType,
995
+ targetName: fieldAttribute.association['targetName'],
996
+ targetNames: fieldAttribute.association['targetNames'],
997
+ associatedWith: fieldAttribute.association['associatedWith'],
998
+ });
999
+
1000
+ if (connectionType === 'BELONGS_TO') {
1001
+ const targetNames = extractTargetNamesFromSrc(
1002
+ fieldAttribute.association
1003
+ );
1004
+
1005
+ if (targetNames) {
1006
+ const idxName = indexNameFromKeys(targetNames);
1007
+ relationship[mKey].indexes.push([idxName, targetNames]);
1008
+ }
1009
+ }
1010
+ }
1011
+ });
1012
+
1013
+ if (model.attributes) {
1014
+ keys[mKey].compositeKeys = processCompositeKeys(model.attributes);
1015
+
1016
+ for (const attribute of model.attributes) {
1017
+ if (!isModelAttributeKey(attribute)) {
1018
+ continue;
1019
+ }
1020
+
1021
+ const { fields } = attribute.properties;
1022
+
1023
+ if (isModelAttributePrimaryKey(attribute)) {
1024
+ keys[mKey].primaryKey = fields;
1025
+ continue;
1026
+ }
1027
+
1028
+ // create indexes for all other keys
1029
+ const idxName = indexNameFromKeys(fields);
1030
+ const idxExists = relationship[mKey].indexes.find(
1031
+ ([index]) => index === idxName
1032
+ );
1033
+
1034
+ if (!idxExists) {
1035
+ relationship[mKey].indexes.push([idxName, fields]);
1036
+ }
1037
+ }
1038
+ }
1039
+
1040
+ // set 'id' as the PK for models without a custom PK explicitly defined
1041
+ if (!keys[mKey].primaryKey) {
1042
+ keys[mKey].primaryKey = [ID];
1043
+ }
1044
+
1045
+ // create primary index
1046
+ relationship[mKey].indexes.push([
1047
+ 'byPk',
1048
+ keys[mKey].primaryKey as string[],
1049
+ { unique: true },
1050
+ ]);
1051
+ });
1052
+
1053
+ return [relationship, keys];
1054
+ };
1055
+
1056
+ export const getIndex = (
1057
+ rel: RelationType[],
1058
+ src: string
1059
+ ): string | undefined => {
1060
+ let indexName;
1061
+ rel.some((relItem: RelationType) => {
1062
+ if (relItem.modelName === src) {
1063
+ const targetNames = extractTargetNamesFromSrc(relItem);
1064
+ indexName = targetNames && indexNameFromKeys(targetNames);
1065
+ return true;
1066
+ }
1067
+ });
1068
+ return indexName;
1069
+ };
1070
+
1071
+ export const getIndexFromAssociation = (
1072
+ indexes: IndexesType,
1073
+ src: string | string[]
1074
+ ): string | undefined => {
1075
+ let indexName: string;
1076
+
1077
+ if (Array.isArray(src)) {
1078
+ indexName = indexNameFromKeys(src);
1079
+ } else {
1080
+ indexName = src;
1081
+ }
1082
+
1083
+ const associationIndex = indexes.find(([idxName]) => idxName === indexName);
1084
+ return associationIndex && associationIndex[0];
1085
+ };
1086
+
1087
+ /**
1088
+ * Backwards-compatability for schema generated prior to custom primary key support:
1089
+ the single field `targetName` has been replaced with an array of `targetNames`.
1090
+ `targetName` and `targetNames` are exclusive (will never exist on the same schema)
1091
+ * @param src {RelationType | ModelAssociation | undefined}
1092
+ * @returns array of targetNames, or `undefined`
1093
+ */
1094
+ export const extractTargetNamesFromSrc = (
1095
+ src: RelationType | ModelAssociation | undefined
1096
+ ): string[] | undefined => {
1097
+ const targetName = src?.targetName;
1098
+ const targetNames = src?.targetNames;
1099
+
1100
+ if (Array.isArray(targetNames)) {
1101
+ return targetNames;
1102
+ } else if (typeof targetName === 'string') {
1103
+ return [targetName];
1104
+ } else {
1105
+ return undefined;
1106
+ }
1107
+ };
1108
+
1109
+ // Generates spinal-cased index name from an array of key field names
1110
+ // E.g. for keys `[id, title]` => 'id-title'
1111
+ export const indexNameFromKeys = (keys: string[]): string => {
1112
+ return keys.reduce((prev: string, cur: string, idx: number) => {
1113
+ if (idx === 0) {
1114
+ return cur;
1115
+ }
1116
+ return `${prev}${IDENTIFIER_KEY_SEPARATOR}${cur}`;
1117
+ }, '');
1118
+ };
1119
+
1120
+ export const keysEqual = (keysA, keysB): boolean => {
1121
+ if (keysA.length !== keysB.length) {
1122
+ return false;
1123
+ }
1124
+
1125
+ return keysA.every((key, idx) => key === keysB[idx]);
1126
+ };
1127
+
1128
+ // Returns primary keys for a model
1129
+ export const getIndexKeys = (
1130
+ namespace: SchemaNamespace,
1131
+ modelName: string
1132
+ ): string[] => {
1133
+ const keyPath = namespace?.keys[modelName]?.primaryKey;
1134
+
1135
+ if (keyPath) {
1136
+ return keyPath;
1137
+ }
1138
+
1139
+ return [ID];
1140
+ };
1141
+
1142
+ //#endregion