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

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 (151) hide show
  1. package/lib/authModeStrategies/defaultAuthStrategy.d.ts +2 -0
  2. package/lib/authModeStrategies/index.d.ts +2 -0
  3. package/lib/authModeStrategies/multiAuthStrategy.d.ts +13 -0
  4. package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
  5. package/lib/datastore/datastore.d.ts +207 -0
  6. package/lib/datastore/datastore.js +641 -144
  7. package/lib/datastore/datastore.js.map +1 -1
  8. package/lib/index.d.ts +16 -0
  9. package/lib/index.js +4 -0
  10. package/lib/index.js.map +1 -1
  11. package/lib/predicates/index.d.ts +30 -0
  12. package/lib/predicates/index.js +127 -6
  13. package/lib/predicates/index.js.map +1 -1
  14. package/lib/predicates/next.d.ts +342 -0
  15. package/lib/predicates/next.js +801 -0
  16. package/lib/predicates/next.js.map +1 -0
  17. package/lib/predicates/sort.d.ts +8 -0
  18. package/lib/predicates/sort.js +10 -4
  19. package/lib/predicates/sort.js.map +1 -1
  20. package/lib/ssr/index.d.ts +3 -0
  21. package/lib/storage/adapter/AsyncStorageAdapter.d.ts +42 -0
  22. package/lib/storage/adapter/AsyncStorageAdapter.js +106 -293
  23. package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  24. package/lib/storage/adapter/AsyncStorageDatabase.d.ts +39 -0
  25. package/lib/storage/adapter/AsyncStorageDatabase.js +6 -5
  26. package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  27. package/lib/storage/adapter/InMemoryStore.d.ts +11 -0
  28. package/lib/storage/adapter/InMemoryStore.js.map +1 -1
  29. package/lib/storage/adapter/InMemoryStore.native.d.ts +1 -0
  30. package/lib/storage/adapter/IndexedDBAdapter.d.ts +61 -0
  31. package/lib/storage/adapter/IndexedDBAdapter.js +223 -289
  32. package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
  33. package/lib/storage/adapter/getDefaultAdapter/index.d.ts +3 -0
  34. package/lib/storage/adapter/getDefaultAdapter/index.js.map +1 -1
  35. package/lib/storage/adapter/getDefaultAdapter/index.native.d.ts +3 -0
  36. package/lib/storage/adapter/index.d.ts +9 -0
  37. package/lib/storage/relationship.d.ts +140 -0
  38. package/lib/storage/relationship.js +335 -0
  39. package/lib/storage/relationship.js.map +1 -0
  40. package/lib/storage/storage.d.ts +50 -0
  41. package/lib/storage/storage.js +32 -16
  42. package/lib/storage/storage.js.map +1 -1
  43. package/lib/sync/datastoreConnectivity.d.ts +16 -0
  44. package/lib/sync/datastoreConnectivity.js.map +1 -1
  45. package/lib/sync/datastoreReachability/index.d.ts +3 -0
  46. package/lib/sync/datastoreReachability/index.native.d.ts +3 -0
  47. package/lib/sync/index.d.ts +89 -0
  48. package/lib/sync/index.js +2 -8
  49. package/lib/sync/index.js.map +1 -1
  50. package/lib/sync/merger.d.ts +17 -0
  51. package/lib/sync/merger.js.map +1 -1
  52. package/lib/sync/outbox.d.ts +27 -0
  53. package/lib/sync/outbox.js.map +1 -1
  54. package/lib/sync/processors/errorMaps.d.ts +17 -0
  55. package/lib/sync/processors/errorMaps.js +1 -1
  56. package/lib/sync/processors/errorMaps.js.map +1 -1
  57. package/lib/sync/processors/mutation.d.ts +58 -0
  58. package/lib/sync/processors/mutation.js +9 -6
  59. package/lib/sync/processors/mutation.js.map +1 -1
  60. package/lib/sync/processors/subscription.d.ts +33 -0
  61. package/lib/sync/processors/subscription.js +4 -1
  62. package/lib/sync/processors/subscription.js.map +1 -1
  63. package/lib/sync/processors/sync.d.ts +28 -0
  64. package/lib/sync/processors/sync.js.map +1 -1
  65. package/lib/sync/utils.d.ts +42 -0
  66. package/lib/sync/utils.js +30 -31
  67. package/lib/sync/utils.js.map +1 -1
  68. package/lib/types.d.ts +553 -0
  69. package/lib/types.js +6 -1
  70. package/lib/types.js.map +1 -1
  71. package/lib/util.d.ts +189 -0
  72. package/lib/util.js +174 -104
  73. package/lib/util.js.map +1 -1
  74. package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
  75. package/lib-esm/datastore/datastore.d.ts +59 -8
  76. package/lib-esm/datastore/datastore.js +643 -147
  77. package/lib-esm/datastore/datastore.js.map +1 -1
  78. package/lib-esm/index.d.ts +3 -2
  79. package/lib-esm/index.js +2 -1
  80. package/lib-esm/index.js.map +1 -1
  81. package/lib-esm/predicates/index.d.ts +16 -2
  82. package/lib-esm/predicates/index.js +128 -7
  83. package/lib-esm/predicates/index.js.map +1 -1
  84. package/lib-esm/predicates/next.d.ts +342 -0
  85. package/lib-esm/predicates/next.js +797 -0
  86. package/lib-esm/predicates/next.js.map +1 -0
  87. package/lib-esm/predicates/sort.js +10 -4
  88. package/lib-esm/predicates/sort.js.map +1 -1
  89. package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +2 -1
  90. package/lib-esm/storage/adapter/AsyncStorageAdapter.js +108 -295
  91. package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
  92. package/lib-esm/storage/adapter/AsyncStorageDatabase.js +6 -5
  93. package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
  94. package/lib-esm/storage/adapter/InMemoryStore.d.ts +1 -1
  95. package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
  96. package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +4 -2
  97. package/lib-esm/storage/adapter/IndexedDBAdapter.js +226 -292
  98. package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
  99. package/lib-esm/storage/adapter/getDefaultAdapter/index.js.map +1 -1
  100. package/lib-esm/storage/relationship.d.ts +140 -0
  101. package/lib-esm/storage/relationship.js +333 -0
  102. package/lib-esm/storage/relationship.js.map +1 -0
  103. package/lib-esm/storage/storage.d.ts +7 -6
  104. package/lib-esm/storage/storage.js +32 -16
  105. package/lib-esm/storage/storage.js.map +1 -1
  106. package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
  107. package/lib-esm/sync/index.js +3 -9
  108. package/lib-esm/sync/index.js.map +1 -1
  109. package/lib-esm/sync/merger.js.map +1 -1
  110. package/lib-esm/sync/outbox.js.map +1 -1
  111. package/lib-esm/sync/processors/errorMaps.js +1 -1
  112. package/lib-esm/sync/processors/errorMaps.js.map +1 -1
  113. package/lib-esm/sync/processors/mutation.js +10 -7
  114. package/lib-esm/sync/processors/mutation.js.map +1 -1
  115. package/lib-esm/sync/processors/subscription.js +4 -1
  116. package/lib-esm/sync/processors/subscription.js.map +1 -1
  117. package/lib-esm/sync/processors/sync.js.map +1 -1
  118. package/lib-esm/sync/utils.d.ts +1 -1
  119. package/lib-esm/sync/utils.js +31 -32
  120. package/lib-esm/sync/utils.js.map +1 -1
  121. package/lib-esm/types.d.ts +59 -7
  122. package/lib-esm/types.js +6 -2
  123. package/lib-esm/types.js.map +1 -1
  124. package/lib-esm/util.d.ts +39 -6
  125. package/lib-esm/util.js +170 -104
  126. package/lib-esm/util.js.map +1 -1
  127. package/package.json +14 -11
  128. package/src/authModeStrategies/multiAuthStrategy.ts +1 -1
  129. package/src/datastore/datastore.ts +680 -204
  130. package/src/index.ts +4 -0
  131. package/src/predicates/index.ts +143 -17
  132. package/src/predicates/next.ts +1016 -0
  133. package/src/predicates/sort.ts +8 -2
  134. package/src/storage/adapter/AsyncStorageAdapter.ts +56 -178
  135. package/src/storage/adapter/AsyncStorageDatabase.ts +16 -15
  136. package/src/storage/adapter/InMemoryStore.ts +5 -2
  137. package/src/storage/adapter/IndexedDBAdapter.ts +166 -191
  138. package/src/storage/adapter/getDefaultAdapter/index.ts +2 -2
  139. package/src/storage/relationship.ts +272 -0
  140. package/src/storage/storage.ts +56 -37
  141. package/src/sync/datastoreConnectivity.ts +4 -4
  142. package/src/sync/index.ts +22 -28
  143. package/src/sync/merger.ts +1 -1
  144. package/src/sync/outbox.ts +6 -6
  145. package/src/sync/processors/errorMaps.ts +1 -1
  146. package/src/sync/processors/mutation.ts +22 -17
  147. package/src/sync/processors/subscription.ts +19 -15
  148. package/src/sync/processors/sync.ts +16 -16
  149. package/src/sync/utils.ts +42 -48
  150. package/src/types.ts +116 -11
  151. package/src/util.ts +108 -150
