@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/sync/index.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { browserOrNode, ConsoleLogger as Logger } from '@aws-amplify/core';
1
+ import {
2
+ browserOrNode,
3
+ ConsoleLogger as Logger,
4
+ BackgroundProcessManager,
5
+ } from '@aws-amplify/core';
2
6
  import { CONTROL_MSG as PUBSUB_CONTROL_MSG } from '@aws-amplify/pubsub';
3
7
  import Observable, { ZenObservable } from 'zen-observable-ts';
4
8
  import { ModelInstanceCreator } from '../datastore/datastore';
@@ -21,8 +25,13 @@ import {
21
25
  TypeConstructorMap,
22
26
  ModelPredicate,
23
27
  AuthModeStrategy,
28
+ ManagedIdentifier,
29
+ OptionallyManagedIdentifier,
24
30
  AmplifyContext,
25
31
  } from '../types';
32
+ // tslint:disable:no-duplicate-imports
33
+ import type { __modelMeta__ } from '../types';
34
+
26
35
  import { exhaustiveCheck, getNow, SYNC, USER } from '../util';
27
36
  import DataStoreConnectivity from './datastoreConnectivity';
28
37
  import { ModelMerger } from './merger';
@@ -32,6 +41,7 @@ import { CONTROL_MSG, SubscriptionProcessor } from './processors/subscription';
32
41
  import { SyncProcessor } from './processors/sync';
33
42
  import {
34
43
  createMutationInstanceFromModelOperation,
44
+ getIdentifierValue,
35
45
  predicateToGraphQLCondition,
36
46
  TransformerMutationType,
37
47
  } from './utils';
@@ -46,25 +56,26 @@ type StartParams = {
46
56
  };
47
57
 
48
58
  export declare class MutationEvent {
49
- constructor(init: ModelInit<MutationEvent>);
50
- static copyOf(
51
- src: MutationEvent,
52
- mutator: (draft: MutableModel<MutationEvent>) => void | MutationEvent
53
- ): MutationEvent;
59
+ readonly [__modelMeta__]: {
60
+ identifier: OptionallyManagedIdentifier<MutationEvent, 'id'>;
61
+ };
54
62
  public readonly id: string;
55
63
  public readonly model: string;
56
64
  public readonly operation: TransformerMutationType;
57
65
  public readonly modelId: string;
58
66
  public readonly condition: string;
59
- public data: string;
67
+ public readonly data: string;
68
+ constructor(init: ModelInit<MutationEvent>);
69
+ static copyOf(
70
+ src: MutationEvent,
71
+ mutator: (draft: MutableModel<MutationEvent>) => void | MutationEvent
72
+ ): MutationEvent;
60
73
  }
61
74
 
