@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
@@ -1,4 +1,4 @@
1
- import API, { GraphQLResult, GRAPHQL_AUTH_MODE } from '@aws-amplify/api';
1
+ import { API, GraphQLResult, GRAPHQL_AUTH_MODE } from '@aws-amplify/api';
2
2
  import Observable from 'zen-observable-ts';
3
3
  import {
4
4
  InternalSchema,
@@ -26,6 +26,7 @@ import {
26
26
  ConsoleLogger as Logger,
27
27
  Hub,
28
28
  NonRetryableError,
29
+ BackgroundProcessManager,
29
30
  } from '@aws-amplify/core';
30
31
  import { ModelPredicateCreator } from '../../predicates';
31
32
  import { getSyncErrorType } from './errorMaps';
@@ -40,6 +41,8 @@ const logger = new Logger('DataStore');
40
41
  class SyncProcessor {
41
42
  private readonly typeQuery = new WeakMap<SchemaModel, [string, string]>();
42
43
 
44
+ private runningProcesses = new BackgroundProcessManager();
45
+
43
46
  constructor(
44
47
  private readonly schema: InternalSchema,
45
48
  private readonly syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>,
@@ -85,14 +88,13 @@ class SyncProcessor {
85
88
  return predicateToGraphQLFilter(predicatesGroup);
86
89
  }
87
90
 
88
- private async retrievePage<
89
- T extends ModelInstanceMetadata = ModelInstanceMetadata
90
- >(
91
+ private async retrievePage<T extends ModelInstanceMetadata>(
91
92
  modelDefinition: SchemaModel,
92
93
  lastSync: number,
93
94
  nextToken: string,
94
95
  limit: number = null,
95
- filter: GraphQLFilter
96
+ filter: GraphQLFilter,
97
+ onTerminate: Promise<void>
96
98
  ): Promise<{ nextToken: string; startedAt: number; items: T[] }> {
97
99
  const [opName, query] = this.typeQuery.get(modelDefinition);
98
100
 
@@ -115,6 +117,12 @@ class SyncProcessor {
115
117
 
116
118
  let authModeAttempts = 0;
117
119
  const authModeRetry = async () => {
120
+ if (!this.runningProcesses.isOpen) {
121
+ throw new Error(
122
+ 'sync.retreievePage termination was requested. Exiting.'
123
+ );
124
+ }
125
+
118
126
  try {
119
127
  logger.debug(
120
128
  `Attempting sync with authMode: ${readAuthModes[authModeAttempts]}`
@@ -125,6 +133,7 @@ class SyncProcessor {
125
133
  opName,
126
134
  modelDefinition,
127
135
  authMode: readAuthModes[authModeAttempts],
136
+ onTerminate,
128
137
  });
129
138
  logger.debug(
130
139
  `Sync successful with authMode: ${readAuthModes[authModeAttempts]}`
@@ -171,28 +180,20 @@ class SyncProcessor {
171
180
  };
172
181
  }
173
182
 
174
- // Partial data private feature flag. Not a public API. This will be removed in a future release.
175
- private partialDataFeatureFlagEnabled() {
176
- try {
177
- const flag = sessionStorage.getItem('datastorePartialData');
178
- return Boolean(flag);
179
- } catch (e) {
180
- return false;
181
- }
182
- }
183
-
184
183
  private async jitteredRetry<T>({
185
184
  query,
186
185
  variables,
187
186
  opName,
188
187
  modelDefinition,
189
188
  authMode,
189
+ onTerminate,
190
190
  }: {
191
191
  query: string;
192
192
  variables: { limit: number; lastSync: number; nextToken: string };
193
193
  opName: string;
194
194
  modelDefinition: SchemaModel;
195
195
  authMode: GRAPHQL_AUTH_MODE;
196
+ onTerminate: Promise<void>;
196
197
  }): Promise<
197
198
  GraphQLResult<{
198
199
  [opName: string]: {
@@ -217,6 +218,8 @@ class SyncProcessor {
217
218
  authToken,
218
219
  userAgentSuffix: USER_AGENT_SUFFIX_DATASTORE,
219
220
  });
221
+
222
+ // TODO: onTerminate.then(() => API.cancel(...))
220
223
  } catch (error) {
221
224
  // Catch client-side (GraphQLAuthError) & 401/403 errors here so that we don't continue to retry
222
225
  const clientOrForbiddenErrorMessage =
@@ -225,92 +228,90 @@ class SyncProcessor {
225
228
  throw new NonRetryableError(clientOrForbiddenErrorMessage);
226
229
  }
227
230
 
228
- const hasItems = Boolean(
229
- error &&
230
- error.data &&
231
- error.data[opName] &&
232
- error.data[opName].items
233
- );
234
- if (this.partialDataFeatureFlagEnabled()) {
235
- if (hasItems) {
236
- const result = error;
237
- result.data[opName].items = result.data[opName].items.filter(
238
- item => item !== null
239
- );
240
- if (error.errors) {
241
- await Promise.all(
242
- error.errors.map(async err => {
243
- try {
244
- await this.errorHandler({
245
- recoverySuggestion:
246
- 'Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues',
247
- localModel: null,
248
- message: err.message,
249
- model: modelDefinition.name,
250
- operation: opName,
251
- errorType: getSyncErrorType(err),
252
- process: ProcessName.sync,
253
- remoteModel: null,
254
- cause: err,
255
- });
256
- } catch (e) {
257
- logger.error('Sync error handler failed with:', e);
258
- }
259
- })
260
- );
261
- Hub.dispatch('datastore', {
262
- event: 'syncQueriesPartialSyncError',
263
- data: {
264
- errors: error.errors,
265
- modelName: modelDefinition.name,
266
- },
267
- });
268
- }
269
-
270
- return result;
271
- } else {
272
- throw error;
273
- }
274
- }
231
+ const hasItems = Boolean(error?.data?.[opName]?.items);
275
232
 
276
- // If the error is unauthorized, filter out unauthorized items and return accessible items
277
233
  const unauthorized =
278
- error &&
279
- error.errors &&
234
+ error?.errors &&
280
235
  (error.errors as [any]).some(
281
236
  err => err.errorType === 'Unauthorized'
282
237
  );
283
- if (unauthorized) {
284
- const result = error;
285
238
 
286
- if (hasItems) {
287
- result.data[opName].items = result.data[opName].items.filter(
288
- item => item !== null
289
- );
290
- } else {
291
- result.data[opName] = {
292
- ...opResultDefaults,
293
- ...result.data[opName],
294
- };
295
- }
239
+ const otherErrors =
240
+ error?.errors &&
241
+ (error.errors as [any]).filter(
242
+ err => err.errorType !== 'Unauthorized'
243
+ );
244
+
245
+ const result = error;
246
+
247
+ if (hasItems) {
248
+ result.data[opName].items = result.data[opName].items.filter(
249
+ item => item !== null
250
+ );
251
+ }
252
+
253
+ if (hasItems && otherErrors?.length) {
254
+ await Promise.all(
255
+ otherErrors.map(async err => {
256
+ try {
257
+ await this.errorHandler({
258
+ recoverySuggestion:
259
+ 'Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues',
260
+ localModel: null,
261
+ message: err.message,
262
+ model: modelDefinition.name,
263
+ operation: opName,
264
+ errorType: getSyncErrorType(err),
265
+ process: ProcessName.sync,
266
+ remoteModel: null,
267
+ cause: err,
268
+ });
269
+ } catch (e) {
270
+ logger.error('Sync error handler failed with:', e);
271
+ }
272
+ })
273
+ );
274
+ Hub.dispatch('datastore', {
275
+ event: 'nonApplicableDataReceived',
276
+ data: {
277
+ errors: otherErrors,
278
+ modelName: modelDefinition.name,
279
+ },
280
+ });
281
+ }
282
+
283
+ if (unauthorized) {
296
284
  logger.warn(
297
285
  'queryError',
298
286
  `User is unauthorized to query ${opName}, some items could not be returned.`
299
287
  );
288
+
289
+ result.data = result.data || {};
290
+
291
+ result.data[opName] = {
292
+ ...opResultDefaults,
293
+ ...result.data[opName],
294
+ };
295
+
296
+ return result;
297
+ }
298
+
299
+ if (result.data?.[opName].items?.length) {
300
300
  return result;
301
- } else {
302
- throw error;
303
301
  }
302
+
303
+ throw error;
304
304
  }
305
305
  },
306
- [query, variables]
306
+ [query, variables],
307
+ undefined,
308
+ onTerminate
307
309
  );
308
310
  }
309
311
 
310
312
  start(
311
313
  typesLastSync: Map<SchemaModel, [string, number]>
312
314
  ): Observable<SyncModelPage> {
313
- let processing = true;
314
315
  const { maxRecordsToSync, syncPageSize } = this.amplifyConfig;
315
316
  const parentPromises = new Map<string, Promise<void>>();
316
317
  const observable = new Observable<SyncModelPage>(observer => {
@@ -329,76 +330,88 @@ class SyncProcessor {
329
330
 
330
331
  const allModelsReady = Array.from(sortedTypesLastSyncs.entries())
331
332
  .filter(([{ syncable }]) => syncable)
332
- .map(async ([modelDefinition, [namespace, lastSync]]) => {
333
- let done = false;
334
- let nextToken: string = null;
335
- let startedAt: number = null;
336
- let items: ModelInstanceMetadata[] = null;
337
-
338
- let recordsReceived = 0;
339
- const filter = this.graphqlFilterFromPredicate(modelDefinition);
340
-
341
- const parents = this.schema.namespaces[
342
- namespace
343
- ].modelTopologicalOrdering.get(modelDefinition.name);
344
- const promises = parents.map(parent =>
345
- parentPromises.get(`${namespace}_${parent}`)
346
- );
347
-
348
- const promise = new Promise<void>(async res => {
349
- await Promise.all(promises);
350
-
351
- do {
352
- if (!processing) {
353
- return;
354
- }
355
-
356
- const limit = Math.min(
357
- maxRecordsToSync - recordsReceived,
358
- syncPageSize
333
+ .map(
334
+ ([modelDefinition, [namespace, lastSync]]) =>
335
+ this.runningProcesses.isOpen &&
336
+ this.runningProcesses.add(async onTerminate => {
337
+ let done = false;
338
+ let nextToken: string = null;
339
+ let startedAt: number = null;
340
+ let items: ModelInstanceMetadata[] = null;
341
+
342
+ let recordsReceived = 0;
343
+ const filter = this.graphqlFilterFromPredicate(modelDefinition);
344
+
345
+ const parents = this.schema.namespaces[
346
+ namespace
347
+ ].modelTopologicalOrdering.get(modelDefinition.name);
348
+ const promises = parents.map(parent =>
349
+ parentPromises.get(`${namespace}_${parent}`)
359
350
  );
360
351
 
361
- ({ items, nextToken, startedAt } = await this.retrievePage(
362
- modelDefinition,
363
- lastSync,
364
- nextToken,
365
- limit,
366
- filter
367
- ));
368
-
369
- recordsReceived += items.length;
370
-
371
- done = nextToken === null || recordsReceived >= maxRecordsToSync;
372
-
373
- observer.next({
374
- namespace,
375
- modelDefinition,
376
- items,
377
- done,
378
- startedAt,
379
- isFullSync: !lastSync,
352
+ const promise = new Promise<void>(async res => {
353
+ await Promise.all(promises);
354
+
355
+ do {
356
+ if (!this.runningProcesses.isOpen) {
357
+ return;
358
+ }
359
+
360
+ const limit = Math.min(
361
+ maxRecordsToSync - recordsReceived,
362
+ syncPageSize
363
+ );
364
+
365
+ ({ items, nextToken, startedAt } = await this.retrievePage(
366
+ modelDefinition,
367
+ lastSync,
368
+ nextToken,
369
+ limit,
370
+ filter,
371
+ onTerminate
372
+ ));
373
+
374
+ recordsReceived += items.length;
375
+
376
+ done =
377
+ nextToken === null || recordsReceived >= maxRecordsToSync;
378
+
379
+ observer.next({
380
+ namespace,
381
+ modelDefinition,
382
+ items,
383
+ done,
384
+ startedAt,
385
+ isFullSync: !lastSync,
386
+ });
387
+ } while (!done);
388
+
389
+ res();
380
390
  });
381
- } while (!done);
382
-
383
- res();
384
- });
385
391
 
386
- parentPromises.set(`${namespace}_${modelDefinition.name}`, promise);
392
+ parentPromises.set(
393
+ `${namespace}_${modelDefinition.name}`,
394
+ promise
395
+ );
387
396
 
388
- await promise;
389
- });
397
+ await promise;
398
+ }, `adding model ${modelDefinition.name}`)
399
+ );
390
400
 
391
401
  Promise.all(allModelsReady).then(() => {
392
402
  observer.complete();
393
403
  });
394
-
395
- return () => {
396
- processing = false;
397
- };
398
404
  });
399
405
 
400
406
  return observable;
401
407
  }
408
+
409
+ async stop() {
410
+ logger.debug('stopping sync processor');
411
+ await this.runningProcesses.close();
412
+ await this.runningProcesses.open();
413
+ logger.debug('sync processor stopped');
414
+ }
402
415
  }
403
416
 
404
417
  export type SyncModelPage = {
package/src/sync/utils.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  isGraphQLScalarType,
12
12
  isPredicateObj,
13
13
  isSchemaModel,
14
+ isSchemaModelWithAttributes,
14
15
  isTargetNameAssociation,
15
16
  isNonModelFieldType,
16
17
  ModelFields,
@@ -27,7 +28,12 @@ import {
27
28
  InternalSchema,
28
29
  AuthModeStrategy,
29
30
  } from '../types';
30
- import { exhaustiveCheck } from '../util';
31
+ import {
32
+ exhaustiveCheck,
33
+ extractPrimaryKeyFieldNames,
34
+ establishRelationAndKeys,
35
+ IDENTIFIER_KEY_SEPARATOR,
36
+ } from '../util';
31
37
  import { MutationEvent } from './';
32
38
 
33
39
  const logger = new Logger('DataStore');
@@ -47,7 +53,7 @@ export enum TransformerMutationType {
47
53
  GET = 'Get',
48
54
  }
49
55
 
50
- const dummyMetadata: Omit<ModelInstanceMetadata, 'id'> = {
56
+ const dummyMetadata: ModelInstanceMetadata = {
51
57
  _version: undefined,
52
58
  _lastChangedAt: undefined,
53
59
  _deleted: undefined,
@@ -79,7 +85,7 @@ export function generateSelectionSet(
79
85
  if (isSchemaModel(modelDefinition)) {
80
86
  scalarAndMetadataFields = scalarAndMetadataFields
81
87
  .concat(getMetadataFields())
82
- .concat(getConnectionFields(modelDefinition));
88
+ .concat(getConnectionFields(modelDefinition, namespace));
83
89
  }
84
90
 
85
91
  const result = scalarAndMetadataFields.join('\n');
@@ -103,7 +109,7 @@ function getOwnerFields(
103
109
  modelDefinition: SchemaModel | SchemaNonModel
104
110
  ): string[] {
105
111
  const ownerFields: string[] = [];
106
- if (isSchemaModel(modelDefinition) && modelDefinition.attributes) {
112
+ if (isSchemaModelWithAttributes(modelDefinition)) {
107
113
  modelDefinition.attributes.forEach(attr => {
108
114
  if (attr.properties && attr.properties.rules) {
109
115
  const rule = attr.properties.rules.find(rule => rule.allow === 'owner');
@@ -138,8 +144,12 @@ function getScalarFields(
138
144
  return result;
139
145
  }
140
146
 
141
- function getConnectionFields(modelDefinition: SchemaModel): string[] {
142
- const result = [];
147
+ // Used for generating the selection set for queries and mutations
148
+ function getConnectionFields(
149
+ modelDefinition: SchemaModel,
150
+ namespace: SchemaNamespace
151
+ ): string[] {
152
+ const result: string[] = [];
143
153
 
144
154
  Object.values(modelDefinition.fields)
145
155
  .filter(({ association }) => association && Object.keys(association).length)
@@ -153,7 +163,26 @@ function getConnectionFields(modelDefinition: SchemaModel): string[] {
153
163
  break;
154
164
  case 'BELONGS_TO':
155
165
  if (isTargetNameAssociation(association)) {
156
- result.push(`${name} { id _deleted }`);
166
+ // New codegen (CPK)
167
+ if (association.targetNames && association.targetNames.length > 0) {
168
+ // Need to retrieve relations in order to get connected model keys
169
+ const [relations] = establishRelationAndKeys(namespace);
170
+
171
+ const connectedModelName =
172
+ modelDefinition.fields[name].type['model'];
173
+
174
+ const byPkIndex = relations[connectedModelName].indexes.find(
175
+ ([name]) => name === 'byPk'
176
+ );
177
+ const keyFields = byPkIndex && byPkIndex[1];
178
+ const keyFieldSelectionSet = keyFields?.join(' ');
179
+
180
+ // We rely on `_deleted` when we process the sync query (e.g. in batchSave in the adapters)
181
+ result.push(`${name} { ${keyFieldSelectionSet} _deleted }`);
182
+ } else {
183
+ // backwards-compatability for schema generated prior to custom primary key support
184
+ result.push(`${name} { id _deleted }`);
185
+ }
157
186
  }
158
187
  break;
159
188
  default:
@@ -412,10 +441,13 @@ export function createMutationInstanceFromModelOperation<
412
441
  return v;
413
442
  };
414
443
 
444
+ const modelId = getIdentifierValue(modelDefinition, element);
445
+ const optionalId = OpType.INSERT && id ? { id } : {};
446
+
415
447
  const mutationEvent = modelInstanceCreator(MutationEventConstructor, {
416
- ...(id ? { id } : {}),
448
+ ...optionalId,
417
449
  data: JSON.stringify(element, replacer),
418
- modelId: element.id,
450
+ modelId,
419
451
  model: model.name,
420
452
  operation,
421
453
  condition: JSON.stringify(condition),
@@ -425,7 +457,8 @@ export function createMutationInstanceFromModelOperation<
425
457
  }
426
458
 
427
459
  export function predicateToGraphQLCondition(
428
- predicate: PredicatesGroup<any>
460
+ predicate: PredicatesGroup<any>,
461
+ modelDefinition: SchemaModel
429
462
  ): GraphQLCondition {
430
463
  const result = {};
431
464
 
@@ -433,17 +466,27 @@ export function predicateToGraphQLCondition(
433
466
  return result;
434
467
  }
435
468
 
469
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
470
+
436
471
  predicate.predicates.forEach(p => {
437
472
  if (isPredicateObj(p)) {
438
473
  const { field, operator, operand } = p;
439
474
 
440
- if (field === 'id') {
475
+ // This is compatible with how the GQL Transform currently generates the Condition Input,
476
+ // i.e. any PK and SK fields are omitted and can't be used as conditions.
477
+ // However, I think this limits usability.
478
+ // What if we want to delete all records where SK > some value
479
+ // Or all records where PK = some value but SKs are different values
480
+
481
+ // TODO: if the Transform gets updated ^ we'll need to modify this logic to only omit
482
+ // key fields from the predicate/condition when ALL of the keyFields are present and using `eq` operators
483
+ if (keyFields.includes(field as string)) {
441
484
  return;
442
485
  }
443
486
 
444
487
  result[field] = { [operator]: operand };
445
488
  } else {
446
- result[p.type] = predicateToGraphQLCondition(p);
489
+ result[p.type] = predicateToGraphQLCondition(p, modelDefinition);
447
490
  }
448
491
  });
449
492
 
@@ -610,3 +653,15 @@ export async function getTokenForCustomAuth(
610
653
  }
611
654
  }
612
655
  }
656
+
657
+ // Util that takes a modelDefinition and model and returns either the id value(s) or the custom primary key value(s)
658
+ export function getIdentifierValue(
659
+ modelDefinition: SchemaModel,
660
+ model: ModelInstanceMetadata | PersistentModel
661
+ ): string {
662
+ const pkFieldNames = extractPrimaryKeyFieldNames(modelDefinition);
663
+
664
+ const idOrPk = pkFieldNames.map(f => model[f]).join(IDENTIFIER_KEY_SEPARATOR);
665
+
666
+ return idOrPk;
667
+ }