@aws-amplify/datastore 4.1.5 → 4.1.6-lerna-upgrade.4
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.
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +1 -27
- package/lib/storage/adapter/AsyncStorageAdapter.js +0 -133
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +2 -28
- package/lib/storage/adapter/IndexedDBAdapter.js +0 -102
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/StorageAdapterBase.d.ts +3 -15
- package/lib/storage/adapter/StorageAdapterBase.js +77 -117
- package/lib/storage/adapter/StorageAdapterBase.js.map +1 -1
- package/lib/sync/index.d.ts +14 -0
- package/lib/sync/index.js +60 -0
- package/lib/sync/index.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +1 -27
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +1 -134
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +2 -28
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +1 -103
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/StorageAdapterBase.d.ts +3 -15
- package/lib-esm/storage/adapter/StorageAdapterBase.js +79 -119
- package/lib-esm/storage/adapter/StorageAdapterBase.js.map +1 -1
- package/lib-esm/sync/index.d.ts +14 -0
- package/lib-esm/sync/index.js +62 -2
- package/lib-esm/sync/index.js.map +1 -1
- package/package.json +7 -7
- package/src/storage/adapter/AsyncStorageAdapter.ts +0 -162
- package/src/storage/adapter/IndexedDBAdapter.ts +1 -110
- package/src/storage/adapter/StorageAdapterBase.ts +44 -138
- package/src/sync/index.ts +76 -1
- package/CHANGELOG.md +0 -1044
|
@@ -8,20 +8,15 @@ import {
|
|
|
8
8
|
PersistentModelConstructor,
|
|
9
9
|
PredicatesGroup,
|
|
10
10
|
QueryOne,
|
|
11
|
-
RelationType,
|
|
12
11
|
} from '../../types';
|
|
13
12
|
import {
|
|
14
13
|
DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR,
|
|
15
|
-
getIndex,
|
|
16
|
-
getIndexFromAssociation,
|
|
17
14
|
traverseModel,
|
|
18
15
|
validatePredicate,
|
|
19
16
|
inMemoryPagination,
|
|
20
|
-
NAMESPACES,
|
|
21
17
|
keysEqual,
|
|
22
18
|
getStorename,
|
|
23
19
|
getIndexKeys,
|
|
24
|
-
IDENTIFIER_KEY_SEPARATOR,
|
|
25
20
|
} from '../../util';
|
|
26
21
|
import { StorageAdapterBase } from './StorageAdapterBase';
|
|
27
22
|
|
|
@@ -235,163 +230,6 @@ export class AsyncStorageAdapter extends StorageAdapterBase {
|
|
|
235
230
|
}
|
|
236
231
|
}
|
|
237
232
|
|
|
238
|
-
/**
|
|
239
|
-
* Gets related Has One record for `model`
|
|
240
|
-
*
|
|
241
|
-
* @param model
|
|
242
|
-
* @param srcModel
|
|
243
|
-
* @param namespace
|
|
244
|
-
* @param rel
|
|
245
|
-
* @returns
|
|
246
|
-
*/
|
|
247
|
-
protected async getHasOneChild<T extends PersistentModel>(
|
|
248
|
-
model: T,
|
|
249
|
-
srcModel: string,
|
|
250
|
-
namespace: NAMESPACES,
|
|
251
|
-
rel: RelationType
|
|
252
|
-
) {
|
|
253
|
-
let hasOneIndex;
|
|
254
|
-
const { modelName, targetNames, associatedWith } = rel;
|
|
255
|
-
const storeName = getStorename(namespace, modelName);
|
|
256
|
-
const index: string | undefined =
|
|
257
|
-
getIndex(
|
|
258
|
-
this.schema.namespaces[namespace].relationships![modelName]
|
|
259
|
-
.relationTypes,
|
|
260
|
-
srcModel
|
|
261
|
-
) ||
|
|
262
|
-
// if we were unable to find an index via relationTypes
|
|
263
|
-
// i.e. for keyName connections, attempt to find one by the
|
|
264
|
-
// associatedWith property
|
|
265
|
-
getIndexFromAssociation(
|
|
266
|
-
this.schema.namespaces[namespace].relationships![modelName].indexes,
|
|
267
|
-
rel.associatedWith!
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
if (index) {
|
|
271
|
-
hasOneIndex = index.split(IDENTIFIER_KEY_SEPARATOR);
|
|
272
|
-
} else if (associatedWith) {
|
|
273
|
-
if (Array.isArray(associatedWith)) {
|
|
274
|
-
hasOneIndex = associatedWith;
|
|
275
|
-
} else {
|
|
276
|
-
hasOneIndex = [associatedWith];
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// iterate over targetNames array and see if each key is present in model object
|
|
281
|
-
// targetNames here being the keys for the CHILD model
|
|
282
|
-
const hasConnectedModelFields = targetNames!.every(targetName =>
|
|
283
|
-
model.hasOwnProperty(targetName)
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
// PK / Composite key for the parent model
|
|
287
|
-
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
288
|
-
|
|
289
|
-
let values;
|
|
290
|
-
|
|
291
|
-
const isUnidirectionalConnection = hasOneIndex === associatedWith;
|
|
292
|
-
|
|
293
|
-
if (hasConnectedModelFields && isUnidirectionalConnection) {
|
|
294
|
-
// Values will be that of the child model
|
|
295
|
-
values = targetNames!
|
|
296
|
-
.filter(targetName => model[targetName] ?? false)
|
|
297
|
-
.map(targetName => model[targetName]);
|
|
298
|
-
} else {
|
|
299
|
-
// values will be that of the parent model
|
|
300
|
-
values = keyValuesPath.split(DEFAULT_PRIMARY_KEY_VALUE_SEPARATOR);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (values.length === 0) return;
|
|
304
|
-
|
|
305
|
-
const allRecords = await this.db.getAll(storeName);
|
|
306
|
-
|
|
307
|
-
let recordToDelete;
|
|
308
|
-
|
|
309
|
-
// values === targetNames
|
|
310
|
-
if (hasConnectedModelFields) {
|
|
311
|
-
/**
|
|
312
|
-
* Retrieve record by finding the record where all
|
|
313
|
-
* targetNames are present on the connected model.
|
|
314
|
-
*
|
|
315
|
-
*/
|
|
316
|
-
|
|
317
|
-
recordToDelete = allRecords.find(childItem =>
|
|
318
|
-
hasOneIndex.every(index => values.includes(childItem[index]))
|
|
319
|
-
);
|
|
320
|
-
} else {
|
|
321
|
-
// values === keyValuePath
|
|
322
|
-
recordToDelete = allRecords.find(
|
|
323
|
-
childItem => childItem[hasOneIndex] === values
|
|
324
|
-
) as T[];
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return recordToDelete;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Backwards compatability for pre-CPK codegen
|
|
332
|
-
* TODO - deprecate this in v6; will need to re-gen MIPR for older unit
|
|
333
|
-
* tests that hit this path
|
|
334
|
-
*/
|
|
335
|
-
protected async getHasOneChildLegacy<T extends PersistentModel>(
|
|
336
|
-
model: T,
|
|
337
|
-
srcModel: string,
|
|
338
|
-
namespace: NAMESPACES,
|
|
339
|
-
rel: RelationType
|
|
340
|
-
) {
|
|
341
|
-
const { modelName, targetName, associatedWith } = rel;
|
|
342
|
-
const storeName = getStorename(namespace, modelName);
|
|
343
|
-
const index: string | undefined =
|
|
344
|
-
getIndex(
|
|
345
|
-
this.schema.namespaces[namespace].relationships![modelName]
|
|
346
|
-
.relationTypes,
|
|
347
|
-
srcModel
|
|
348
|
-
) ||
|
|
349
|
-
// if we were unable to find an index via relationTypes
|
|
350
|
-
// i.e. for keyName connections, attempt to find one by the
|
|
351
|
-
// associatedWith property
|
|
352
|
-
getIndexFromAssociation(
|
|
353
|
-
this.schema.namespaces[namespace].relationships![modelName].indexes,
|
|
354
|
-
rel.associatedWith!
|
|
355
|
-
);
|
|
356
|
-
const hasOneIndex = index || associatedWith;
|
|
357
|
-
const hasOneCustomField = targetName! in model;
|
|
358
|
-
const keyValuesPath: string = this.getIndexKeyValuesPath(model);
|
|
359
|
-
const value = hasOneCustomField ? model[targetName!] : keyValuesPath;
|
|
360
|
-
|
|
361
|
-
if (!value) return;
|
|
362
|
-
|
|
363
|
-
const allRecords = await this.db.getAll(storeName);
|
|
364
|
-
|
|
365
|
-
const recordToDelete = allRecords.find(
|
|
366
|
-
childItem => childItem[hasOneIndex as string] === value
|
|
367
|
-
) as T;
|
|
368
|
-
|
|
369
|
-
return recordToDelete;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Gets related Has Many records by given `storeName`, `index`, and `keyValues`
|
|
374
|
-
*
|
|
375
|
-
* @param storeName
|
|
376
|
-
* @param index
|
|
377
|
-
* @param keyValues
|
|
378
|
-
* @returns
|
|
379
|
-
*/
|
|
380
|
-
protected async getHasManyChildren<T extends PersistentModel>(
|
|
381
|
-
storeName: string,
|
|
382
|
-
index: string,
|
|
383
|
-
keyValues: string[]
|
|
384
|
-
): Promise<T[]> {
|
|
385
|
-
const allRecords = await this.db.getAll(storeName);
|
|
386
|
-
const indices = index!.split(IDENTIFIER_KEY_SEPARATOR);
|
|
387
|
-
|
|
388
|
-
const childRecords = allRecords.filter(childItem =>
|
|
389
|
-
indices.every(index => keyValues.includes(childItem[index]))
|
|
390
|
-
) as T[];
|
|
391
|
-
|
|
392
|
-
return childRecords;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
233
|
//#region platform-specific helper methods
|
|
396
234
|
|
|
397
235
|
/**
|
|
@@ -12,15 +12,12 @@ import {
|
|
|
12
12
|
PredicateObject,
|
|
13
13
|
PredicatesGroup,
|
|
14
14
|
QueryOne,
|
|
15
|
-
RelationType,
|
|
16
15
|
} from '../../types';
|
|
17
16
|
import {
|
|
18
|
-
getIndex,
|
|
19
17
|
isPrivateMode,
|
|
20
18
|
traverseModel,
|
|
21
19
|
validatePredicate,
|
|
22
20
|
inMemoryPagination,
|
|
23
|
-
NAMESPACES,
|
|
24
21
|
keysEqual,
|
|
25
22
|
getStorename,
|
|
26
23
|
isSafariCompatabilityMode,
|
|
@@ -384,7 +381,7 @@ class IndexedDBAdapter extends StorageAdapterBase {
|
|
|
384
381
|
}
|
|
385
382
|
|
|
386
383
|
protected async deleteItem<T extends PersistentModel>(
|
|
387
|
-
deleteQueue
|
|
384
|
+
deleteQueue: {
|
|
388
385
|
storeName: string;
|
|
389
386
|
items: T[] | IDBValidKey[];
|
|
390
387
|
}[]
|
|
@@ -420,112 +417,6 @@ class IndexedDBAdapter extends StorageAdapterBase {
|
|
|
420
417
|
}
|
|
421
418
|
}
|
|
422
419
|
|
|
423
|
-
/**
|
|
424
|
-
* Gets related Has One record for `model`
|
|
425
|
-
*
|
|
426
|
-
* @param model
|
|
427
|
-
* @param srcModel
|
|
428
|
-
* @param namespace
|
|
429
|
-
* @param rel
|
|
430
|
-
* @returns
|
|
431
|
-
*/
|
|
432
|
-
protected async getHasOneChild<T extends PersistentModel>(
|
|
433
|
-
model: T,
|
|
434
|
-
srcModel: string,
|
|
435
|
-
namespace: NAMESPACES,
|
|
436
|
-
rel: RelationType
|
|
437
|
-
) {
|
|
438
|
-
const hasOneIndex = 'byPk';
|
|
439
|
-
const { modelName, targetNames } = rel;
|
|
440
|
-
const storeName = getStorename(namespace, modelName);
|
|
441
|
-
|
|
442
|
-
const values = targetNames!
|
|
443
|
-
.filter(targetName => model[targetName] ?? false)
|
|
444
|
-
.map(targetName => model[targetName]);
|
|
445
|
-
|
|
446
|
-
if (values.length === 0) return;
|
|
447
|
-
|
|
448
|
-
const recordToDelete = <T>(
|
|
449
|
-
await this.db
|
|
450
|
-
.transaction(storeName, 'readwrite')
|
|
451
|
-
.objectStore(storeName)
|
|
452
|
-
.index(hasOneIndex)
|
|
453
|
-
.get(this.canonicalKeyPath(values))
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
return recordToDelete;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Backwards compatability for pre-CPK codegen
|
|
461
|
-
* TODO - deprecate this in v6; will need to re-gen MIPR for older unit
|
|
462
|
-
* tests that hit this path
|
|
463
|
-
*/
|
|
464
|
-
protected async getHasOneChildLegacy<T extends PersistentModel>(
|
|
465
|
-
model: T,
|
|
466
|
-
srcModel: string,
|
|
467
|
-
namespace: NAMESPACES,
|
|
468
|
-
rel: RelationType
|
|
469
|
-
) {
|
|
470
|
-
const hasOneIndex = 'byPk';
|
|
471
|
-
const { modelName, targetName } = rel;
|
|
472
|
-
const storeName = getStorename(namespace, modelName);
|
|
473
|
-
|
|
474
|
-
let index;
|
|
475
|
-
let values: string[];
|
|
476
|
-
|
|
477
|
-
if (targetName && targetName in model) {
|
|
478
|
-
index = hasOneIndex;
|
|
479
|
-
const value = model[targetName];
|
|
480
|
-
if (value === null) {
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
values = [value];
|
|
484
|
-
} else {
|
|
485
|
-
// backwards compatability for older versions of codegen that did not emit targetName for HAS_ONE relations
|
|
486
|
-
index = getIndex(
|
|
487
|
-
this.schema.namespaces[namespace].relationships![modelName]
|
|
488
|
-
.relationTypes,
|
|
489
|
-
srcModel
|
|
490
|
-
);
|
|
491
|
-
values = this.getIndexKeyValuesFromModel(model);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (!values || !index) return;
|
|
495
|
-
|
|
496
|
-
const recordToDelete = <T>(
|
|
497
|
-
await this.db
|
|
498
|
-
.transaction(storeName, 'readwrite')
|
|
499
|
-
.objectStore(storeName)
|
|
500
|
-
.index(index)
|
|
501
|
-
.get(this.canonicalKeyPath(values))
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
return recordToDelete;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Gets related Has Many records by given `storeName`, `index`, and `keyValues`
|
|
509
|
-
*
|
|
510
|
-
* @param storeName
|
|
511
|
-
* @param index
|
|
512
|
-
* @param keyValues
|
|
513
|
-
* @returns
|
|
514
|
-
*/
|
|
515
|
-
protected async getHasManyChildren<T extends PersistentModel>(
|
|
516
|
-
storeName: string,
|
|
517
|
-
index: string,
|
|
518
|
-
keyValues: string[]
|
|
519
|
-
): Promise<T[]> {
|
|
520
|
-
const childRecords = await this.db
|
|
521
|
-
.transaction(storeName, 'readwrite')
|
|
522
|
-
.objectStore(storeName)
|
|
523
|
-
.index(index as string)
|
|
524
|
-
.getAll(this.canonicalKeyPath(keyValues));
|
|
525
|
-
|
|
526
|
-
return childRecords;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
420
|
//#region platform-specific helper methods
|
|
530
421
|
|
|
531
422
|
private async checkPrivate() {
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
PredicateObject,
|
|
16
16
|
PredicatesGroup,
|
|
17
17
|
QueryOne,
|
|
18
|
-
RelationType,
|
|
19
18
|
} from '../../types';
|
|
20
19
|
import {
|
|
21
20
|
NAMESPACES,
|
|
@@ -24,12 +23,12 @@ import {
|
|
|
24
23
|
extractPrimaryKeyValues,
|
|
25
24
|
traverseModel,
|
|
26
25
|
validatePredicate,
|
|
27
|
-
getIndex,
|
|
28
|
-
getIndexFromAssociation,
|
|
29
26
|
isModelConstructor,
|
|
27
|
+
extractPrimaryKeyFieldNames,
|
|
30
28
|
} from '../../util';
|
|
31
29
|
import type { IDBPDatabase, IDBPObjectStore } from 'idb';
|
|
32
30
|
import type AsyncStorageDatabase from './AsyncStorageDatabase';
|
|
31
|
+
import { ModelRelationship } from '../relationship';
|
|
33
32
|
|
|
34
33
|
const logger = new Logger('DataStore');
|
|
35
34
|
const DB_NAME = 'amplify-datastore';
|
|
@@ -382,17 +381,12 @@ export abstract class StorageAdapterBase implements Adapter {
|
|
|
382
381
|
const modelConstructor =
|
|
383
382
|
modelOrModelConstructor as PersistentModelConstructor<T>;
|
|
384
383
|
const namespace = this.namespaceResolver(modelConstructor) as NAMESPACES;
|
|
385
|
-
|
|
386
384
|
const models = await this.query(modelConstructor, condition);
|
|
387
|
-
const relations =
|
|
388
|
-
this.schema.namespaces![namespace].relationships![modelConstructor.name]
|
|
389
|
-
.relationTypes;
|
|
390
385
|
|
|
391
386
|
if (condition !== undefined) {
|
|
392
387
|
await this.deleteTraverse(
|
|
393
|
-
relations,
|
|
394
388
|
models,
|
|
395
|
-
modelConstructor
|
|
389
|
+
modelConstructor,
|
|
396
390
|
namespace,
|
|
397
391
|
deleteQueue
|
|
398
392
|
);
|
|
@@ -407,9 +401,8 @@ export abstract class StorageAdapterBase implements Adapter {
|
|
|
407
401
|
return [models, deletedModels];
|
|
408
402
|
} else {
|
|
409
403
|
await this.deleteTraverse(
|
|
410
|
-
relations,
|
|
411
404
|
models,
|
|
412
|
-
modelConstructor
|
|
405
|
+
modelConstructor,
|
|
413
406
|
namespace,
|
|
414
407
|
deleteQueue
|
|
415
408
|
);
|
|
@@ -428,6 +421,7 @@ export abstract class StorageAdapterBase implements Adapter {
|
|
|
428
421
|
|
|
429
422
|
const modelConstructor = Object.getPrototypeOf(model)
|
|
430
423
|
.constructor as PersistentModelConstructor<T>;
|
|
424
|
+
|
|
431
425
|
const namespaceName = this.namespaceResolver(
|
|
432
426
|
modelConstructor
|
|
433
427
|
) as NAMESPACES;
|
|
@@ -457,28 +451,16 @@ export abstract class StorageAdapterBase implements Adapter {
|
|
|
457
451
|
throw new Error(msg);
|
|
458
452
|
}
|
|
459
453
|
|
|
460
|
-
const relations =
|
|
461
|
-
this.schema.namespaces[namespaceName].relationships![
|
|
462
|
-
modelConstructor.name
|
|
463
|
-
].relationTypes;
|
|
464
|
-
|
|
465
454
|
await this.deleteTraverse(
|
|
466
|
-
relations,
|
|
467
455
|
[model],
|
|
468
|
-
modelConstructor
|
|
456
|
+
modelConstructor,
|
|
469
457
|
namespaceName,
|
|
470
458
|
deleteQueue
|
|
471
459
|
);
|
|
472
460
|
} else {
|
|
473
|
-
const relations =
|
|
474
|
-
this.schema.namespaces[namespaceName].relationships![
|
|
475
|
-
modelConstructor.name
|
|
476
|
-
].relationTypes;
|
|
477
|
-
|
|
478
461
|
await this.deleteTraverse(
|
|
479
|
-
relations,
|
|
480
462
|
[model],
|
|
481
|
-
modelConstructor
|
|
463
|
+
modelConstructor,
|
|
482
464
|
namespaceName,
|
|
483
465
|
deleteQueue
|
|
484
466
|
);
|
|
@@ -501,139 +483,63 @@ export abstract class StorageAdapterBase implements Adapter {
|
|
|
501
483
|
}[]
|
|
502
484
|
);
|
|
503
485
|
|
|
504
|
-
protected abstract getHasOneChild<T extends PersistentModel>(
|
|
505
|
-
model: T,
|
|
506
|
-
srcModel: string,
|
|
507
|
-
namespace: NAMESPACES,
|
|
508
|
-
rel: RelationType
|
|
509
|
-
): Promise<T | undefined>;
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Backwards compatability for pre-CPK codegen
|
|
513
|
-
* TODO - deprecate this in v6; will need to re-gen MIPR for older unit
|
|
514
|
-
* tests that hit this path
|
|
515
|
-
*/
|
|
516
|
-
protected abstract getHasOneChildLegacy<T extends PersistentModel>(
|
|
517
|
-
model: T,
|
|
518
|
-
srcModel: string,
|
|
519
|
-
namespace: NAMESPACES,
|
|
520
|
-
rel: RelationType
|
|
521
|
-
): Promise<T | undefined>;
|
|
522
|
-
|
|
523
|
-
protected abstract getHasManyChildren<T extends PersistentModel>(
|
|
524
|
-
storeName: string,
|
|
525
|
-
index: string,
|
|
526
|
-
keyValues: string[]
|
|
527
|
-
): Promise<T[] | undefined>;
|
|
528
|
-
|
|
529
486
|
/**
|
|
530
487
|
* Recursively traverse relationship graph and add
|
|
531
488
|
* all Has One and Has Many relations to `deleteQueue` param
|
|
532
489
|
*
|
|
533
490
|
* Actual deletion of records added to `deleteQueue` occurs in the `delete` method
|
|
534
491
|
*
|
|
535
|
-
* @param relations
|
|
536
492
|
* @param models
|
|
537
|
-
* @param
|
|
493
|
+
* @param modelConstructor
|
|
538
494
|
* @param namespace
|
|
539
495
|
* @param deleteQueue
|
|
540
496
|
*/
|
|
541
|
-
|
|
542
|
-
relations: RelationType[],
|
|
497
|
+
private async deleteTraverse<T extends PersistentModel>(
|
|
543
498
|
models: T[],
|
|
544
|
-
|
|
499
|
+
modelConstructor: PersistentModelConstructor<T>,
|
|
545
500
|
namespace: NAMESPACES,
|
|
546
501
|
deleteQueue: { storeName: string; items: T[] }[]
|
|
547
502
|
): Promise<void> {
|
|
548
|
-
|
|
549
|
-
const { modelName, relationType, targetNames, associatedWith } = rel;
|
|
550
|
-
|
|
551
|
-
const storeName = getStorename(namespace, modelName);
|
|
552
|
-
const index: string =
|
|
553
|
-
getIndex(
|
|
554
|
-
this.schema.namespaces[namespace].relationships![modelName]
|
|
555
|
-
.relationTypes,
|
|
556
|
-
srcModel
|
|
557
|
-
) ||
|
|
558
|
-
// if we were unable to find an index via relationTypes
|
|
559
|
-
// i.e. for keyName connections, attempt to find one by the
|
|
560
|
-
// associatedWith property
|
|
561
|
-
getIndexFromAssociation(
|
|
562
|
-
this.schema.namespaces[namespace].relationships![modelName].indexes,
|
|
563
|
-
associatedWith!
|
|
564
|
-
)!;
|
|
565
|
-
|
|
566
|
-
for await (const model of models) {
|
|
567
|
-
const childRecords: PersistentModel[] = [];
|
|
568
|
-
|
|
569
|
-
switch (relationType) {
|
|
570
|
-
case 'HAS_ONE':
|
|
571
|
-
let childRecord;
|
|
572
|
-
if (targetNames?.length) {
|
|
573
|
-
childRecord = await this.getHasOneChild(
|
|
574
|
-
model,
|
|
575
|
-
srcModel,
|
|
576
|
-
namespace,
|
|
577
|
-
rel
|
|
578
|
-
);
|
|
579
|
-
} else {
|
|
580
|
-
childRecord = await this.getHasOneChildLegacy(
|
|
581
|
-
model,
|
|
582
|
-
srcModel,
|
|
583
|
-
namespace,
|
|
584
|
-
rel
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
if (childRecord) {
|
|
589
|
-
childRecords.push(childRecord);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
break;
|
|
593
|
-
case 'HAS_MANY':
|
|
594
|
-
const keyValues: string[] = this.getIndexKeyValuesFromModel(model);
|
|
595
|
-
|
|
596
|
-
const records = await this.getHasManyChildren(
|
|
597
|
-
storeName,
|
|
598
|
-
index,
|
|
599
|
-
keyValues
|
|
600
|
-
);
|
|
601
|
-
|
|
602
|
-
if (records?.length) {
|
|
603
|
-
childRecords.push(...records);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
break;
|
|
607
|
-
case 'BELONGS_TO':
|
|
608
|
-
// Intentionally blank
|
|
609
|
-
break;
|
|
610
|
-
default:
|
|
611
|
-
throw new Error(`Invalid relation type ${relationType}`);
|
|
612
|
-
}
|
|
503
|
+
const cascadingRelationTypes = ['HAS_ONE', 'HAS_MANY'];
|
|
613
504
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
505
|
+
for await (const model of models) {
|
|
506
|
+
const modelDefinition =
|
|
507
|
+
this.schema.namespaces[namespace].models[modelConstructor.name];
|
|
617
508
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
)
|
|
509
|
+
const modelMeta = {
|
|
510
|
+
builder: modelConstructor,
|
|
511
|
+
schema: modelDefinition,
|
|
512
|
+
pkField: extractPrimaryKeyFieldNames(modelDefinition),
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const relationships = ModelRelationship.allFrom(modelMeta).filter(r =>
|
|
516
|
+
cascadingRelationTypes.includes(r.type)
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
for await (const r of relationships) {
|
|
520
|
+
const queryObject = r.createRemoteQueryObject(model);
|
|
521
|
+
if (queryObject !== null) {
|
|
522
|
+
const relatedRecords = await this.query(
|
|
523
|
+
r.remoteModelConstructor,
|
|
524
|
+
ModelPredicateCreator.createFromFlatEqualities(
|
|
525
|
+
r.remoteDefinition!,
|
|
526
|
+
queryObject
|
|
527
|
+
)
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
await this.deleteTraverse(
|
|
531
|
+
relatedRecords,
|
|
532
|
+
r.remoteModelConstructor,
|
|
533
|
+
namespace,
|
|
534
|
+
deleteQueue
|
|
535
|
+
);
|
|
536
|
+
}
|
|
626
537
|
}
|
|
627
538
|
}
|
|
628
539
|
|
|
629
540
|
deleteQueue.push({
|
|
630
|
-
storeName: getStorename(namespace,
|
|
631
|
-
items: models
|
|
632
|
-
this.modelInstanceCreator(
|
|
633
|
-
this.getModelConstructorByModelName!(namespace, srcModel),
|
|
634
|
-
record
|
|
635
|
-
)
|
|
636
|
-
),
|
|
541
|
+
storeName: getStorename(namespace, modelConstructor.name),
|
|
542
|
+
items: models,
|
|
637
543
|
});
|
|
638
544
|
}
|
|
639
545
|
}
|
package/src/sync/index.ts
CHANGED
|
@@ -2,8 +2,13 @@ import {
|
|
|
2
2
|
browserOrNode,
|
|
3
3
|
ConsoleLogger as Logger,
|
|
4
4
|
BackgroundProcessManager,
|
|
5
|
+
Hub,
|
|
5
6
|
} from '@aws-amplify/core';
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CONTROL_MSG as PUBSUB_CONTROL_MSG,
|
|
9
|
+
CONNECTION_STATE_CHANGE as PUBSUB_CONNECTION_STATE_CHANGE,
|
|
10
|
+
ConnectionState,
|
|
11
|
+
} from '@aws-amplify/pubsub';
|
|
7
12
|
import Observable, { ZenObservable } from 'zen-observable-ts';
|
|
8
13
|
import { ModelInstanceCreator } from '../datastore/datastore';
|
|
9
14
|
import { ModelPredicateCreator } from '../predicates';
|
|
@@ -116,6 +121,13 @@ export class SyncEngine {
|
|
|
116
121
|
PersistentModelConstructor<any>,
|
|
117
122
|
boolean
|
|
118
123
|
> = new WeakMap();
|
|
124
|
+
private unsleepSyncQueriesObservable: (() => void) | null;
|
|
125
|
+
private waitForSleepState: Promise<void>;
|
|
126
|
+
private syncQueriesObservableStartSleeping: (
|
|
127
|
+
value?: void | PromiseLike<void>
|
|
128
|
+
) => void;
|
|
129
|
+
private stopDisruptionListener: () => void;
|
|
130
|
+
private connectionDisrupted = false;
|
|
119
131
|
|
|
120
132
|
private runningProcesses: BackgroundProcessManager;
|
|
121
133
|
|
|
@@ -144,6 +156,9 @@ export class SyncEngine {
|
|
|
144
156
|
private readonly connectivityMonitor?: DataStoreConnectivity
|
|
145
157
|
) {
|
|
146
158
|
this.runningProcesses = new BackgroundProcessManager();
|
|
159
|
+
this.waitForSleepState = new Promise(resolve => {
|
|
160
|
+
this.syncQueriesObservableStartSleeping = resolve;
|
|
161
|
+
});
|
|
147
162
|
|
|
148
163
|
const MutationEvent = this.modelClasses[
|
|
149
164
|
'MutationEvent'
|
|
@@ -237,6 +252,8 @@ export class SyncEngine {
|
|
|
237
252
|
'Realtime disabled when in a server-side environment'
|
|
238
253
|
);
|
|
239
254
|
} else {
|
|
255
|
+
this.stopDisruptionListener =
|
|
256
|
+
this.startDisruptionListener();
|
|
240
257
|
//#region GraphQL Subscriptions
|
|
241
258
|
[ctlSubsObservable, dataSubsObservable] =
|
|
242
259
|
this.subscriptionsProcessor.start();
|
|
@@ -769,11 +786,19 @@ export class SyncEngine {
|
|
|
769
786
|
|
|
770
787
|
onTerminate.then(() => {
|
|
771
788
|
terminated = true;
|
|
789
|
+
this.syncQueriesObservableStartSleeping();
|
|
772
790
|
unsleep();
|
|
773
791
|
});
|
|
774
792
|
|
|
793
|
+
this.unsleepSyncQueriesObservable = unsleep;
|
|
794
|
+
this.syncQueriesObservableStartSleeping();
|
|
775
795
|
return sleep;
|
|
776
796
|
}, 'syncQueriesObservable sleep');
|
|
797
|
+
|
|
798
|
+
this.unsleepSyncQueriesObservable = null;
|
|
799
|
+
this.waitForSleepState = new Promise(resolve => {
|
|
800
|
+
this.syncQueriesObservableStartSleeping = resolve;
|
|
801
|
+
});
|
|
777
802
|
}
|
|
778
803
|
}, 'syncQueriesObservable main');
|
|
779
804
|
});
|
|
@@ -808,6 +833,11 @@ export class SyncEngine {
|
|
|
808
833
|
*/
|
|
809
834
|
this.unsubscribeConnectivity();
|
|
810
835
|
|
|
836
|
+
/**
|
|
837
|
+
* Stop listening for websocket connection disruption
|
|
838
|
+
*/
|
|
839
|
+
this.stopDisruptionListener && this.stopDisruptionListener();
|
|
840
|
+
|
|
811
841
|
/**
|
|
812
842
|
* aggressively shut down any lingering background processes.
|
|
813
843
|
* some of this might be semi-redundant with unsubscribing. however,
|
|
@@ -1053,4 +1083,49 @@ export class SyncEngine {
|
|
|
1053
1083
|
};
|
|
1054
1084
|
return namespace;
|
|
1055
1085
|
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* listen for websocket connection disruption
|
|
1089
|
+
*
|
|
1090
|
+
* May indicate there was a period of time where messages
|
|
1091
|
+
* from AppSync were missed. A sync needs to be triggered to
|
|
1092
|
+
* retrieve the missed data.
|
|
1093
|
+
*/
|
|
1094
|
+
private startDisruptionListener() {
|
|
1095
|
+
return Hub.listen('api', (data: any) => {
|
|
1096
|
+
if (
|
|
1097
|
+
data.source === 'PubSub' &&
|
|
1098
|
+
data.payload.event === PUBSUB_CONNECTION_STATE_CHANGE
|
|
1099
|
+
) {
|
|
1100
|
+
const connectionState = data.payload.data
|
|
1101
|
+
.connectionState as ConnectionState;
|
|
1102
|
+
|
|
1103
|
+
switch (connectionState) {
|
|
1104
|
+
// Do not need to listen for ConnectionDisruptedPendingNetwork
|
|
1105
|
+
// Normal network reconnection logic will handle the sync
|
|
1106
|
+
case ConnectionState.ConnectionDisrupted:
|
|
1107
|
+
this.connectionDisrupted = true;
|
|
1108
|
+
break;
|
|
1109
|
+
|
|
1110
|
+
case ConnectionState.Connected:
|
|
1111
|
+
if (this.connectionDisrupted) {
|
|
1112
|
+
this.scheduleSync();
|
|
1113
|
+
}
|
|
1114
|
+
this.connectionDisrupted = false;
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/*
|
|
1122
|
+
* Schedule a sync to start when syncQueriesObservable enters sleep state
|
|
1123
|
+
* Start sync immediately if syncQueriesObservable is already in sleep state
|
|
1124
|
+
*/
|
|
1125
|
+
private scheduleSync(): Promise<void> {
|
|
1126
|
+
return this.waitForSleepState.then(() => {
|
|
1127
|
+
// unsleepSyncQueriesObservable will be set if waitForSleepState has resolved
|
|
1128
|
+
this.unsleepSyncQueriesObservable!();
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1056
1131
|
}
|