62
- declare class ModelMetadata {
63
- constructor(init: ModelInit<ModelMetadata>);
64
- static copyOf(
65
- src: ModelMetadata,
66
- mutator: (draft: MutableModel<ModelMetadata>) => void | ModelMetadata
67
- ): ModelMetadata;
75
+ export declare class ModelMetadata {
76
+ readonly [__modelMeta__]: {
77
+ identifier: ManagedIdentifier<ModelMetadata, 'id'>;
78
+ };
68
79
  public readonly id: string;
69
80
  public readonly namespace: string;
70
81
  public readonly model: string;
@@ -72,6 +83,11 @@ declare class ModelMetadata {
72
83
  public readonly lastSync?: number;
73
84
  public readonly lastFullSync?: number;
74
85
  public readonly lastSyncPredicate?: null | string;
86
+ constructor(init: ModelInit<ModelMetadata>);
87
+ static copyOf(
88
+ src: ModelMetadata,
89
+ mutator: (draft: MutableModel<ModelMetadata>) => void | ModelMetadata
90
+ ): ModelMetadata;
75
91
  }
76
92
 
77
93
  export enum ControlMessage {
@@ -101,6 +117,8 @@ export class SyncEngine {
101
117
  boolean
102
118
  > = new WeakMap();
103
119
 
120
+ private runningProcesses: BackgroundProcessManager;
121
+
104
122
  public getModelSyncedStatus(
105
123
  modelConstructor: PersistentModelConstructor<any>
106
124
  ): boolean {
@@ -119,11 +137,14 @@ export class SyncEngine {
119
137
  private readonly syncPredicates: WeakMap<SchemaModel, ModelPredicate<any>>,
120
138
  private readonly amplifyConfig: Record<string, any> = {},
121
139
  private readonly authModeStrategy: AuthModeStrategy,
122
- private readonly amplifyContext: AmplifyContext
140
+ private readonly amplifyContext: AmplifyContext,
141
+ private readonly connectivityMonitor?: DataStoreConnectivity
123
142
  ) {
143
+ this.runningProcesses = new BackgroundProcessManager();
144
+
124
145
  const MutationEvent = this.modelClasses[
125
146
  'MutationEvent'
126
- ] as PersistentModelConstructor<any>;
147
+ ] as PersistentModelConstructor<MutationEvent>;
127
148
 
128
149
  this.outbox = new MutationEventOutbox(
129
150
  this.schema,
@@ -166,7 +187,8 @@ export class SyncEngine {
166
187
  this.amplifyContext
167
188
  );
168
189
 
169
- this.datastoreConnectivity = new DataStoreConnectivity();
190
+ this.datastoreConnectivity =
191
+ this.connectivityMonitor || new DataStoreConnectivity();
170
192
  }
171
193
 
172
194
  start(params: StartParams) {
@@ -175,7 +197,7 @@ export class SyncEngine {
175
197
 
176
198
  let subscriptions: ZenObservable.Subscription[] = [];
177
199
 
178
- (async () => {
200
+ this.runningProcesses.add(async () => {
179
201
  try {
180
202
  await this.setupModels(params);
181
203
  } catch (err) {
@@ -183,230 +205,259 @@ export class SyncEngine {
183
205
  return;
184
206
  }
185
207
 
186
- const startPromise = new Promise(resolve => {
187
- this.datastoreConnectivity.status().subscribe(async ({ online }) => {
188
- // From offline to online
189
- if (online && !this.online) {
190
- this.online = online;
191
-
192
- observer.next({
193
- type: ControlMessage.SYNC_ENGINE_NETWORK_STATUS,
194
- data: {
195
- active: this.online,
196
- },
197
- });
198
-
199
- let ctlSubsObservable: Observable<CONTROL_MSG>;
200
- let dataSubsObservable: Observable<
201
- [TransformerMutationType, SchemaModel, PersistentModel]
202
- >;
203
-
204
- if (isNode) {
205
- logger.warn(
206
- 'Realtime disabled when in a server-side environment'
207
- );
208
- } else {
209
- //#region GraphQL Subscriptions
210
- [
211
- // const ctlObservable: Observable<CONTROL_MSG>
212
- ctlSubsObservable,
213
- // const dataObservable: Observable<[TransformerMutationType, SchemaModel, Readonly<{
214
- // id: string;
215
- // } & Record<string, any>>]>
216
- dataSubsObservable,
217
- ] = this.subscriptionsProcessor.start();
218
-
219
- try {
220
- await new Promise((resolve, reject) => {
221
- const ctlSubsSubscription = ctlSubsObservable.subscribe({
222
- next: msg => {
223
- if (msg === CONTROL_MSG.CONNECTED) {
224
- resolve();
225
- }
226
- },
227
- error: err => {
228
- reject(err);
229
- const handleDisconnect = this.disconnectionHandler();
230
- handleDisconnect(err);
231
- },
232
- });
233
-
234
- subscriptions.push(ctlSubsSubscription);
208
+ // this is awaited at the bottom. so, we don't need to register
209
+ // this explicitly with the context. it's already contained.
210
+ const startPromise = new Promise((doneStarting, failedStarting) => {
211
+ this.datastoreConnectivity.status().subscribe(
212
+ async ({ online }) =>
213
+ this.runningProcesses.isOpen &&
214
+ this.runningProcesses.add(async onTerminate => {
215
+ // From offline to online
216
+ if (online && !this.online) {
217
+ this.online = online;
218
+
219
+ observer.next({
220
+ type: ControlMessage.SYNC_ENGINE_NETWORK_STATUS,
221
+ data: {
222
+ active: this.online,
223
+ },
235
224
  });
236
- } catch (err) {
237
- observer.error(err);
238
- return;
239
- }
240
-
241
- logger.log('Realtime ready');
242
225
 
243
- observer.next({
244
- type: ControlMessage.SYNC_ENGINE_SUBSCRIPTIONS_ESTABLISHED,
245
- });
226
+ let ctlSubsObservable: Observable<CONTROL_MSG>;
227
+ let dataSubsObservable: Observable<
228
+ [TransformerMutationType, SchemaModel, PersistentModel]
229
+ >;
246
230
 
247
- //#endregion
248
- }
249
-
250
- //#region Base & Sync queries
251
- try {
252
- await new Promise((resolve, reject) => {
253
- const syncQuerySubscription =
254
- this.syncQueriesObservable().subscribe({
255
- next: message => {
256
- const { type } = message;
257
-
258
- if (
259
- type === ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
260
- ) {
261
- resolve();
262
- }
263
-
264
- observer.next(message);
265
- },
266
- complete: () => {
267
- resolve();
268
- },
269
- error: error => {
270
- reject(error);
271
- },
272
- });
273
-
274
- if (syncQuerySubscription) {
275
- subscriptions.push(syncQuerySubscription);
276
- }
277
- });
278
- } catch (error) {
279
- observer.error(error);
280
- return;
281
- }
282
- //#endregion
283
-
284
- //#region process mutations
285
- subscriptions.push(
286
- this.mutationsProcessor
287
- .start()
288
- .subscribe(({ modelDefinition, model: item, hasMore }) => {
289
- const modelConstructor = this.userModelClasses[
290
- modelDefinition.name
291
- ] as PersistentModelConstructor<any>;
292
-
293
- const model = this.modelInstanceCreator(
294
- modelConstructor,
295
- item
296
- );
297
-
298
- this.storage.runExclusive(storage =>
299
- this.modelMerger.merge(storage, model)
231
+ // NOTE: need a way to override this conditional for testing.
232
+ if (isNode) {
233
+ logger.warn(
234
+ 'Realtime disabled when in a server-side environment'
300
235
  );
236
+ } else {
237
+ //#region GraphQL Subscriptions
238
+ [
239
+ // const ctlObservable: Observable<CONTROL_MSG>
240
+ ctlSubsObservable,
241
+ // const dataObservable: Observable<[TransformerMutationType, SchemaModel, Readonly<{
242
+ // id: string;
243
+ // } & Record<string, any>>]>
244
+ dataSubsObservable,
245
+ ] = this.subscriptionsProcessor.start();
246
+
247
+ try {
248
+ await new Promise((resolve, reject) => {
249
+ onTerminate.then(reject);
250
+ const ctlSubsSubscription = ctlSubsObservable.subscribe(
251
+ {
252
+ next: msg => {
253
+ if (msg === CONTROL_MSG.CONNECTED) {
254
+ resolve();
255
+ }
256
+ },
257
+ error: err => {
258
+ reject(err);
259
+ const handleDisconnect =
260
+ this.disconnectionHandler();
261
+ handleDisconnect(err);
262
+ },
263
+ }
264
+ );
265
+
266
+ subscriptions.push(ctlSubsSubscription);
267
+ });
268
+ } catch (err) {
269
+ observer.error(err);
270
+ failedStarting();
271
+ return;
272
+ }
301
273
 
302
- observer.next({
303
- type: ControlMessage.SYNC_ENGINE_OUTBOX_MUTATION_PROCESSED,
304
- data: {
305
- model: modelConstructor,
306
- element: model,
307
- },
308
- });
274
+ logger.log('Realtime ready');
309
275
 
310
276
  observer.next({
311
- type: ControlMessage.SYNC_ENGINE_OUTBOX_STATUS,
312
- data: {
313
- isEmpty: !hasMore,
314
- },
277
+ type: ControlMessage.SYNC_ENGINE_SUBSCRIPTIONS_ESTABLISHED,
315
278
  });
316
- })
317
- );
318
- //#endregion
319
-
320
- //#region Merge subscriptions buffer
321
- // TODO: extract to function
322
- if (!isNode) {
323
- subscriptions.push(
324
- dataSubsObservable.subscribe(
325
- ([_transformerMutationType, modelDefinition, item]) => {
326
- const modelConstructor = this.userModelClasses[
327
- modelDefinition.name
328
- ] as PersistentModelConstructor<any>;
329
-
330
- const model = this.modelInstanceCreator(
331
- modelConstructor,
332
- item
333
- );
334
279
 
335
- this.storage.runExclusive(storage =>
336
- this.modelMerger.merge(storage, model)
337
- );
338
- }
339
- )
340
- );
341
- }
342
- //#endregion
343
- } else if (!online) {
344
- this.online = online;
280
+ //#endregion
281
+ }
345
282
 
346
- observer.next({
347
- type: ControlMessage.SYNC_ENGINE_NETWORK_STATUS,
348
- data: {
349
- active: this.online,
350
- },
351
- });
283
+ //#region Base & Sync queries
284
+ try {
285
+ await new Promise((resolve, reject) => {
286
+ const syncQuerySubscription =
287
+ this.syncQueriesObservable().subscribe({
288
+ next: message => {
289
+ const { type } = message;
290
+
291
+ if (
292
+ type ===
293
+ ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
294
+ ) {
295
+ resolve();
296
+ }
297
+
298
+ observer.next(message);
299
+ },
300
+ complete: () => {
301
+ resolve();
302
+ },
303
+ error: error => {
304
+ reject(error);
305
+ },
306
+ });
307
+
308
+ if (syncQuerySubscription) {
309
+ subscriptions.push(syncQuerySubscription);
310
+ }
311
+ });
312
+ } catch (error) {
313
+ observer.error(error);
314
+ failedStarting();
315
+ return;
316
+ }
317
+ //#endregion
318
+
319
+ //#region process mutations (outbox)
320
+ subscriptions.push(
321
+ this.mutationsProcessor
322
+ .start()
323
+ .subscribe(({ modelDefinition, model: item, hasMore }) =>
324
+ this.runningProcesses.add(async () => {
325
+ const modelConstructor = this.userModelClasses[
326
+ modelDefinition.name
327
+ ] as PersistentModelConstructor<any>;
328
+
329
+ const model = this.modelInstanceCreator(
330
+ modelConstructor,
331
+ item
332
+ );
333
+
334
+ await this.storage.runExclusive(storage =>
335
+ this.modelMerger.merge(
336
+ storage,
337
+ model,
338
+ modelDefinition
339
+ )
340
+ );
341
+
342
+ observer.next({
343
+ type: ControlMessage.SYNC_ENGINE_OUTBOX_MUTATION_PROCESSED,
344
+ data: {
345
+ model: modelConstructor,
346
+ element: model,
347
+ },
348
+ });
349
+
350
+ observer.next({
351
+ type: ControlMessage.SYNC_ENGINE_OUTBOX_STATUS,
352
+ data: {
353
+ isEmpty: !hasMore,
354
+ },
355
+ });
356
+ }, 'mutation processor event')
357
+ )
358
+ );
359
+ //#endregion
360
+
361
+ //#region Merge subscriptions buffer
362
+ // TODO: extract to function
363
+ if (!isNode) {
364
+ subscriptions.push(
365
+ dataSubsObservable.subscribe(
366
+ ([_transformerMutationType, modelDefinition, item]) =>
367
+ this.runningProcesses.add(async () => {
368
+ const modelConstructor = this.userModelClasses[
369
+ modelDefinition.name
370
+ ] as PersistentModelConstructor<any>;
371
+
372
+ const model = this.modelInstanceCreator(
373
+ modelConstructor,
374
+ item
375
+ );
376
+
377
+ await this.storage.runExclusive(storage =>
378
+ this.modelMerger.merge(
379
+ storage,
380
+ model,
381
+ modelDefinition
382
+ )
383
+ );
384
+ }, 'subscription dataSubsObservable event')
385
+ )
386
+ );
387
+ }
388
+ //#endregion
389
+ } else if (!online) {
390
+ this.online = online;
391
+
392
+ observer.next({
393
+ type: ControlMessage.SYNC_ENGINE_NETWORK_STATUS,
394
+ data: {
395
+ active: this.online,
396
+ },
397
+ });
352
398
 
353
- subscriptions.forEach(sub => sub.unsubscribe());
354
- subscriptions = [];
355
- }
399
+ subscriptions.forEach(sub => sub.unsubscribe());
400
+ subscriptions = [];
401
+ }
356
402
 
357
- resolve();
358
- });
403
+ doneStarting();
404
+ }, 'datastore connectivity event')
405
+ );
359
406
  });
360
407
 
361
408
  this.storage
362
409
  .observe(null, null, ownSymbol)
363
410
  .filter(({ model }) => {
364
411
  const modelDefinition = this.getModelDefinition(model);
365
-
366
412
  return modelDefinition.syncable === true;
367
413
  })
368
414
  .subscribe({
369
- next: async ({ opType, model, element, condition }) => {
370
- const namespace =
371
- this.schema.namespaces[this.namespaceResolver(model)];
372
- const MutationEventConstructor = this.modelClasses[
373
- 'MutationEvent'
374
- ] as PersistentModelConstructor<MutationEvent>;
375
- const graphQLCondition = predicateToGraphQLCondition(condition);
376
- const mutationEvent = createMutationInstanceFromModelOperation(
377
- namespace.relationships,
378
- this.getModelDefinition(model),
379
- opType,
380
- model,
381
- element,
382
- graphQLCondition,
383
- MutationEventConstructor,
384
- this.modelInstanceCreator
385
- );
386
-
387
- await this.outbox.enqueue(this.storage, mutationEvent);
388
-
389
- observer.next({
390
- type: ControlMessage.SYNC_ENGINE_OUTBOX_MUTATION_ENQUEUED,
391
- data: {
415
+ next: async ({ opType, model, element, condition }) =>
416
+ this.runningProcesses.add(async () => {
417
+ const namespace =
418
+ this.schema.namespaces[this.namespaceResolver(model)];
419
+ const MutationEventConstructor = this.modelClasses[
420
+ 'MutationEvent'
421
+ ] as PersistentModelConstructor<MutationEvent>;
422
+ const modelDefinition = this.getModelDefinition(model);
423
+ const graphQLCondition = predicateToGraphQLCondition(
424
+ condition,
425
+ modelDefinition
426
+ );
427
+ const mutationEvent = createMutationInstanceFromModelOperation(
428
+ namespace.relationships,
429
+ this.getModelDefinition(model),
430
+ opType,
392
431
  model,
393
432
  element,
394
- },
395
- });
433
+ graphQLCondition,
434
+ MutationEventConstructor,
435
+ this.modelInstanceCreator
436
+ );
396
437
 
397
- observer.next({
398
- type: ControlMessage.SYNC_ENGINE_OUTBOX_STATUS,
399
- data: {
400
- isEmpty: false,
401
- },
402
- });
438
+ await this.outbox.enqueue(this.storage, mutationEvent);
403
439
 
404
- await startPromise;
440
+ observer.next({
441
+ type: ControlMessage.SYNC_ENGINE_OUTBOX_MUTATION_ENQUEUED,
442
+ data: {
443
+ model,
444
+ element,
445
+ },
446
+ });
405
447
 
406
- if (this.online) {
407
- this.mutationsProcessor.resume();
408
- }
409
- },
448
+ observer.next({
449
+ type: ControlMessage.SYNC_ENGINE_OUTBOX_STATUS,
450
+ data: {
451
+ isEmpty: false,
452
+ },
453
+ });
454
+
455
+ await startPromise;
456
+
457
+ if (this.online) {
458
+ this.mutationsProcessor.resume();
459
+ }
460
+ }, 'storage event'),
410
461
  });
411
462
 
412
463
  observer.next({
@@ -427,11 +478,7 @@ export class SyncEngine {
427
478
  observer.next({
428
479
  type: ControlMessage.SYNC_ENGINE_READY,
429
480
  });
430
- })();
431
-
432
- return () => {
433
- subscriptions.forEach(sub => sub.unsubscribe());
434
- };
481
+ }, 'sync start');
435
482
  });
436
483
  }
437
484
 
@@ -439,7 +486,12 @@ export class SyncEngine {
439
486
  currentTimeStamp: number
440
487
  ): Promise<Map<SchemaModel, [string, number]>> {
441
488
  const modelLastSync: Map<SchemaModel, [string, number]> = new Map(
442
- (await this.getModelsMetadata()).map(
489
+ (
490
+ await this.runningProcesses.add(
491
+ () => this.getModelsMetadata(),
492
+ 'sync/index getModelsMetadataWithNextFullSync'
493
+ )
494
+ ).map(
443
495
  ({
444
496
  namespace,
445
497
  model,
@@ -474,220 +526,249 @@ export class SyncEngine {
474
526
 
475
527
  return new Observable<ControlMessageType<ControlMessage>>(observer => {
476
528
  let syncQueriesSubscription: ZenObservable.Subscription;
477
- let waitTimeoutId: ReturnType<typeof setTimeout>;
478
-
479
- (async () => {
480
- while (!observer.closed) {
481
- const count: WeakMap<
482
- PersistentModelConstructor<any>,
483
- {
484
- new: number;
485
- updated: number;
486
- deleted: number;
487
- }
488
- > = new WeakMap();
489
529
 
490
- const modelLastSync = await this.getModelsMetadataWithNextFullSync(
491
- Date.now()
492
- );
493
- const paginatingModels = new Set(modelLastSync.keys());
494
-
495
- let newestFullSyncStartedAt: number;
496
- let theInterval: number;
497
-
498
- let start: number;
499
- let duration: number;
500
- let newestStartedAt: number;
501
- await new Promise(resolve => {
502
- syncQueriesSubscription = this.syncQueriesProcessor
503
- .start(modelLastSync)
504
- .subscribe({
505
- next: async ({
506
- namespace,
507
- modelDefinition,
508
- items,
509
- done,
510
- startedAt,
511
- isFullSync,
512
- }) => {
513
- const modelConstructor = this.userModelClasses[
514
- modelDefinition.name
515
- ] as PersistentModelConstructor<any>;
516
-
517
- if (!count.has(modelConstructor)) {
518
- count.set(modelConstructor, {
519
- new: 0,
520
- updated: 0,
521
- deleted: 0,
522
- });
530
+ this.runningProcesses.isOpen &&
531
+ this.runningProcesses.add(async onTerminate => {
532
+ let terminated = false;
533
+
534
+ while (!observer.closed && !terminated) {
535
+ const count: WeakMap<
536
+ PersistentModelConstructor<any>,
537
+ {
538
+ new: number;
539
+ updated: number;
540
+ deleted: number;
541
+ }
542
+ > = new WeakMap();
543
+
544
+ const modelLastSync = await this.getModelsMetadataWithNextFullSync(
545
+ Date.now()
546
+ );
547
+ const paginatingModels = new Set(modelLastSync.keys());
548
+
549
+ let newestFullSyncStartedAt: number;
550
+ let theInterval: number;
551
+
552
+ let start: number;
553
+ let duration: number;
554
+ let newestStartedAt: number;
555
+ await new Promise((resolve, reject) => {
556
+ if (!this.runningProcesses.isOpen) resolve();
557
+ onTerminate.then(() => resolve());
558
+ syncQueriesSubscription = this.syncQueriesProcessor
559
+ .start(modelLastSync)
560
+ .subscribe({
561
+ next: async ({
562
+ namespace,
563
+ modelDefinition,
564
+ items,
565
+ done,
566
+ startedAt,
567
+ isFullSync,
568
+ }) => {
569
+ const modelConstructor = this.userModelClasses[
570
+ modelDefinition.name
571
+ ] as PersistentModelConstructor<any>;
523
572
 
524
- start = getNow();
525
- newestStartedAt =
526
- newestStartedAt === undefined
527
- ? startedAt
528
- : Math.max(newestStartedAt, startedAt);
529
- }
573
+ if (!count.has(modelConstructor)) {
574
+ count.set(modelConstructor, {
575
+ new: 0,
576
+ updated: 0,
577
+ deleted: 0,
578
+ });
530
579
 
531
- /**
532
- * If there are mutations in the outbox for a given id, those need to be
533
- * merged individually. Otherwise, we can merge them in batches.
534
- */
535
- await this.storage.runExclusive(async storage => {
536
- const idsInOutbox = await this.outbox.getModelIds(storage);
537
-
538
- const oneByOne: ModelInstanceMetadata[] = [];
539
- const page = items.filter(item => {
540
- if (!idsInOutbox.has(item.id)) {
541
- return true;
542
- }
580
+ start = getNow();
581
+ newestStartedAt =
582
+ newestStartedAt === undefined
583
+ ? startedAt
584
+ : Math.max(newestStartedAt, startedAt);
585
+ }
543
586
 
544
- oneByOne.push(item);
545
- return false;
546
- });
587
+ /**
588
+ * If there are mutations in the outbox for a given id, those need to be
589
+ * merged individually. Otherwise, we can merge them in batches.
590
+ */
591
+ await this.storage.runExclusive(async storage => {
592
+ const idsInOutbox = await this.outbox.getModelIds(
593
+ storage
594
+ );
547
595
 
548
- const opTypeCount: [any, OpType][] = [];
596
+ const oneByOne: ModelInstanceMetadata[] = [];
597
+ const page = items.filter(item => {
598
+ const itemId = getIdentifierValue(
599
+ modelDefinition,
600
+ item
601
+ );
549
602
 
550
- for (const item of oneByOne) {
551
- const opType = await this.modelMerger.merge(
552
- storage,
553
- item
554
- );
603
+ if (!idsInOutbox.has(itemId)) {
604
+ return true;
605
+ }
555
606
 
556
- if (opType !== undefined) {
557
- opTypeCount.push([item, opType]);
558
- }
559
- }
607
+ oneByOne.push(item);
608
+ return false;
609
+ });
560
610
 
561
- opTypeCount.push(
562
- ...(await this.modelMerger.mergePage(
563
- storage,
564
- modelConstructor,
565
- page
566
- ))
567
- );
611
+ const opTypeCount: [any, OpType][] = [];
568
612
 
569
- const counts = count.get(modelConstructor);
570
-
571
- opTypeCount.forEach(([, opType]) => {
572
- switch (opType) {
573
- case OpType.INSERT:
574
- counts.new++;
575
- break;
576
- case OpType.UPDATE:
577
- counts.updated++;
578
- break;
579
- case OpType.DELETE:
580
- counts.deleted++;
581
- break;
582
- default:
583
- exhaustiveCheck(opType);
584
- }
585
- });
586
- });
613
+ for (const item of oneByOne) {
614
+ const opType = await this.modelMerger.merge(
615
+ storage,
616
+ item,
617
+ modelDefinition
618
+ );
587
619
 
588
- if (done) {
589
- const { name: modelName } = modelDefinition;
620
+ if (opType !== undefined) {
621
+ opTypeCount.push([item, opType]);
622
+ }
623
+ }
590
624
 
591
- //#region update last sync for type
592
- let modelMetadata = await this.getModelMetadata(
593
- namespace,
594
- modelName
595
- );
625
+ opTypeCount.push(
626
+ ...(await this.modelMerger.mergePage(
627
+ storage,
628
+ modelConstructor,
629
+ page,
630
+ modelDefinition
631
+ ))
632
+ );
596
633
 
597
- const { lastFullSync, fullSyncInterval } = modelMetadata;
598
-
599
- theInterval = fullSyncInterval;
600
-
601
- newestFullSyncStartedAt =
602
- newestFullSyncStartedAt === undefined
603
- ? lastFullSync
604
- : Math.max(
605
- newestFullSyncStartedAt,
606
- isFullSync ? startedAt : lastFullSync
607
- );
608
-
609
- modelMetadata = (
610
- this.modelClasses
611
- .ModelMetadata as PersistentModelConstructor<any>
612
- ).copyOf(modelMetadata, draft => {
613
- draft.lastSync = startedAt;
614
- draft.lastFullSync = isFullSync
615
- ? startedAt
616
- : modelMetadata.lastFullSync;
634
+ const counts = count.get(modelConstructor);
635
+
636
+ opTypeCount.forEach(([, opType]) => {
637
+ switch (opType) {
638
+ case OpType.INSERT:
639
+ counts.new++;
640
+ break;
641
+ case OpType.UPDATE:
642
+ counts.updated++;
643
+ break;
644
+ case OpType.DELETE:
645
+ counts.deleted++;
646
+ break;
647
+ default:
648
+ exhaustiveCheck(opType);
649
+ }
650
+ });
617
651
  });
618
652
 
619
- await this.storage.save(
620
- modelMetadata,
621
- undefined,
622
- ownSymbol
623
- );
624
- //#endregion
653
+ if (done) {
654
+ const { name: modelName } = modelDefinition;
625
655
 
626
- const counts = count.get(modelConstructor);
656
+ //#region update last sync for type
657
+ let modelMetadata = await this.getModelMetadata(
658
+ namespace,
659
+ modelName
660
+ );
627
661
 
628
- this.modelSyncedStatus.set(modelConstructor, true);
662
+ const { lastFullSync, fullSyncInterval } = modelMetadata;
663
+
664
+ theInterval = fullSyncInterval;
665
+
666
+ newestFullSyncStartedAt =
667
+ newestFullSyncStartedAt === undefined
668
+ ? lastFullSync
669
+ : Math.max(
670
+ newestFullSyncStartedAt,
671
+ isFullSync ? startedAt : lastFullSync
672
+ );
673
+
674
+ modelMetadata = (
675
+ this.modelClasses
676
+ .ModelMetadata as PersistentModelConstructor<ModelMetadata>
677
+ ).copyOf(modelMetadata, draft => {
678
+ draft.lastSync = startedAt;
679
+ draft.lastFullSync = isFullSync
680
+ ? startedAt
681
+ : modelMetadata.lastFullSync;
682
+ });
629
683
 
630
- observer.next({
631
- type: ControlMessage.SYNC_ENGINE_MODEL_SYNCED,
632
- data: {
633
- model: modelConstructor,
634
- isFullSync,
635
- isDeltaSync: !isFullSync,
636
- counts,
637
- },
638
- });
684
+ await this.storage.save(
685
+ modelMetadata,
686
+ undefined,
687
+ ownSymbol
688
+ );
689
+ //#endregion
639
690
 
640
- paginatingModels.delete(modelDefinition);
691
+ const counts = count.get(modelConstructor);
692
+
693
+ this.modelSyncedStatus.set(modelConstructor, true);
641
694
 
642
- if (paginatingModels.size === 0) {
643
- duration = getNow() - start;
644
- resolve();
645
695
  observer.next({
646
- type: ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY,
696
+ type: ControlMessage.SYNC_ENGINE_MODEL_SYNCED,
697
+ data: {
698
+ model: modelConstructor,
699
+ isFullSync,
700
+ isDeltaSync: !isFullSync,
701
+ counts,
702
+ },
647
703
  });
648
- syncQueriesSubscription.unsubscribe();
704
+
705
+ paginatingModels.delete(modelDefinition);
706
+
707
+ if (paginatingModels.size === 0) {
708
+ duration = getNow() - start;
709
+ resolve();
710
+ observer.next({
711
+ type: ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY,
712
+ });
713
+ syncQueriesSubscription.unsubscribe();
714
+ }
649
715
  }
650
- }
651
- },
652
- error: error => {
653
- observer.error(error);
716
+ },
717
+ error: error => {
718
+ observer.error(error);
719
+ },
720
+ });
721
+
722
+ observer.next({
723
+ type: ControlMessage.SYNC_ENGINE_SYNC_QUERIES_STARTED,
724
+ data: {
725
+ models: Array.from(paginatingModels).map(({ name }) => name),
654
726
  },
655
727
  });
656
-
657
- observer.next({
658
- type: ControlMessage.SYNC_ENGINE_SYNC_QUERIES_STARTED,
659
- data: {
660
- models: Array.from(paginatingModels).map(({ name }) => name),
661
- },
662
728
  });
663
- });
664
-
665
- const msNextFullSync =
666
- newestFullSyncStartedAt +
667
- theInterval -
668
- (newestStartedAt + duration);
669
-
670
- logger.debug(
671
- `Next fullSync in ${msNextFullSync / 1000} seconds. (${new Date(
672
- Date.now() + msNextFullSync
673
- )})`
674
- );
675
729
 
676
- await new Promise(res => {
677
- waitTimeoutId = setTimeout(res, msNextFullSync);
678
- });
679
- }
680
- })();
730
+ const msNextFullSync =
731
+ newestFullSyncStartedAt +
732
+ theInterval -
733
+ (newestStartedAt + duration);
734
+
735
+ logger.debug(
736
+ `Next fullSync in ${msNextFullSync / 1000} seconds. (${new Date(
737
+ Date.now() + msNextFullSync
738
+ )})`
739
+ );
740
+
741
+ // TODO: create `BackgroundProcessManager.sleep()` ... but, need to put
742
+ // a lot of thought into what that contract looks like to
743
+ // support possible use-cases:
744
+ //
745
+ // 1. non-cancelable
746
+ // 2. cancelable, unsleep on exit()
747
+ // 3. cancelable, throw Error on exit()
748
+ // 4. cancelable, callback first on exit()?
749
+ // 5. ... etc. ? ...
750
+ //
751
+ // TLDR; this is a lot of complexity here for a sleep(),
752
+ // but, it's not clear to me yet how to support an
753
+ // extensible, centralized cancelable `sleep()` elegantly.
754
+ await this.runningProcesses.add(async onTerminate => {
755
+ let sleepTimer;
756
+ let unsleep;
757
+
758
+ const sleep = new Promise(_unsleep => {
759
+ unsleep = _unsleep;
760
+ sleepTimer = setTimeout(unsleep, msNextFullSync);
761
+ });
681
762
 
682
- return () => {
683
- if (syncQueriesSubscription) {
684
- syncQueriesSubscription.unsubscribe();
685
- }
763
+ onTerminate.then(() => {
764
+ terminated = true;
765
+ unsleep();
766
+ });
686
767
 
687
- if (waitTimeoutId) {
688
- clearTimeout(waitTimeoutId);
689
- }
690
- };
768
+ return sleep;
769
+ }, 'syncQueriesObservable sleep');
770
+ }
771
+ }, 'syncQueriesObservable main');
691
772
  });
692
773
  }
693
774
 
@@ -707,9 +788,39 @@ export class SyncEngine {
707
788
  this.datastoreConnectivity.unsubscribe();
708
789
  }
709
790
 
791
+ /**
792
+ * Stops all subscription activities and resolves when all activies report
793
+ * that they're disconnected, done retrying, etc..
794
+ */
795
+ public async stop() {
796
+ logger.debug('stopping sync engine');
797
+
798
+ /**
799
+ * Gracefully disconnecting subscribers first just prevents *more* work
800
+ * from entering the pipelines.
801
+ */
802
+ this.unsubscribeConnectivity();
803
+
804
+ /**
805
+ * aggressively shut down any lingering background processes.
806
+ * some of this might be semi-redundant with unsubscribing. however,
807
+ * unsubscribing doesn't allow us to wait for settling.
808
+ * (Whereas `stop()` does.)
809
+ */
810
+
811
+ await this.mutationsProcessor.stop();
812
+ await this.subscriptionsProcessor.stop();
813
+ await this.datastoreConnectivity.stop();
814
+ await this.syncQueriesProcessor.stop();
815
+ await this.runningProcesses.close();
816
+ await this.runningProcesses.open();
817
+
818
+ logger.debug('sync engine stopped and ready to restart');
819
+ }
820
+
710
821
  private async setupModels(params: StartParams) {
711
822
  const { fullSyncInterval } = params;
712
- const ModelMetadata = this.modelClasses
823
+ const ModelMetadataConstructor = this.modelClasses
713
824
  .ModelMetadata as PersistentModelConstructor<ModelMetadata>;
714
825
 
715
826
  const models: [string, SchemaModel][] = [];
@@ -741,7 +852,7 @@ export class SyncEngine {
741
852
 
742
853
  if (modelMetadata === undefined) {
743
854
  [[savedModel]] = await this.storage.save(
744
- this.modelInstanceCreator(ModelMetadata, {
855
+ this.modelInstanceCreator(ModelMetadataConstructor, {
745
856
  model: model.name,
746
857
  namespace,
747
858
  lastSync: null,
@@ -759,9 +870,7 @@ export class SyncEngine {
759
870
  const syncPredicateUpdated = prevSyncPredicate !== lastSyncPredicate;
760
871
 
761
872
  [[savedModel]] = await this.storage.save(
762
- (
763
- this.modelClasses.ModelMetadata as PersistentModelConstructor<any>
764
- ).copyOf(modelMetadata, draft => {
873
+ ModelMetadataConstructor.copyOf(modelMetadata, draft => {
765
874
  draft.fullSyncInterval = fullSyncInterval;
766
875
  // perform a base sync if the syncPredicate changed in between calls to DataStore.start
767
876
  // ensures that the local store contains all the data specified by the syncExpression