@@ -1,6 +1,6 @@
1
1
  import { API } from '@aws-amplify/api';
2
2
  import { Auth } from '@aws-amplify/auth';
3
- import { BrowserStorageCache as Cache } from '@aws-amplify/cache';
3
+ import { Cache } from '@aws-amplify/cache';
4
4
  import {
5
5
  Amplify,
6
6
  ConsoleLogger as Logger,
@@ -27,6 +27,7 @@ import {
27
27
  } from '../predicates';
28
28
  import { Adapter } from '../storage/adapter';
29
29
  import { ExclusiveStorage as Storage } from '../storage/storage';
30
+ import { ModelRelationship } from '../storage/relationship';
30
31
  import { ControlMessage, SyncEngine } from '../sync';
31
32
  import {
32
33
  AuthModeStrategy,
@@ -40,6 +41,7 @@ import {
40
41
  ModelInit,
41
42
  ModelInstanceMetadata,
42
43
  ModelPredicate,
44
+ ModelField,
43
45
  SortPredicate,
44
46
  MutableModel,
45
47
  NamespaceResolver,
@@ -69,6 +71,7 @@ import {
69
71
  IdentifierFieldOrIdentifierObject,
70
72
  isIdentifierObject,
71
73
  AmplifyContext,
74
+ isFieldAssociation,
72
75
  } from '../types';
73
76
  // tslint:disable:no-duplicate-imports
74
77
  import type { __modelMeta__ } from '../types';
@@ -77,7 +80,6 @@ import {
77
80
  DATASTORE,
78
81
  errorMessages,
79
82
  establishRelationAndKeys,
80
- exhaustiveCheck,
81
83
  isModelConstructor,
82
84
  monotonicUlidFactory,
83
85
  NAMESPACES,
@@ -88,13 +90,20 @@ import {
88
90
  registerNonModelClass,
89
91
  sortCompareFunction,
90
92
  DeferredCallbackResolver,
93
+ inMemoryPagination,
91
94
  extractPrimaryKeyFieldNames,
92
95
  extractPrimaryKeysAndValues,
93
96
  isIdManaged,
94
97
  isIdOptionallyManaged,
95
- validatePredicate,
96
98
  mergePatches,
97
99
  } from '../util';
100
+ import {
101
+ RecursiveModelPredicateExtender,
102
+ ModelPredicateExtender,
103
+ recursivePredicateFor,
104
+ predicateFor,
105
+ GroupCondition,
106
+ } from '../predicates/next';
98
107
  import { getIdentifierValue } from '../sync/utils';
99
108
  import DataStoreConnectivity from '../sync/datastoreConnectivity';
100
109
 
@@ -140,19 +149,98 @@ const modelPatchesMap = new WeakMap<
140
149
  const getModelDefinition = (
141
150
  modelConstructor: PersistentModelConstructor<any>
142
151
  ) => {
143
- const namespace = modelNamespaceMap.get(modelConstructor);
152
+ const namespace = modelNamespaceMap.get(modelConstructor)!;
153
+ const definition = namespace
154
+ ? schema.namespaces[namespace].models[modelConstructor.name]
155
+ : undefined;
156
+
157
+ // compatibility with legacy/pre-PK codegen for lazy loading to inject
158
+ // index fields into the model definition.
159
+ if (definition) {
160
+ const indexes =
161
+ schema.namespaces[namespace].relationships![modelConstructor.name]
162
+ .indexes;
163
+
164
+ const indexFields = new Set<string>();
165
+ for (const index of indexes) {
166
+ for (const indexField of index[1]) {
167
+ indexFields.add(indexField);
168
+ }
169
+ }
144
170
 
145
- return schema.namespaces[namespace].models[modelConstructor.name];
171
+ definition.fields = {
172
+ ...Object.fromEntries(
173
+ [...indexFields.values()].map(
174
+ name => [
175
+ name,
176
+ {
177
+ name,
178
+ type: 'ID',
179
+ isArray: false,
180
+ },
181
+ ],
182
+ []
183
+ )
184
+ ),
185
+ ...definition.fields,
186
+ };
187
+ }
188
+
189
+ return definition;
190
+ };
191
+
192
+ const getModelPKFieldName = (
193
+ modelConstructor: PersistentModelConstructor<any>
194
+ ) => {
195
+ const namespace = modelNamespaceMap.get(modelConstructor);
196
+ return (
197
+ (namespace &&
198
+ schema.namespaces?.[namespace]?.keys?.[modelConstructor.name]
199
+ .primaryKey) || ['id']
200
+ );
146
201
  };
147
202
 
148
203
  const isValidModelConstructor = <T extends PersistentModel>(
149
204
  obj: any
150
205
  ): obj is PersistentModelConstructor<T> => {
151
- return isModelConstructor(obj) && modelNamespaceMap.has(obj);
206
+ if (isModelConstructor(obj) && modelNamespaceMap.has(obj)) {
207
+ return true;
208
+ } else {
209
+ return false;
210
+ }
211
+ };
212
+
213
+ const namespaceResolver: NamespaceResolver = modelConstructor => {
214
+ const resolver = modelNamespaceMap.get(modelConstructor);
215
+ if (!resolver) {
216
+ throw new Error(
217
+ `Namespace Resolver for '${modelConstructor.name}' not found! This is probably a bug in '@amplify-js/datastore'.`
218
+ );
219
+ }
220
+ return resolver;
152
221
  };
153
222
 
154
- const namespaceResolver: NamespaceResolver = modelConstructor =>
155
- modelNamespaceMap.get(modelConstructor);
223
+ const buildSeedPredicate = <T extends PersistentModel>(
224
+ modelConstructor: PersistentModelConstructor<T>
225
+ ) => {
226
+ if (!modelConstructor) throw new Error('Missing modelConstructor');
227
+
228
+ const modelSchema = getModelDefinition(
229
+ modelConstructor as PersistentModelConstructor<T>
230
+ );
231
+ if (!modelSchema) throw new Error('Missing modelSchema');
232
+
233
+ const pks = getModelPKFieldName(
234
+ modelConstructor as PersistentModelConstructor<T>
235
+ );
236
+ if (!pks) throw new Error('Could not determine PK');
237
+
238
+ return recursivePredicateFor<T>({
239
+ builder: modelConstructor as PersistentModelConstructor<T>,
240
+ schema: modelSchema,
241
+ pkField: pks,
242
+ });
243
+ };
156
244
 
157
245
  // exporting syncClasses for testing outbox.test.ts
158
246
  export let syncClasses: TypeConstructorMap;
@@ -160,6 +248,78 @@ let userClasses: TypeConstructorMap;
160
248
  let dataStoreClasses: TypeConstructorMap;
161
249
  let storageClasses: TypeConstructorMap;
162
250
 
251
+ /**
252
+ * Maps a model to its related models for memoization/immutability.
253
+ */
254
+ const modelInstanceAssociationsMap = new WeakMap<PersistentModel, object>();
255
+
256
+ /**
257
+ * Describes whether and to what a model is attached for lazy loading purposes.
258
+ */
259
+ enum ModelAttachment {
260
+ /**
261
+ * Model doesn't lazy load from any data source.
262
+ *
263
+ * Related entity properties provided at instantiation are returned
264
+ * via the respective lazy interfaces when their properties are invoked.
265
+ */
266
+ Detached = 'Detached',
267
+
268
+ /**
269
+ * Model lazy loads from the global DataStore.
270
+ */
271
+ DataStore = 'DataStore',
272
+
273
+ /**
274
+ * Demonstrative. Not yet implemented.
275
+ */
276
+ API = 'API',
277
+ }
278
+
279
+ /**
280
+ * Tells us which data source a model is attached to (lazy loads from).
281
+ *
282
+ * If `Deatched`, the model's lazy properties will only ever return properties
283
+ * from memory provided at construction time.
284
+ */
285
+ const attachedModelInstances = new WeakMap<PersistentModel, ModelAttachment>();
286
+
287
+ /**
288
+ * Registers a model instance against a data source (DataStore, API, or
289
+ * Detached/None).
290
+ *
291
+ * The API option is demonstrative. Lazy loading against API is not yet
292
+ * implemented.
293
+ *
294
+ * @param result A model instance or array of instances
295
+ * @param attachment A ModelAttachment data source
296
+ * @returns passes the `result` back through after attachment
297
+ */
298
+ export function attached<T extends PersistentModel | PersistentModel[]>(
299
+ result: T,
300
+ attachment: ModelAttachment
301
+ ): T {
302
+ if (Array.isArray(result)) {
303
+ result.map(record => attached(record, attachment)) as T;
304
+ } else {
305
+ result && attachedModelInstances.set(result, attachment);
306
+ }
307
+ return result;
308
+ }
309
+
310
+ /**
311
+ * Determines what source a model instance should lazy load from.
312
+ *
313
+ * If the instace was never explicitly registered, it is detached by default.
314
+ *
315
+ * @param instance A model instance
316
+ */
317
+ export const getAttachment = (instance: PersistentModel) => {
318
+ return attachedModelInstances.has(instance)
319
+ ? attachedModelInstances.get(instance)
320
+ : ModelAttachment.Detached;
321
+ };
322
+
163
323
  const initSchema = (userSchema: Schema) => {
164
324
  if (schema !== undefined) {
165
325
  console.warn('The schema has already been initialized');
@@ -169,6 +329,8 @@ const initSchema = (userSchema: Schema) => {
169
329
 
170
330
  logger.log('validating schema', { schema: userSchema });
171
331
 
332
+ checkSchemaCodegenVersion(userSchema.codegenVersion);
333
+
172
334
  const internalUserNamespace: SchemaNamespace = {
173
335
  name: USER,
174
336
  ...userSchema,
@@ -194,6 +356,7 @@ const initSchema = (userSchema: Schema) => {
194
356
  [syncNamespace.name]: syncNamespace,
195
357
  },
196
358
  version: userSchema.version,
359
+ codegenVersion: userSchema.codegenVersion,
197
360
  };
198
361
 
199
362
  Object.keys(schema.namespaces).forEach(namespace => {
@@ -221,6 +384,33 @@ const initSchema = (userSchema: Schema) => {
221
384
  );
222
385
 
223
386
  modelAssociations.set(model.name, connectedModels);
387
+
388
+ Object.values(model.fields).forEach(field => {
389
+ if (
390
+ typeof field.type === 'object' &&
391
+ !Object.getOwnPropertyDescriptor(
392
+ <ModelFieldType>field.type,
393
+ 'modelConstructor'
394
+ )
395
+ ) {
396
+ Object.defineProperty(field.type, 'modelConstructor', {
397
+ get: () => {
398
+ return {
399
+ builder: userClasses[(<ModelFieldType>field.type).model],
400
+ schema:
401
+ schema.namespaces[namespace].models[
402
+ (<ModelFieldType>field.type).model
403
+ ],
404
+ pkField: getModelPKFieldName(
405
+ userClasses[
406
+ (<ModelFieldType>field.type).model
407
+ ] as PersistentModelConstructor<any>
408
+ ),
409
+ };
410
+ },
411
+ });
412
+ }
413
+ });
224
414
  });
225
415
 
226
416
  const result = new Map<string, string[]>();
@@ -240,7 +430,7 @@ const initSchema = (userSchema: Schema) => {
240
430
  for (const modelName of Array.from(modelAssociations.keys())) {
241
431
  const parents = modelAssociations.get(modelName);
242
432
 
243
- if (parents.every(x => result.has(x))) {
433
+ if (parents?.every(x => result.has(x))) {
244
434
  result.set(modelName, parents);
245
435
  }
246
436
  }
@@ -272,6 +462,47 @@ const checkSchemaInitialized = () => {
272
462
  }
273
463
  };
274
464
 
465
+ /**
466
+ * Throws an exception if the schema is using a codegen version that is not supported.
467
+ *
468
+ * Set the supported version by setting majorVersion and minorVersion
469
+ * This functions similar to ^ version range.
470
+ * The tested codegenVersion major version must exactly match the set majorVersion
471
+ * The tested codegenVersion minor version must be gt or equal to the set minorVersion
472
+ * Example: For a min supported version of 5.4.0 set majorVersion = 5 and minorVersion = 4
473
+ *
474
+ * This regex will not work when setting a supported range with minor version
475
+ * of 2 or more digits.
476
+ * i.e. minorVersion = 10 will not work
477
+ * The regex will work for testing a codegenVersion with multi digit minor
478
+ * versions as long as the minimum minorVersion is single digit.
479
+ * i.e. codegenVersion = 5.30.1, majorVersion = 5, minorVersion = 4 PASSES
480
+ *
481
+ * @param codegenVersion schema codegenVersion
482
+ */
483
+ const checkSchemaCodegenVersion = (codegenVersion: string) => {
484
+ // TODO: set to correct version when released in codegen
485
+ const majorVersion = 3;
486
+ const minorVersion = 2;
487
+ let isValid = false;
488
+
489
+ try {
490
+ const versionParts = codegenVersion.split('.');
491
+ const [major, minor, patch, patchrevision] = versionParts;
492
+ isValid = Number(major) === majorVersion && Number(minor) >= minorVersion;
493
+ } catch (err) {
494
+ console.log(`Error parsing codegen version: ${codegenVersion}\n${err}`);
495
+ }
496
+
497
+ if (!isValid) {
498
+ const message = `Models were generated with an unsupported version of codegen. Codegen artifacts are from ${
499
+ codegenVersion || 'an unknown version'
500
+ }, whereas ^${majorVersion}.${minorVersion}.0 is required. Update to the latest CLI and rerun codegen.`;
501
+ logger.error(message);
502
+ throw new Error(message);
503
+ }
504
+ };
505
+
275
506
  const createTypeClasses: (
276
507
  namespace: SchemaNamespace
277
508
  ) => TypeConstructorMap = namespace => {
@@ -286,7 +517,7 @@ const createTypeClasses: (
286
517
 
287
518
  Object.entries(namespace.nonModels || {}).forEach(
288
519
  ([typeName, typeDefinition]) => {
289
- const clazz = createNonModelClass(typeDefinition);
520
+ const clazz = createNonModelClass(typeDefinition) as any;
290
521
  classes[typeName] = clazz;
291
522
  }
292
523
  );
@@ -310,7 +541,7 @@ export declare type ModelInstanceCreator = typeof modelInstanceCreator;
310
541
  * the model visible to the consuming app -- in case the app doesn't have
311
542
  * metadata fields (_version, _deleted, etc.) exposed on the model itself.
312
543
  */
313
- const instancesMetadata = new WeakSet<ModelInit<unknown, unknown>>();
544
+ const instancesMetadata = new WeakSet<ModelInit<any, any>>();
314
545
 
315
546
  function modelInstanceCreator<T extends PersistentModel>(
316
547
  modelConstructor: PersistentModelConstructor<T>,
@@ -418,7 +649,7 @@ const validateModelFields =
418
649
  } else if (
419
650
  !isNullOrUndefined(v) &&
420
651
  validateScalar &&
421
- !validateScalar(v)
652
+ !validateScalar(v as never) // TODO: why never, TS ... why ...
422
653
  ) {
423
654
  throw new Error(
424
655
  `Field ${name} should be of type ${type}, validation failed. ${v}`
@@ -428,7 +659,7 @@ const validateModelFields =
428
659
  // do not check non model fields if undefined or null
429
660
  if (!isNullOrUndefined(v)) {
430
661
  const subNonModelDefinition =
431
- schema.namespaces.user.nonModels[type.nonModel];
662
+ schema.namespaces.user.nonModels![type.nonModel];
432
663
  const modelValidator = validateModelFields(subNonModelDefinition);
433
664
 
434
665
  if (isArray) {
@@ -608,7 +839,8 @@ const createModelClass = <T extends PersistentModel>(
608
839
 
609
840
  if (patches.length || hasExistingPatches) {
610
841
  if (hasExistingPatches) {
611
- const [existingPatches, existingSource] = modelPatchesMap.get(source);
842
+ const [existingPatches, existingSource] =
843
+ modelPatchesMap.get(source)!;
612
844
  const mergedPatches = mergePatches(
613
845
  existingSource,
614
846
  existingPatches,
@@ -622,7 +854,7 @@ const createModelClass = <T extends PersistentModel>(
622
854
  }
623
855
  }
624
856
 
625
- return model;
857
+ return attached(model, ModelAttachment.DataStore);
626
858
  }
627
859
 
628
860
  // "private" method (that's hidden via `Setting`) for `withSSRContext` to use
@@ -640,7 +872,7 @@ const createModelClass = <T extends PersistentModel>(
640
872
  modelValidator(k, v);
641
873
  });
642
874
 
643
- return instance;
875
+ return attached(instance, ModelAttachment.DataStore);
644
876
  }
645
877
  });
646
878
 
@@ -648,9 +880,169 @@ const createModelClass = <T extends PersistentModel>(
648
880
 
649
881
  Object.defineProperty(clazz, 'name', { value: modelDefinition.name });
650
882
 
883
+ for (const field in modelDefinition.fields) {
884
+ if (!isFieldAssociation(modelDefinition, field)) {
885
+ continue;
886
+ }
887
+
888
+ const {
889
+ type,
890
+ association: localAssociation,
891
+ association: { targetName, targetNames },
892
+ } = modelDefinition.fields[field] as Required<ModelField>;
893
+
894
+ const relationship = new ModelRelationship(
895
+ {
896
+ builder: clazz,
897
+ schema: modelDefinition,
898
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
899
+ },
900
+ field
901
+ );
902
+
903
+ Object.defineProperty(clazz.prototype, modelDefinition.fields[field].name, {
904
+ set(model: PersistentModel) {
905
+ if (!model || !(typeof model === 'object')) return;
906
+
907
+ // Avoid validation error when processing AppSync response with nested
908
+ // selection set. Nested entitites lack version field and can not be validated
909
+ // TODO: explore a more reliable method to solve this
910
+ if (model.hasOwnProperty('_version')) {
911
+ const modelConstructor = Object.getPrototypeOf(model || {})
912
+ .constructor as PersistentModelConstructor<T>;
913
+
914
+ if (!isValidModelConstructor(modelConstructor)) {
915
+ const msg = `Value passed to ${modelDefinition.name}.${field} is not a valid instance of a model`;
916
+ logger.error(msg, { model });
917
+
918
+ throw new Error(msg);
919
+ }
920
+
921
+ if (
922
+ modelConstructor.name.toLowerCase() !==
923
+ relationship.remoteModelConstructor.name.toLowerCase()
924
+ ) {
925
+ const msg = `Value passed to ${modelDefinition.name}.${field} is not an instance of ${relationship.remoteModelConstructor.name}`;
926
+ logger.error(msg, { model });
927
+
928
+ throw new Error(msg);
929
+ }
930
+ }
931
+
932
+ if (relationship.isComplete) {
933
+ for (let i = 0; i < relationship.localJoinFields.length; i++) {
934
+ this[relationship.localJoinFields[i]] =
935
+ model[relationship.remoteJoinFields[i]];
936
+ }
937
+ const instanceMemos = modelInstanceAssociationsMap.has(this)
938
+ ? modelInstanceAssociationsMap.get(this)!
939
+ : modelInstanceAssociationsMap.set(this, {}).get(this)!;
940
+ instanceMemos[field] = model;
941
+ }
942
+ },
943
+ get() {
944
+ const instanceMemos = modelInstanceAssociationsMap.has(this)
945
+ ? modelInstanceAssociationsMap.get(this)!
946
+ : modelInstanceAssociationsMap.set(this, {}).get(this)!;
947
+
948
+ if (!instanceMemos.hasOwnProperty(field)) {
949
+ if (getAttachment(this) === ModelAttachment.DataStore) {
950
+ const resultPromise = instance.query(
951
+ relationship.remoteModelConstructor as PersistentModelConstructor<T>,
952
+ base =>
953
+ base.and(q => {
954
+ return relationship.remoteJoinFields.map((field, index) => {
955
+ return (q[field] as any).eq(
956
+ this[relationship.localJoinFields[index]]
957
+ );
958
+ });
959
+ })
960
+ );
961
+
962
+ if (relationship.type === 'HAS_MANY') {
963
+ instanceMemos[field] = new AsyncCollection(resultPromise);
964
+ } else {
965
+ instanceMemos[field] = resultPromise.then(rows => {
966
+ if (rows.length > 1) {
967
+ // should never happen for a HAS_ONE or BELONGS_TO.
968
+ const err = new Error(`
969
+ Data integrity error.
970
+ Too many records found for a HAS_ONE/BELONGS_TO field '${modelDefinition.name}.${field}'
971
+ `);
972
+ console.error(err);
973
+ throw err;
974
+ } else {
975
+ return rows[0];
976
+ }
977
+ });
978
+ }
979
+ } else if (getAttachment(this) === ModelAttachment.API) {
980
+ throw new Error('Lazy loading from API is not yet supported!');
981
+ } else {
982
+ if (relationship.type === 'HAS_MANY') {
983
+ return new AsyncCollection([]);
984
+ } else {
985
+ return Promise.resolve(undefined);
986
+ }
987
+ }
988
+ }
989
+
990
+ return instanceMemos[field];
991
+ },
992
+ });
993
+ }
994
+
651
995
  return clazz;
652
996
  };
653
997
 
998
+ export class AsyncItem<T> extends Promise<T> {}
999
+
1000
+ export class AsyncCollection<T> implements AsyncIterable<T> {
1001
+ values: Array<any> | Promise<Array<any>>;
1002
+
1003
+ constructor(values: Array<any> | Promise<Array<any>>) {
1004
+ this.values = values;
1005
+ }
1006
+
1007
+ [Symbol.asyncIterator](): AsyncIterator<T> {
1008
+ let values;
1009
+ let index = 0;
1010
+ return {
1011
+ next: async () => {
1012
+ if (!values) values = await this.values;
1013
+ if (index < values.length) {
1014
+ const result = {
1015
+ value: values[index],
1016
+ done: false,
1017
+ };
1018
+ index++;
1019
+ return result;
1020
+ }
1021
+ return {
1022
+ value: null,
1023
+ done: true,
1024
+ };
1025
+ },
1026
+ };
1027
+ }
1028
+
1029
+ async toArray({
1030
+ max = Number.MAX_SAFE_INTEGER,
1031
+ }: { max?: number } = {}): Promise<T[]> {
1032
+ const output: T[] = [];
1033
+ let i = 0;
1034
+ for await (const element of this) {
1035
+ if (i < max) {
1036
+ output.push(element);
1037
+ i++;
1038
+ } else {
1039
+ break;
1040
+ }
1041
+ }
1042
+ return output;
1043
+ }
1044
+ }
1045
+
654
1046
  const checkReadOnlyPropertyOnCreate = <T extends PersistentModel>(
655
1047
  draft: T,
656
1048
  modelDefinition: SchemaModel
@@ -740,8 +1132,7 @@ function getModelConstructorByModelName(
740
1132
  result = storageClasses[modelName];
741
1133
  break;
742
1134
  default:
743
- exhaustiveCheck(namespaceName);
744
- break;
1135
+ throw new Error(`Invalid namespace: ${namespaceName}`);
745
1136
  }
746
1137
 
747
1138
  if (isValidModelConstructor(result)) {
@@ -858,23 +1249,31 @@ class DataStore {
858
1249
  private API = API;
859
1250
  private Cache = Cache;
860
1251
 
1252
+ // Non-null assertions (bang operator) have been added to most of these properties
1253
+ // to make TS happy. These properties are all expected to be set immediately after
1254
+ // construction.
1255
+
1256
+ // TODO: Refactor to use proper DI if possible. If not possible, change these to
1257
+ // optionals and implement conditional checks throughout. Rinse/repeat on all
1258
+ // sync engine processors, storage engine, adapters, etc..
1259
+
861
1260
  private amplifyConfig: Record<string, any> = {};
862
- private authModeStrategy: AuthModeStrategy;
863
- private conflictHandler: ConflictHandler;
864
- private errorHandler: (error: SyncError<PersistentModel>) => void;
865
- private fullSyncInterval: number;
1261
+ private authModeStrategy!: AuthModeStrategy;
1262
+ private conflictHandler!: ConflictHandler;
1263
+ private errorHandler!: (error: SyncError<PersistentModel>) => void;
1264
+ private fullSyncInterval!: number;
866
1265
  private initialized?: Promise<void>;
867
- private initReject: Function;
868
- private initResolve: Function;
869
- private maxRecordsToSync: number;
1266
+ private initReject!: Function;
1267
+ private initResolve!: Function;
1268
+ private maxRecordsToSync!: number;
870
1269
  private storage?: Storage;
871
1270
  private sync?: SyncEngine;
872
- private syncPageSize: number;
873
- private syncExpressions: SyncExpression[];
1271
+ private syncPageSize!: number;
1272
+ private syncExpressions!: SyncExpression[];
874
1273
  private syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>> =
875
1274
  new WeakMap<SchemaModel, ModelPredicate<any>>();
876
- private sessionId: string;
877
- private storageAdapter: Adapter;
1275
+ private sessionId?: string;
1276
+ private storageAdapter!: Adapter;
878
1277
  // object that gets passed to descendent classes. Allows us to pass these down by reference
879
1278
  private amplifyContext: AmplifyContext = {
880
1279
  Auth: this.Auth,
@@ -992,10 +1391,8 @@ class DataStore {
992
1391
  );
993
1392
 
994
1393
  await this.storage.init();
995
-
996
1394
  checkSchemaInitialized();
997
1395
  await checkSchemaVersion(this.storage, schema.version);
998
-
999
1396
  const { aws_appsync_graphqlEndpoint } = this.amplifyConfig;
1000
1397
 
1001
1398
  if (aws_appsync_graphqlEndpoint) {
@@ -1075,27 +1472,36 @@ class DataStore {
1075
1472
  ): Promise<T | undefined>;
1076
1473
  <T extends PersistentModel>(
1077
1474
  modelConstructor: PersistentModelConstructor<T>,
1078
- criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
1475
+ criteria?:
1476
+ | RecursiveModelPredicateExtender<T>
1477
+ | typeof PredicateAll
1478
+ | null,
1079
1479
  paginationProducer?: ProducerPaginationInput<T>
1080
1480
  ): Promise<T[]>;
1081
1481
  } = async <T extends PersistentModel>(
1082
1482
  modelConstructor: PersistentModelConstructor<T>,
1083
1483
  identifierOrCriteria?:
1084
1484
  | IdentifierFieldOrIdentifierObject<T, PersistentModelMetaData<T>>
1085
- | ProducerModelPredicate<T>
1086
- | typeof PredicateAll,
1485
+ | RecursiveModelPredicateExtender<T>
1486
+ | typeof PredicateAll
1487
+ | null,
1087
1488
  paginationProducer?: ProducerPaginationInput<T>
1088
1489
  ): Promise<T | T[] | undefined> => {
1089
1490
  return this.runningProcesses
1090
1491
  .add(async () => {
1091
1492
  await this.start();
1092
1493
 
1494
+ let result: T[];
1495
+
1496
+ if (!this.storage) {
1497
+ throw new Error('No storage to query');
1498
+ }
1499
+
1093
1500
  //#region Input validation
1094
1501
 
1095
1502
  if (!isValidModelConstructor(modelConstructor)) {
1096
1503
  const msg = 'Constructor is not for a valid model';
1097
1504
  logger.error(msg, { modelConstructor });
1098
-
1099
1505
  throw new Error(msg);
1100
1506
  }
1101
1507
 
@@ -1106,9 +1512,16 @@ class DataStore {
1106
1512
  }
1107
1513
 
1108
1514
  const modelDefinition = getModelDefinition(modelConstructor);
1109
- const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1515
+ if (!modelDefinition) {
1516
+ throw new Error('Invalid model definition provided!');
1517
+ }
1110
1518
 
1111
- let predicate: ModelPredicate<T>;
1519
+ const pagination = this.processPagination(
1520
+ modelDefinition,
1521
+ paginationProducer
1522
+ );
1523
+
1524
+ const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1112
1525
 
1113
1526
  if (isQueryOne(identifierOrCriteria)) {
1114
1527
  if (keyFields.length > 1) {
@@ -1118,71 +1531,78 @@ class DataStore {
1118
1531
  throw new Error(msg);
1119
1532
  }
1120
1533
 
1121
- predicate = ModelPredicateCreator.createForSingleField<T>(
1534
+ const predicate = ModelPredicateCreator.createForSingleField<T>(
1122
1535
  modelDefinition,
1123
1536
  keyFields[0],
1124
1537
  identifierOrCriteria
1125
1538
  );
1539
+
1540
+ result = await this.storage.query<T>(
1541
+ modelConstructor,
1542
+ predicate,
1543
+ pagination
1544
+ );
1126
1545
  } else {
1127
1546
  // Object is being queried using object literal syntax
1128
1547
  if (isIdentifierObject(<T>identifierOrCriteria, modelDefinition)) {
1129
- predicate = ModelPredicateCreator.createForPk<T>(
1548
+ const predicate = ModelPredicateCreator.createForPk<T>(
1130
1549
  modelDefinition,
1131
1550
  <T>identifierOrCriteria
1132
1551
  );
1133
- } else if (isPredicatesAll(identifierOrCriteria)) {
1134
- // Predicates.ALL means "all records", so no predicate (undefined)
1135
- predicate = undefined;
1136
- } else {
1137
- predicate = ModelPredicateCreator.createFromExisting(
1138
- modelDefinition,
1139
- <any>identifierOrCriteria
1552
+ result = await this.storage.query<T>(
1553
+ modelConstructor,
1554
+ predicate,
1555
+ pagination
1556
+ );
1557
+ } else if (
1558
+ !identifierOrCriteria ||
1559
+ isPredicatesAll(identifierOrCriteria)
1560
+ ) {
1561
+ result = await this.storage?.query<T>(
1562
+ modelConstructor,
1563
+ undefined,
1564
+ pagination
1140
1565
  );
1566
+ } else {
1567
+ const seedPredicate = recursivePredicateFor<T>({
1568
+ builder: modelConstructor,
1569
+ schema: modelDefinition,
1570
+ pkField: getModelPKFieldName(modelConstructor),
1571
+ });
1572
+ const predicate = (
1573
+ identifierOrCriteria as RecursiveModelPredicateExtender<T>
1574
+ )(seedPredicate).__query;
1575
+ result = (await predicate.fetch(this.storage)) as T[];
1576
+ result = inMemoryPagination(result, pagination);
1141
1577
  }
1142
1578
  }
1143
1579
 
1144
- const pagination = this.processPagination(
1145
- modelDefinition,
1146
- paginationProducer
1147
- );
1148
-
1149
1580
  //#endregion
1150
1581
 
1151
- logger.debug('params ready', {
1152
- modelConstructor,
1153
- predicate: ModelPredicateCreator.getPredicates(predicate, false),
1154
- pagination: {
1155
- ...pagination,
1156
- sort: ModelSortPredicateCreator.getPredicates(
1157
- pagination && pagination.sort,
1158
- false
1159
- ),
1160
- },
1161
- });
1162
-
1163
- const result = await this.storage.query(
1164
- modelConstructor,
1165
- predicate,
1166
- pagination
1167
- );
1168
-
1169
1582
  const returnOne =
1170
1583
  isQueryOne(identifierOrCriteria) ||
1171
1584
  isIdentifierObject(identifierOrCriteria, modelDefinition);
1172
1585
 
1173
- return returnOne ? result[0] : result;
1586
+ return attached(
1587
+ returnOne ? result[0] : result,
1588
+ ModelAttachment.DataStore
1589
+ );
1174
1590
  }, 'datastore query')
1175
1591
  .catch(this.handleAddProcError('DataStore.query()'));
1176
1592
  };
1177
1593
 
1178
1594
  save = async <T extends PersistentModel>(
1179
1595
  model: T,
1180
- condition?: ProducerModelPredicate<T>
1596
+ condition?: ModelPredicateExtender<T>
1181
1597
  ): Promise<T> => {
1182
1598
  return this.runningProcesses
1183
1599
  .add(async () => {
1184
1600
  await this.start();
1185
1601
 
1602
+ if (!this.storage) {
1603
+ throw new Error('No storage to save to');
1604
+ }
1605
+
1186
1606
  // Immer patches for constructing a correct update mutation input
1187
1607
  // Allows us to only include changed fields for updates
1188
1608
  const patchesTuple = modelPatchesMap.get(model);
@@ -1198,22 +1618,67 @@ class DataStore {
1198
1618
  }
1199
1619
 
1200
1620
  const modelDefinition = getModelDefinition(modelConstructor);
1621
+ if (!modelDefinition) {
1622
+ throw new Error('Model Definition could not be found for model');
1623
+ }
1201
1624
 
1202
- const producedCondition = ModelPredicateCreator.createFromExisting(
1203
- modelDefinition,
1204
- condition!
1205
- );
1625
+ const modelMeta = {
1626
+ builder: modelConstructor as PersistentModelConstructor<T>,
1627
+ schema: modelDefinition,
1628
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
1629
+ };
1206
1630
 
1207
- const [savedModel] = await this.storage.runExclusive(async s => {
1208
- await s.save(model, producedCondition, undefined, patchesTuple);
1631
+ await this.storage.runExclusive(async s => {
1632
+ // no enforcement for HAS_MANY on save, because the ~related~ entities
1633
+ // hold the FK in that case.
1634
+ const nonHasManyRelationships = ModelRelationship.allFrom(
1635
+ modelMeta
1636
+ ).filter(r => r.type === 'BELONGS_TO');
1637
+ for (const relationship of nonHasManyRelationships) {
1638
+ const queryObject = relationship.createRemoteQueryObject(model);
1639
+ if (queryObject !== null) {
1640
+ // console.log({ queryObject });
1641
+ const related = await s.query(
1642
+ relationship.remoteModelConstructor,
1643
+ ModelPredicateCreator.createFromFlatEqualities(
1644
+ relationship.remoteDefinition!,
1645
+ queryObject
1646
+ )
1647
+ );
1648
+ if (related.length === 0) {
1649
+ throw new Error(
1650
+ [
1651
+ `Data integrity error. You tried to save a ${
1652
+ modelDefinition.name
1653
+ } (${JSON.stringify(model)})`,
1654
+ `but the instance assigned to the "${relationship.field}" property`,
1655
+ `does not exist in the local database. If you're trying to create the related`,
1656
+ `"${relationship.remoteDefinition?.name}", you must save it independently first.`,
1657
+ ].join(' ')
1658
+ );
1659
+ }
1660
+ }
1661
+ }
1662
+ });
1209
1663
 
1664
+ const producedCondition = condition
1665
+ ? condition(predicateFor(modelMeta)).__query.toStoragePredicate<T>()
1666
+ : undefined;
1667
+
1668
+ const [savedModel] = await this.storage.runExclusive(async s => {
1669
+ const saved = await s.save(
1670
+ model,
1671
+ producedCondition,
1672
+ undefined,
1673
+ patchesTuple
1674
+ );
1210
1675
  return s.query<T>(
1211
1676
  modelConstructor,
1212
1677
  ModelPredicateCreator.createForPk(modelDefinition, model)
1213
1678
  );
1214
1679
  });
1215
1680
 
1216
- return savedModel;
1681
+ return attached(savedModel, ModelAttachment.DataStore);
1217
1682
  }, 'datastore save')
1218
1683
  .catch(this.handleAddProcError('DataStore.save()'));
1219
1684
  };
@@ -1260,24 +1725,28 @@ class DataStore {
1260
1725
  ): Promise<T[]>;
1261
1726
  <T extends PersistentModel>(
1262
1727
  modelConstructor: PersistentModelConstructor<T>,
1263
- condition: ProducerModelPredicate<T> | typeof PredicateAll
1728
+ condition: ModelPredicateExtender<T> | typeof PredicateAll
1264
1729
  ): Promise<T[]>;
1265
1730
  <T extends PersistentModel>(
1266
1731
  model: T,
1267
- condition?: ProducerModelPredicate<T>
1732
+ condition?: ModelPredicateExtender<T>
1268
1733
  ): Promise<T>;
1269
1734
  } = async <T extends PersistentModel>(
1270
1735
  modelOrConstructor: T | PersistentModelConstructor<T>,
1271
1736
  identifierOrCriteria?:
1272
1737
  | IdentifierFieldOrIdentifierObject<T, PersistentModelMetaData<T>>
1273
- | ProducerModelPredicate<T>
1738
+ | ModelPredicateExtender<T>
1274
1739
  | typeof PredicateAll
1275
1740
  ): Promise<T | T[]> => {
1276
1741
  return this.runningProcesses
1277
1742
  .add(async () => {
1278
1743
  await this.start();
1279
1744
 
1280
- let condition: ModelPredicate<T>;
1745
+ if (!this.storage) {
1746
+ throw new Error('No storage to delete from');
1747
+ }
1748
+
1749
+ let condition: ModelPredicate<T> | undefined;
1281
1750
 
1282
1751
  if (!modelOrConstructor) {
1283
1752
  const msg = 'Model or Model Constructor required';
@@ -1299,6 +1768,12 @@ class DataStore {
1299
1768
 
1300
1769
  const modelDefinition = getModelDefinition(modelConstructor);
1301
1770
 
1771
+ if (!modelDefinition) {
1772
+ throw new Error(
1773
+ 'Could not find model definition for modelConstructor.'
1774
+ );
1775
+ }
1776
+
1302
1777
  if (typeof identifierOrCriteria === 'string') {
1303
1778
  const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1304
1779
 
@@ -1310,7 +1785,7 @@ class DataStore {
1310
1785
  }
1311
1786
 
1312
1787
  condition = ModelPredicateCreator.createForSingleField<T>(
1313
- getModelDefinition(modelConstructor),
1788
+ modelDefinition,
1314
1789
  keyFields[0],
1315
1790
  identifierOrCriteria
1316
1791
  );
@@ -1321,14 +1796,13 @@ class DataStore {
1321
1796
  <T>identifierOrCriteria
1322
1797
  );
1323
1798
  } else {
1324
- condition = ModelPredicateCreator.createFromExisting(
1325
- modelDefinition,
1326
- /**
1327
- * idOrCriteria is always a ProducerModelPredicate<T>, never a symbol.
1328
- * The symbol is used only for typing purposes. e.g. see Predicates.ALL
1329
- */
1330
- identifierOrCriteria as ProducerModelPredicate<T>
1331
- );
1799
+ condition = (identifierOrCriteria as ModelPredicateExtender<T>)(
1800
+ predicateFor({
1801
+ builder: modelConstructor as PersistentModelConstructor<T>,
1802
+ schema: modelDefinition,
1803
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
1804
+ })
1805
+ )?.__query.toStoragePredicate<T>();
1332
1806
  }
1333
1807
 
1334
1808
  if (
@@ -1348,7 +1822,7 @@ class DataStore {
1348
1822
  condition
1349
1823
  );
1350
1824
 
1351
- return deleted;
1825
+ return attached(deleted, ModelAttachment.DataStore);
1352
1826
  } else {
1353
1827
  const model = modelOrConstructor;
1354
1828
  const modelConstructor = Object.getPrototypeOf(model || {})
@@ -1363,6 +1837,12 @@ class DataStore {
1363
1837
 
1364
1838
  const modelDefinition = getModelDefinition(modelConstructor);
1365
1839
 
1840
+ if (!modelDefinition) {
1841
+ throw new Error(
1842
+ 'Could not find model definition for modelConstructor.'
1843
+ );
1844
+ }
1845
+
1366
1846
  const pkPredicate = ModelPredicateCreator.createForPk<T>(
1367
1847
  modelDefinition,
1368
1848
  model
@@ -1376,16 +1856,20 @@ class DataStore {
1376
1856
  throw new Error(msg);
1377
1857
  }
1378
1858
 
1379
- condition = (<ProducerModelPredicate<T>>identifierOrCriteria)(
1380
- pkPredicate
1381
- );
1859
+ condition = (identifierOrCriteria as ModelPredicateExtender<T>)(
1860
+ predicateFor({
1861
+ builder: modelConstructor as PersistentModelConstructor<T>,
1862
+ schema: modelDefinition,
1863
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
1864
+ })
1865
+ ).__query.toStoragePredicate<T>(pkPredicate);
1382
1866
  } else {
1383
1867
  condition = pkPredicate;
1384
1868
  }
1385
1869
 
1386
1870
  const [[deleted]] = await this.storage.delete(model, condition);
1387
1871
 
1388
- return deleted;
1872
+ return attached(deleted, ModelAttachment.DataStore);
1389
1873
  }
1390
1874
  }, 'datastore delete')
1391
1875
  .catch(this.handleAddProcError('DataStore.delete()'));
@@ -1401,7 +1885,7 @@ class DataStore {
1401
1885
 
1402
1886
  <T extends PersistentModel>(
1403
1887
  modelConstructor: PersistentModelConstructor<T>,
1404
- criteria?: ProducerModelPredicate<T> | typeof PredicateAll
1888
+ criteria?: RecursiveModelPredicateExtender<T> | typeof PredicateAll
1405
1889
  ): Observable<SubscriptionMessage<T>>;
1406
1890
 
1407
1891
  <T extends PersistentModel>(model: T): Observable<SubscriptionMessage<T>>;
@@ -1409,10 +1893,10 @@ class DataStore {
1409
1893
  modelOrConstructor?: T | PersistentModelConstructor<T>,
1410
1894
  identifierOrCriteria?:
1411
1895
  | string
1412
- | ProducerModelPredicate<T>
1896
+ | RecursiveModelPredicateExtender<T>
1413
1897
  | typeof PredicateAll
1414
1898
  ): Observable<SubscriptionMessage<T>> => {
1415
- let predicate: ModelPredicate<T>;
1899
+ let executivePredicate: GroupCondition;
1416
1900
 
1417
1901
  const modelConstructor: PersistentModelConstructor<T> | undefined =
1418
1902
  modelOrConstructor && isValidModelConstructor<T>(modelOrConstructor)
@@ -1448,7 +1932,7 @@ class DataStore {
1448
1932
  modelConstructor &&
1449
1933
  isIdentifierObject(
1450
1934
  identifierOrCriteria,
1451
- getModelDefinition(modelConstructor)
1935
+ getModelDefinition(modelConstructor!)!
1452
1936
  )
1453
1937
  ) {
1454
1938
  const msg = errorMessages.observeWithObjectLiteral;
@@ -1470,30 +1954,19 @@ class DataStore {
1470
1954
  throw new Error(msg);
1471
1955
  }
1472
1956
 
1473
- if (typeof identifierOrCriteria === 'string') {
1474
- const modelDefinition = getModelDefinition(modelConstructor);
1475
- const [keyField] = extractPrimaryKeyFieldNames(modelDefinition);
1476
-
1477
- predicate = ModelPredicateCreator.createForSingleField<T>(
1478
- getModelDefinition(modelConstructor),
1479
- keyField,
1480
- identifierOrCriteria
1481
- );
1482
- } else {
1483
- if (isPredicatesAll(identifierOrCriteria)) {
1484
- predicate = undefined;
1485
- } else {
1486
- predicate =
1487
- modelConstructor &&
1488
- ModelPredicateCreator.createFromExisting<T>(
1489
- getModelDefinition(modelConstructor),
1490
- identifierOrCriteria
1491
- );
1492
- }
1957
+ if (modelConstructor && typeof identifierOrCriteria === 'string') {
1958
+ const buildIdPredicate = seed => seed.id.eq(identifierOrCriteria);
1959
+ executivePredicate = buildIdPredicate(
1960
+ buildSeedPredicate(modelConstructor)
1961
+ ).__query;
1962
+ } else if (modelConstructor && typeof identifierOrCriteria === 'function') {
1963
+ executivePredicate = (
1964
+ identifierOrCriteria as RecursiveModelPredicateExtender<T>
1965
+ )(buildSeedPredicate(modelConstructor) as any).__query;
1493
1966
  }
1494
1967
 
1495
1968
  return new Observable<SubscriptionMessage<T>>(observer => {
1496
- let handle: ZenObservable.Subscription;
1969
+ let source: ZenObservable.Subscription;
1497
1970
 
1498
1971
  this.runningProcesses
1499
1972
  .add(async () => {
@@ -1501,8 +1974,7 @@ class DataStore {
1501
1974
 
1502
1975
  // Filter the events returned by Storage according to namespace,
1503
1976
  // append original element data, and subscribe to the observable
1504
- handle = this.storage
1505
- .observe(modelConstructor, predicate)
1977
+ source = this.storage!.observe(modelConstructor)
1506
1978
  .filter(({ model }) => namespaceResolver(model) === USER)
1507
1979
  .subscribe({
1508
1980
  next: item =>
@@ -1518,8 +1990,9 @@ class DataStore {
1518
1990
  // item from storage to ensure it's fully populated.
1519
1991
  if (item.opType !== 'DELETE') {
1520
1992
  const modelDefinition = getModelDefinition(item.model);
1521
- const keyFields =
1522
- extractPrimaryKeyFieldNames(modelDefinition);
1993
+ const keyFields = extractPrimaryKeyFieldNames(
1994
+ modelDefinition!
1995
+ );
1523
1996
  const primaryKeysAndValues = extractPrimaryKeysAndValues(
1524
1997
  item.element,
1525
1998
  keyFields
@@ -1534,7 +2007,12 @@ class DataStore {
1534
2007
  };
1535
2008
  }
1536
2009
 
1537
- observer.next(message as SubscriptionMessage<T>);
2010
+ if (
2011
+ !executivePredicate ||
2012
+ (await executivePredicate.matches(message.element))
2013
+ ) {
2014
+ observer.next(message as SubscriptionMessage<T>);
2015
+ }
1538
2016
  }, 'datastore observe message handler'),
1539
2017
  error: err => observer.error(err),
1540
2018
  complete: () => observer.complete(),
@@ -1549,8 +2027,8 @@ class DataStore {
1549
2027
  // complete() message async and not registering with the context,
1550
2028
  // this will still be problematic.
1551
2029
  return this.runningProcesses.addCleaner(async () => {
1552
- if (handle) {
1553
- handle.unsubscribe();
2030
+ if (source) {
2031
+ source.unsubscribe();
1554
2032
  }
1555
2033
  }, 'DataStore.observe() cleanup');
1556
2034
  });
@@ -1559,12 +2037,12 @@ class DataStore {
1559
2037
  observeQuery: {
1560
2038
  <T extends PersistentModel>(
1561
2039
  modelConstructor: PersistentModelConstructor<T>,
1562
- criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
2040
+ criteria?: RecursiveModelPredicateExtender<T> | typeof PredicateAll,
1563
2041
  paginationProducer?: ObserveQueryOptions<T>
1564
2042
  ): Observable<DataStoreSnapshot<T>>;
1565
2043
  } = <T extends PersistentModel>(
1566
2044
  model: PersistentModelConstructor<T>,
1567
- criteria?: ProducerModelPredicate<T> | typeof PredicateAll,
2045
+ criteria?: RecursiveModelPredicateExtender<T> | typeof PredicateAll,
1568
2046
  options?: ObserveQueryOptions<T>
1569
2047
  ): Observable<DataStoreSnapshot<T>> => {
1570
2048
  return new Observable<DataStoreSnapshot<T>>(observer => {
@@ -1572,7 +2050,8 @@ class DataStore {
1572
2050
  const itemsChanged = new Map<string, T>();
1573
2051
  let deletedItemIds: string[] = [];
1574
2052
  let handle: ZenObservable.Subscription;
1575
- let predicate: ModelPredicate<T>;
2053
+ // let predicate: ModelPredicate<T> | undefined;
2054
+ let executivePredicate: GroupCondition | undefined;
1576
2055
 
1577
2056
  /**
1578
2057
  * As the name suggests, this geneates a snapshot in the form of
@@ -1601,29 +2080,17 @@ class DataStore {
1601
2080
  const sortOptions = sort ? { sort } : undefined;
1602
2081
 
1603
2082
  const modelDefinition = getModelDefinition(model);
1604
- const keyFields = extractPrimaryKeyFieldNames(modelDefinition);
1605
-
1606
- if (isQueryOne(criteria)) {
1607
- predicate = ModelPredicateCreator.createForSingleField<T>(
1608
- modelDefinition,
1609
- keyFields[0],
1610
- criteria
1611
- );
1612
- } else {
1613
- if (isPredicatesAll(criteria)) {
1614
- // Predicates.ALL means "all records", so no predicate (undefined)
1615
- predicate = undefined;
1616
- } else {
1617
- predicate = ModelPredicateCreator.createFromExisting(
1618
- modelDefinition,
1619
- criteria
1620
- );
1621
- }
2083
+ if (!modelDefinition) {
2084
+ throw new Error('Could not find model definition.');
1622
2085
  }
1623
2086
 
1624
- const { predicates, type: predicateGroupType } =
1625
- ModelPredicateCreator.getPredicates(predicate, false) || {};
1626
- const hasPredicate = !!predicates;
2087
+ if (model && typeof criteria === 'function') {
2088
+ executivePredicate = (criteria as RecursiveModelPredicateExtender<T>)(
2089
+ buildSeedPredicate(model)
2090
+ ).__query;
2091
+ } else if (isPredicatesAll(criteria)) {
2092
+ executivePredicate = undefined;
2093
+ }
1627
2094
 
1628
2095
  this.runningProcesses
1629
2096
  .add(async () => {
@@ -1631,7 +2098,7 @@ class DataStore {
1631
2098
  // first, query and return any locally-available records
1632
2099
  (await this.query(model, criteria, sortOptions)).forEach(item => {
1633
2100
  const itemModelDefinition = getModelDefinition(model);
1634
- const idOrPk = getIdentifierValue(itemModelDefinition, item);
2101
+ const idOrPk = getIdentifierValue(itemModelDefinition!, item);
1635
2102
  items.set(idOrPk, item);
1636
2103
  });
1637
2104
 
@@ -1640,52 +2107,57 @@ class DataStore {
1640
2107
  // to have visibility into items that move from in-set to out-of-set.
1641
2108
  // We need to explicitly remove those items from the existing snapshot.
1642
2109
  handle = this.observe(model).subscribe(
1643
- ({ element, model, opType }) => {
1644
- const itemModelDefinition = getModelDefinition(model);
1645
- const idOrPk = getIdentifierValue(itemModelDefinition, element);
1646
- if (
1647
- hasPredicate &&
1648
- !validatePredicate(element, predicateGroupType, predicates)
1649
- ) {
2110
+ ({ element, model, opType }) =>
2111
+ this.runningProcesses.isOpen &&
2112
+ this.runningProcesses.add(async () => {
2113
+ const itemModelDefinition = getModelDefinition(model)!;
2114
+ const idOrPk = getIdentifierValue(
2115
+ itemModelDefinition,
2116
+ element
2117
+ );
1650
2118
  if (
1651
- opType === 'UPDATE' &&
1652
- (items.has(idOrPk) || itemsChanged.has(idOrPk))
2119
+ executivePredicate &&
2120
+ !(await executivePredicate.matches(element))
1653
2121
  ) {
1654
- // tracking as a "deleted item" will include the item in
1655
- // page limit calculations and ensure it is removed from the
1656
- // final items collection, regardless of which collection(s)
1657
- // it is currently in. (I mean, it could be in both, right!?)
2122
+ if (
2123
+ opType === 'UPDATE' &&
2124
+ (items.has(idOrPk) || itemsChanged.has(idOrPk))
2125
+ ) {
2126
+ // tracking as a "deleted item" will include the item in
2127
+ // page limit calculations and ensure it is removed from the
2128
+ // final items collection, regardless of which collection(s)
2129
+ // it is currently in. (I mean, it could be in both, right!?)
2130
+ deletedItemIds.push(idOrPk);
2131
+ } else {
2132
+ // ignore updates for irrelevant/filtered items.
2133
+ return;
2134
+ }
2135
+ }
2136
+
2137
+ // Flag items which have been recently deleted
2138
+ // NOTE: Merging of separate operations to the same model instance is handled upstream
2139
+ // in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
2140
+ // depends on the LATEST record (for a given id).
2141
+ if (opType === 'DELETE') {
1658
2142
  deletedItemIds.push(idOrPk);
1659
2143
  } else {
1660
- // ignore updates for irrelevant/filtered items.
1661
- return;
2144
+ itemsChanged.set(idOrPk, element);
1662
2145
  }
1663
- }
1664
2146
 
1665
- // Flag items which have been recently deleted
1666
- // NOTE: Merging of separate operations to the same model instance is handled upstream
1667
- // in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
1668
- // depends on the LATEST record (for a given id).
1669
- if (opType === 'DELETE') {
1670
- deletedItemIds.push(idOrPk);
1671
- } else {
1672
- itemsChanged.set(idOrPk, element);
1673
- }
2147
+ const isSynced =
2148
+ this.sync?.getModelSyncedStatus(model) ?? false;
1674
2149
 
1675
- const isSynced =
1676
- this.sync?.getModelSyncedStatus(model) ?? false;
2150
+ const limit =
2151
+ itemsChanged.size - deletedItemIds.length >=
2152
+ this.syncPageSize;
1677
2153
 
1678
- const limit =
1679
- itemsChanged.size - deletedItemIds.length >=
1680
- this.syncPageSize;
1681
-
1682
- if (limit || isSynced) {
1683
- limitTimerRace.resolve();
1684
- }
2154
+ if (limit || isSynced) {
2155
+ limitTimerRace.resolve();
2156
+ }
1685
2157
 
1686
- // kicks off every subsequent race as results sync down
1687
- limitTimerRace.start();
1688
- }
2158
+ // kicks off every subsequent race as results sync down
2159
+ limitTimerRace.start();
2160
+ }, 'handle observeQuery observed event')
1689
2161
  );
1690
2162
 
1691
2163
  // returns a set of initial/locally-available results
@@ -1719,7 +2191,7 @@ class DataStore {
1719
2191
  items.clear();
1720
2192
  itemsArray.forEach(item => {
1721
2193
  const itemModelDefinition = getModelDefinition(model);
1722
- const idOrPk = getIdentifierValue(itemModelDefinition, item);
2194
+ const idOrPk = getIdentifierValue(itemModelDefinition!, item);
1723
2195
  items.set(idOrPk, item);
1724
2196
  });
1725
2197
 
@@ -1743,6 +2215,7 @@ class DataStore {
1743
2215
  const emitSnapshot = (snapshot: DataStoreSnapshot<T>): void => {
1744
2216
  // send the generated snapshot to the primary subscription.
1745
2217
  // NOTE: This observer's handler *could* be async ...
2218
+
1746
2219
  observer.next(snapshot);
1747
2220
 
1748
2221
  // reset the changed items sets
@@ -1758,10 +2231,10 @@ class DataStore {
1758
2231
  */
1759
2232
  const sortItems = (itemsToSort: T[]): void => {
1760
2233
  const modelDefinition = getModelDefinition(model);
1761
- const pagination = this.processPagination(modelDefinition, options);
2234
+ const pagination = this.processPagination(modelDefinition!, options);
1762
2235
 
1763
2236
  const sortPredicates = ModelSortPredicateCreator.getPredicates(
1764
- pagination.sort
2237
+ pagination!.sort!
1765
2238
  );
1766
2239
 
1767
2240
  if (sortPredicates.length) {
@@ -1962,9 +2435,9 @@ class DataStore {
1962
2435
  */
1963
2436
  private processPagination<T extends PersistentModel>(
1964
2437
  modelDefinition: SchemaModel,
1965
- paginationProducer: ProducerPaginationInput<T>
2438
+ paginationProducer?: ProducerPaginationInput<T>
1966
2439
  ): PaginationInput<T> | undefined {
1967
- let sortPredicate: SortPredicate<T>;
2440
+ let sortPredicate: SortPredicate<T> | undefined;
1968
2441
  const { limit, page, sort } = paginationProducer || {};
1969
2442
 
1970
2443
  if (limit === undefined && page === undefined && sort === undefined) {
@@ -1998,7 +2471,7 @@ class DataStore {
1998
2471
  if (sort) {
1999
2472
  sortPredicate = ModelSortPredicateCreator.createFromExisting(
2000
2473
  modelDefinition,
2001
- paginationProducer.sort
2474
+ sort
2002
2475
  );
2003
2476
  }
2004
2477
 
@@ -2026,21 +2499,24 @@ class DataStore {
2026
2499
  syncExpression: SyncExpression
2027
2500
  ): Promise<[SchemaModel, ModelPredicate<any>]> => {
2028
2501
  const { modelConstructor, conditionProducer } = await syncExpression;
2029
- const modelDefinition = getModelDefinition(modelConstructor);
2502
+ const modelDefinition = getModelDefinition(modelConstructor)!;
2030
2503
 
2031
2504
  // conditionProducer is either a predicate, e.g. (c) => c.field('eq', 1)
2032
2505
  // OR a function/promise that returns a predicate
2033
2506
  const condition = await this.unwrapPromise(conditionProducer);
2034
2507
  if (isPredicatesAll(condition)) {
2035
- return [modelDefinition, null];
2508
+ return [modelDefinition as any, null as any];
2036
2509
  }
2037
2510
 
2038
- const predicate = this.createFromCondition(
2039
- modelDefinition,
2040
- condition
2041
- );
2511
+ const predicate = condition(
2512
+ predicateFor({
2513
+ builder: modelConstructor,
2514
+ schema: modelDefinition,
2515
+ pkField: extractPrimaryKeyFieldNames(modelDefinition),
2516
+ })
2517
+ ).__query.toStoragePredicate<any>();
2042
2518
 
2043
- return [modelDefinition, predicate];
2519
+ return [modelDefinition as any, predicate as any];
2044
2520
  }
2045
2521
  )
2046
2522
  );
@@ -2065,7 +2541,7 @@ class DataStore {
2065
2541
 
2066
2542
  private async unwrapPromise<T extends PersistentModel>(
2067
2543
  conditionProducer
2068
- ): Promise<ProducerModelPredicate<T>> {
2544
+ ): Promise<ModelPredicateExtender<T>> {
2069
2545
  try {
2070
2546
  const condition = await conditionProducer();
2071
2547
  return condition;