@contember/bindx 0.1.51 → 0.1.53
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/dist/core/ActionDispatcher.d.ts.map +1 -1
- package/dist/core/EntityLoader.d.ts +7 -0
- package/dist/core/EntityLoader.d.ts.map +1 -1
- package/dist/core/actions.d.ts +1 -2
- package/dist/core/actions.d.ts.map +1 -1
- package/dist/dataview/types.d.ts +6 -6
- package/dist/dataview/types.d.ts.map +1 -1
- package/dist/handles/EntityHandle.d.ts.map +1 -1
- package/dist/handles/HasManyListHandle.d.ts +8 -0
- package/dist/handles/HasManyListHandle.d.ts.map +1 -1
- package/dist/handles/HasOneHandle.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/persistence/BatchPersister.d.ts.map +1 -1
- package/dist/store/HasManyStore.d.ts +94 -78
- package/dist/store/HasManyStore.d.ts.map +1 -1
- package/dist/store/RelationStore.d.ts +21 -12
- package/dist/store/RelationStore.d.ts.map +1 -1
- package/dist/store/ResponseOccurrenceIndex.d.ts +65 -0
- package/dist/store/ResponseOccurrenceIndex.d.ts.map +1 -0
- package/dist/store/SnapshotStore.d.ts +53 -15
- package/dist/store/SnapshotStore.d.ts.map +1 -1
- package/dist/store/SubscriptionManager.d.ts +19 -4
- package/dist/store/SubscriptionManager.d.ts.map +1 -1
- package/dist/store/hasManyState.d.ts +164 -0
- package/dist/store/hasManyState.d.ts.map +1 -0
- package/dist/store/relationKey.d.ts +11 -0
- package/dist/store/relationKey.d.ts.map +1 -1
- package/dist/undo/UndoJournal.d.ts.map +1 -1
- package/dist/undo/rekeyJournalEntry.d.ts +4 -4
- package/dist/undo/rekeyJournalEntry.d.ts.map +1 -1
- package/dist/utils/aliasGenerator.d.ts +1 -1
- package/dist/utils/aliasGenerator.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/core/ActionDispatcher.ts +0 -1
- package/src/core/EntityLoader.ts +13 -6
- package/src/core/actions.ts +1 -3
- package/src/dataview/types.ts +6 -6
- package/src/handles/EntityHandle.ts +11 -0
- package/src/handles/HasManyListHandle.ts +15 -17
- package/src/handles/HasOneHandle.ts +4 -2
- package/src/index.ts +6 -0
- package/src/persistence/BatchPersister.ts +15 -11
- package/src/store/HasManyStore.ts +312 -488
- package/src/store/RelationStore.ts +60 -24
- package/src/store/ResponseOccurrenceIndex.ts +224 -0
- package/src/store/SnapshotStore.ts +183 -109
- package/src/store/SubscriptionManager.ts +116 -58
- package/src/store/hasManyState.ts +408 -0
- package/src/store/relationKey.ts +17 -0
- package/src/undo/UndoJournal.ts +3 -1
- package/src/undo/UndoManager.ts +1 -1
- package/src/undo/rekeyJournalEntry.ts +31 -43
- package/src/utils/aliasGenerator.ts +1 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { EntitySnapshot, LoadStatus } from './snapshots.js'
|
|
2
2
|
import { createEntitySnapshot } from './snapshots.js'
|
|
3
3
|
import type { FieldError, FieldErrorFilter } from '../errors/types.js'
|
|
4
|
-
import {
|
|
4
|
+
import type { SelectionMeta } from '../selection/types.js'
|
|
5
|
+
import { SubscriptionManager, type SnapshotVersionBumper, type SynchronousResult } from './SubscriptionManager.js'
|
|
5
6
|
import { ErrorStore } from './ErrorStore.js'
|
|
6
7
|
import {
|
|
7
8
|
RelationStore,
|
|
9
|
+
type HasManyRelationProjection,
|
|
8
10
|
type HasManyRemovalType,
|
|
11
|
+
type HasManyViewMembership,
|
|
12
|
+
type HasManyViewProjection,
|
|
9
13
|
type RelationReconciliationResult,
|
|
10
14
|
type SentHasManyDelta,
|
|
11
15
|
type SentHasOneTransition,
|
|
@@ -18,6 +22,7 @@ import { generateTempId } from './entityId.js'
|
|
|
18
22
|
import { DirtyTracker } from './DirtyTracker.js'
|
|
19
23
|
import { EntitySnapshotStore } from './EntitySnapshotStore.js'
|
|
20
24
|
import { RootRegistry } from './RootRegistry.js'
|
|
25
|
+
import { ResponseOccurrenceIndex, type RelationTargetResolver } from './ResponseOccurrenceIndex.js'
|
|
21
26
|
import { ReachabilityAnalyzer } from './ReachabilityAnalyzer.js'
|
|
22
27
|
import { RekeyOrchestrator } from './RekeyOrchestrator.js'
|
|
23
28
|
import type { RekeyContext, Rekeyable } from './RekeyOrchestrator.js'
|
|
@@ -32,7 +37,13 @@ import type {
|
|
|
32
37
|
} from '../undo/UndoJournal.js'
|
|
33
38
|
|
|
34
39
|
export type {
|
|
40
|
+
HasManyAdditionKind,
|
|
41
|
+
HasManyRelationProjection,
|
|
35
42
|
HasManyRemovalType,
|
|
43
|
+
HasManyView,
|
|
44
|
+
HasManyViewMembership,
|
|
45
|
+
HasManyViewProjection,
|
|
46
|
+
PlannedHasManyAddition,
|
|
36
47
|
RelationReconciliationResult,
|
|
37
48
|
SentHasManyAddition,
|
|
38
49
|
SentHasManyDelta,
|
|
@@ -83,6 +94,8 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
83
94
|
*/
|
|
84
95
|
private readonly lastPropagatedData = new Map<string, unknown>()
|
|
85
96
|
|
|
97
|
+
private readonly responseOccurrences = new ResponseOccurrenceIndex()
|
|
98
|
+
|
|
86
99
|
/**
|
|
87
100
|
* Optional write-journal. When set (by an attached UndoManager), mutating
|
|
88
101
|
* methods record editable-layer pre-images of the cells they touch so a gesture
|
|
@@ -176,6 +189,11 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
176
189
|
}
|
|
177
190
|
}
|
|
178
191
|
|
|
192
|
+
/** Coalesces synchronous notifications, even on failure, without changing undo boundaries. */
|
|
193
|
+
batchNotifications<T>(fn: () => SynchronousResult<T>): T {
|
|
194
|
+
return this.subscriptions.batchNotifications(fn)
|
|
195
|
+
}
|
|
196
|
+
|
|
179
197
|
// ==================== Key Generation ====================
|
|
180
198
|
|
|
181
199
|
private getEntityKey(entityType: string, id: string): string {
|
|
@@ -304,6 +322,23 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
304
322
|
return newSnapshot as EntitySnapshot<T>
|
|
305
323
|
}
|
|
306
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Unites the occurrences of each entity within one server response, so the
|
|
327
|
+
* narrower of two occurrences cannot replace fields the wider one carried.
|
|
328
|
+
* Call it before any of the response is written. See {@link ResponseOccurrenceIndex}.
|
|
329
|
+
*/
|
|
330
|
+
indexServerResponse(entityType: string, data: unknown, selection: SelectionMeta, schema: RelationTargetResolver): void {
|
|
331
|
+
this.responseOccurrences.index(entityType, data, selection, schema)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* What to write for an entity occurrence of a server response: the union of
|
|
336
|
+
* its occurrences in that response, or the occurrence itself.
|
|
337
|
+
*/
|
|
338
|
+
resolveServerOccurrence(occurrence: Record<string, unknown>): Record<string, unknown> {
|
|
339
|
+
return this.responseOccurrences.resolve(occurrence)
|
|
340
|
+
}
|
|
341
|
+
|
|
307
342
|
/**
|
|
308
343
|
* Refreshes server data from a revalidation read while preserving the user's
|
|
309
344
|
* local dirty edits. See {@link EntitySnapshotStore.refreshServerData}.
|
|
@@ -525,25 +560,74 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
525
560
|
|
|
526
561
|
// ==================== Has-Many State (delegated to RelationStore) ====================
|
|
527
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Declares what a view read under {@link alias} can leave out, so the store can tell
|
|
565
|
+
* a filtered view from one that merely orders or renames. Only the caller that
|
|
566
|
+
* selected the relation knows the params; the alias is a hash of them.
|
|
567
|
+
*/
|
|
568
|
+
declareHasManyViewMembership(
|
|
569
|
+
entityType: string,
|
|
570
|
+
fieldName: string,
|
|
571
|
+
alias: string,
|
|
572
|
+
membership: HasManyViewMembership,
|
|
573
|
+
): void {
|
|
574
|
+
this.relations.declareHasManyViewMembership(entityType, fieldName, alias, membership)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Materializes the relation and one args-view of it, refreshing that view's
|
|
579
|
+
* server baseline when one is supplied.
|
|
580
|
+
*
|
|
581
|
+
* `alias` addresses a VIEW inside the relation state, never a key of its own:
|
|
582
|
+
* the state is always keyed by the schema field name, so everything that names
|
|
583
|
+
* the relation the way the schema and the server do finds it.
|
|
584
|
+
*/
|
|
528
585
|
getOrCreateHasMany(
|
|
529
586
|
parentType: string,
|
|
530
587
|
parentId: string,
|
|
531
588
|
fieldName: string,
|
|
532
589
|
serverIds?: string[],
|
|
533
590
|
alias?: string,
|
|
534
|
-
):
|
|
535
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
536
|
-
|
|
591
|
+
): void {
|
|
592
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
593
|
+
this.relations.getOrCreateHasMany(key, alias ?? fieldName, serverIds)
|
|
537
594
|
}
|
|
538
595
|
|
|
596
|
+
/** The relation as persistence and dirty tracking see it — server baseline unioned across views. */
|
|
539
597
|
getHasMany(
|
|
540
598
|
parentType: string,
|
|
541
599
|
parentId: string,
|
|
542
600
|
fieldName: string,
|
|
601
|
+
): HasManyRelationProjection | undefined {
|
|
602
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
603
|
+
return this.relations.getHasManyRelation(key)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Whether the relation carries writes the next persist would send. */
|
|
607
|
+
hasPendingHasManyWrites(parentType: string, parentId: string, fieldName: string): boolean {
|
|
608
|
+
return this.relations.hasPendingHasManyWrites(this.getRelationKey(parentType, parentId, fieldName))
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** Whether one view carries an order its user arranged rather than the default one. */
|
|
612
|
+
hasExplicitHasManyOrder(
|
|
613
|
+
parentType: string,
|
|
614
|
+
parentId: string,
|
|
615
|
+
fieldName: string,
|
|
543
616
|
alias?: string,
|
|
544
|
-
):
|
|
545
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
546
|
-
return this.relations.
|
|
617
|
+
): boolean {
|
|
618
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
619
|
+
return this.relations.hasExplicitHasManyOrder(key, alias ?? fieldName)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** The relation as ONE mounted has-many handle sees it. */
|
|
623
|
+
getHasManyView(
|
|
624
|
+
parentType: string,
|
|
625
|
+
parentId: string,
|
|
626
|
+
fieldName: string,
|
|
627
|
+
alias?: string,
|
|
628
|
+
): HasManyViewProjection {
|
|
629
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
630
|
+
return this.relations.getHasManyView(key, alias ?? fieldName)
|
|
547
631
|
}
|
|
548
632
|
|
|
549
633
|
setHasManyServerIds(
|
|
@@ -553,8 +637,8 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
553
637
|
serverIds: string[],
|
|
554
638
|
alias?: string,
|
|
555
639
|
): void {
|
|
556
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
557
|
-
this.relations.setHasManyServerIds(key, serverIds)
|
|
640
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
641
|
+
this.relations.setHasManyServerIds(key, alias ?? fieldName, serverIds)
|
|
558
642
|
this.notifyRelationSubscribers(key)
|
|
559
643
|
}
|
|
560
644
|
|
|
@@ -564,9 +648,8 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
564
648
|
fieldName: string,
|
|
565
649
|
itemId: string,
|
|
566
650
|
type: HasManyRemovalType,
|
|
567
|
-
alias?: string,
|
|
568
651
|
): void {
|
|
569
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
652
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
570
653
|
this.journal?.recordHasMany(key)
|
|
571
654
|
this.relations.planHasManyRemoval(key, itemId, type)
|
|
572
655
|
this.notifyRelationSubscribers(key)
|
|
@@ -576,10 +659,9 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
576
659
|
parentType: string,
|
|
577
660
|
parentId: string,
|
|
578
661
|
fieldName: string,
|
|
579
|
-
alias?: string,
|
|
580
662
|
): Map<string, HasManyRemovalType> | undefined {
|
|
581
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
582
|
-
return this.relations.
|
|
663
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
664
|
+
return this.relations.getHasManyRelation(key)?.plannedRemovals
|
|
583
665
|
}
|
|
584
666
|
|
|
585
667
|
/**
|
|
@@ -598,34 +680,22 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
598
680
|
itemId: string,
|
|
599
681
|
alias?: string,
|
|
600
682
|
): void {
|
|
601
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
683
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
602
684
|
this.journal?.recordHasMany(key)
|
|
603
|
-
this.relations.planHasManyConnection(key, itemId)
|
|
685
|
+
this.relations.planHasManyConnection(key, alias ?? fieldName, itemId)
|
|
604
686
|
this.notifyRelationSubscribers(key)
|
|
605
687
|
}
|
|
606
688
|
|
|
689
|
+
/** The connections the next persist will send — relation-level, not per view. */
|
|
607
690
|
getHasManyPlannedConnections(
|
|
608
691
|
parentType: string,
|
|
609
692
|
parentId: string,
|
|
610
693
|
fieldName: string,
|
|
611
|
-
alias?: string,
|
|
612
694
|
): Set<string> | undefined {
|
|
613
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
614
|
-
const
|
|
615
|
-
if (!
|
|
616
|
-
return new Set(
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
commitHasMany(
|
|
620
|
-
parentType: string,
|
|
621
|
-
parentId: string,
|
|
622
|
-
fieldName: string,
|
|
623
|
-
newServerIds: string[],
|
|
624
|
-
alias?: string,
|
|
625
|
-
): void {
|
|
626
|
-
const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
|
|
627
|
-
this.relations.commitHasMany(key, newServerIds)
|
|
628
|
-
this.notifyRelationSubscribers(key)
|
|
695
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
696
|
+
const relation = this.relations.getHasManyRelation(key)
|
|
697
|
+
if (!relation) return undefined
|
|
698
|
+
return new Set(relation.plannedAdditions.keys())
|
|
629
699
|
}
|
|
630
700
|
|
|
631
701
|
reconcileSentHasMany(
|
|
@@ -633,9 +703,8 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
633
703
|
parentId: string,
|
|
634
704
|
fieldName: string,
|
|
635
705
|
delta: SentHasManyDelta,
|
|
636
|
-
alias?: string,
|
|
637
706
|
): RelationReconciliationResult {
|
|
638
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
707
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
639
708
|
const result = this.relations.reconcileSentHasMany(key, delta)
|
|
640
709
|
this.notifyRelationSubscribers(key)
|
|
641
710
|
return result
|
|
@@ -645,9 +714,8 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
645
714
|
parentType: string,
|
|
646
715
|
parentId: string,
|
|
647
716
|
fieldName: string,
|
|
648
|
-
alias?: string,
|
|
649
717
|
): void {
|
|
650
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
718
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
651
719
|
this.journal?.recordHasMany(key)
|
|
652
720
|
this.relations.resetHasMany(key)
|
|
653
721
|
this.notifyRelationSubscribers(key)
|
|
@@ -660,9 +728,9 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
660
728
|
itemId: string,
|
|
661
729
|
alias?: string,
|
|
662
730
|
): void {
|
|
663
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
731
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
664
732
|
this.journal?.recordHasMany(key)
|
|
665
|
-
this.relations.addToHasMany(key, itemId)
|
|
733
|
+
this.relations.addToHasMany(key, alias ?? fieldName, itemId)
|
|
666
734
|
this.notifyRelationSubscribers(key)
|
|
667
735
|
}
|
|
668
736
|
|
|
@@ -674,7 +742,7 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
674
742
|
): void {
|
|
675
743
|
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
676
744
|
this.journal?.recordHasMany(key)
|
|
677
|
-
this.relations.connectExistingToHasMany(key, itemId)
|
|
745
|
+
this.relations.connectExistingToHasMany(key, fieldName, itemId)
|
|
678
746
|
this.notifyRelationSubscribers(key)
|
|
679
747
|
}
|
|
680
748
|
|
|
@@ -684,9 +752,8 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
684
752
|
fieldName: string,
|
|
685
753
|
itemId: string,
|
|
686
754
|
removalType: HasManyRemovalType,
|
|
687
|
-
alias?: string,
|
|
688
755
|
): void {
|
|
689
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
756
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
690
757
|
this.journal?.recordHasMany(key)
|
|
691
758
|
// Cancelling the add of a never-persisted child just removes it from the
|
|
692
759
|
// list; its now-unreachable snapshot is no longer reported as a `create` and
|
|
@@ -704,9 +771,9 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
704
771
|
toIndex: number,
|
|
705
772
|
alias?: string,
|
|
706
773
|
): void {
|
|
707
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
774
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
708
775
|
this.journal?.recordHasMany(key)
|
|
709
|
-
this.relations.moveInHasMany(key, fromIndex, toIndex)
|
|
776
|
+
this.relations.moveInHasMany(key, alias ?? fieldName, fromIndex, toIndex)
|
|
710
777
|
this.notifyRelationSubscribers(key)
|
|
711
778
|
}
|
|
712
779
|
|
|
@@ -716,8 +783,8 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
716
783
|
fieldName: string,
|
|
717
784
|
alias?: string,
|
|
718
785
|
): string[] {
|
|
719
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
720
|
-
return this.relations.getHasManyOrderedIds(key)
|
|
786
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
787
|
+
return this.relations.getHasManyOrderedIds(key, alias ?? fieldName)
|
|
721
788
|
}
|
|
722
789
|
|
|
723
790
|
getPresentationHasManyOrderedIds(
|
|
@@ -726,13 +793,13 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
726
793
|
fieldName: string,
|
|
727
794
|
alias?: string,
|
|
728
795
|
): string[] {
|
|
729
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
796
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
797
|
+
const view = alias ?? fieldName
|
|
730
798
|
if (!this.meta.isPessimisticInFlight(this.getEntityKey(parentType, parentId))) {
|
|
731
|
-
return this.relations.getHasManyOrderedIds(key)
|
|
799
|
+
return this.relations.getHasManyOrderedIds(key, view)
|
|
732
800
|
}
|
|
733
801
|
|
|
734
|
-
|
|
735
|
-
return state ? [...state.serverIds] : []
|
|
802
|
+
return [...this.relations.getHasManyView(key, view).serverIds]
|
|
736
803
|
}
|
|
737
804
|
|
|
738
805
|
isHasManyItemCreated(
|
|
@@ -740,24 +807,21 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
740
807
|
parentId: string,
|
|
741
808
|
fieldName: string,
|
|
742
809
|
itemId: string,
|
|
743
|
-
alias?: string,
|
|
744
810
|
): boolean {
|
|
745
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
746
|
-
|
|
747
|
-
return state?.plannedAdditions.get(itemId) === 'created'
|
|
811
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
812
|
+
return this.relations.getHasManyRelation(key)?.plannedAdditions.get(itemId) === 'created'
|
|
748
813
|
}
|
|
749
814
|
|
|
750
815
|
getHasManyCreatedEntities(
|
|
751
816
|
parentType: string,
|
|
752
817
|
parentId: string,
|
|
753
818
|
fieldName: string,
|
|
754
|
-
alias?: string,
|
|
755
819
|
): Set<string> | undefined {
|
|
756
|
-
const key = this.getRelationKey(parentType, parentId,
|
|
757
|
-
const
|
|
758
|
-
if (!
|
|
820
|
+
const key = this.getRelationKey(parentType, parentId, fieldName)
|
|
821
|
+
const relation = this.relations.getHasManyRelation(key)
|
|
822
|
+
if (!relation) return undefined
|
|
759
823
|
const created = new Set<string>()
|
|
760
|
-
for (const [id, kind] of
|
|
824
|
+
for (const [id, kind] of relation.plannedAdditions) {
|
|
761
825
|
if (kind === 'created') created.add(id)
|
|
762
826
|
}
|
|
763
827
|
return created
|
|
@@ -1105,23 +1169,16 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
1105
1169
|
hasManyStates: Map<string, StoredHasManyState>
|
|
1106
1170
|
entityMetas: Map<string, EntityMeta>
|
|
1107
1171
|
}): void {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1172
|
+
this.subscriptions.batchNotifications(() => {
|
|
1173
|
+
const entityKeys = this.entitySnapshots.importSnapshots(snapshot.entitySnapshots)
|
|
1174
|
+
this.meta.importMetas(snapshot.entityMetas)
|
|
1175
|
+
const relationKeys = this.relations.importRelationStates(snapshot.relationStates)
|
|
1176
|
+
const hasManyKeys = this.relations.importHasManyStates(snapshot.hasManyStates)
|
|
1111
1177
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
this.subscriptions.notifyGlobal()
|
|
1117
|
-
|
|
1118
|
-
for (const key of notifiedEntityKeys) {
|
|
1119
|
-
this.subscriptions.notifyEntityDirect(key)
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
for (const key of notifiedRelationKeys) {
|
|
1123
|
-
this.subscriptions.notifyRelationDirect(key)
|
|
1124
|
-
}
|
|
1178
|
+
this.subscriptions.notify()
|
|
1179
|
+
for (const key of entityKeys) this.subscriptions.notifyEntityDirect(key)
|
|
1180
|
+
for (const key of [...relationKeys, ...hasManyKeys]) this.subscriptions.notifyRelationDirect(key)
|
|
1181
|
+
})
|
|
1125
1182
|
}
|
|
1126
1183
|
|
|
1127
1184
|
// ==================== Undo Journal Target (cell capture / restore) ====================
|
|
@@ -1163,25 +1220,19 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
1163
1220
|
* journal's rekey to rebase a pre-image when a just-persisted create became a
|
|
1164
1221
|
* permanent member of the list (membership rebase for sealed creates).
|
|
1165
1222
|
*/
|
|
1166
|
-
getLiveHasManyServerIds(relationKey: string):
|
|
1167
|
-
|
|
1168
|
-
return new Set(state?.serverIds ?? [])
|
|
1223
|
+
getLiveHasManyServerIds(relationKey: string): ReadonlyMap<string, ReadonlySet<string>> {
|
|
1224
|
+
return this.relations.collectHasManyViewServerIds(relationKey)
|
|
1169
1225
|
}
|
|
1170
1226
|
|
|
1171
1227
|
exportHasManyCell(key: string): HasManyCellImage {
|
|
1172
|
-
const state = this.relations.
|
|
1228
|
+
const state = this.relations.getHasManyState(key)
|
|
1173
1229
|
if (!state) return { kind: 'hasMany', key, present: false }
|
|
1174
1230
|
return {
|
|
1175
1231
|
kind: 'hasMany',
|
|
1176
1232
|
key,
|
|
1177
1233
|
present: true,
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
orderedIds: state.orderedIds ? [...state.orderedIds] : null,
|
|
1181
|
-
plannedRemovals: new Map(state.plannedRemovals),
|
|
1182
|
-
plannedAdditions: new Map(state.plannedAdditions),
|
|
1183
|
-
version: state.version,
|
|
1184
|
-
},
|
|
1234
|
+
// getHasMany already returns a deep copy; the image owns it outright.
|
|
1235
|
+
state,
|
|
1185
1236
|
}
|
|
1186
1237
|
}
|
|
1187
1238
|
|
|
@@ -1220,7 +1271,9 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
1220
1271
|
const image = this.exportHasManyCell(hasManyKey)
|
|
1221
1272
|
images.push(image)
|
|
1222
1273
|
if (image.state) {
|
|
1223
|
-
|
|
1274
|
+
for (const view of image.state.views.values()) {
|
|
1275
|
+
if (view.orderedIds) worklist.push(...view.orderedIds)
|
|
1276
|
+
}
|
|
1224
1277
|
for (const additionId of image.state.plannedAdditions.keys()) worklist.push(additionId)
|
|
1225
1278
|
}
|
|
1226
1279
|
}
|
|
@@ -1236,9 +1289,6 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
1236
1289
|
* then present relations, then un-creates, then dropped relations.
|
|
1237
1290
|
*/
|
|
1238
1291
|
applyJournalImages(images: JournalCellImage[]): void {
|
|
1239
|
-
const notifyEntities = new Set<string>()
|
|
1240
|
-
const notifyRelations = new Set<string>()
|
|
1241
|
-
|
|
1242
1292
|
const presentEntities: EntityCellImage[] = []
|
|
1243
1293
|
const presentRelations: Array<RelationCellImage | HasManyCellImage> = []
|
|
1244
1294
|
const absentEntities: EntityCellImage[] = []
|
|
@@ -1246,29 +1296,37 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
1246
1296
|
|
|
1247
1297
|
for (const img of images) {
|
|
1248
1298
|
if (img.kind === 'entity') {
|
|
1249
|
-
notifyEntities.add(img.key)
|
|
1250
1299
|
;(img.present ? presentEntities : absentEntities).push(img)
|
|
1251
1300
|
} else {
|
|
1252
|
-
notifyRelations.add(img.key)
|
|
1253
|
-
notifyEntities.add(entityKeyOfRelationKey(img.key))
|
|
1254
1301
|
;(img.present ? presentRelations : absentRelations).push(img)
|
|
1255
1302
|
}
|
|
1256
1303
|
}
|
|
1257
1304
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1305
|
+
// removeEntity notifies; the batch holds that back until the restore is complete.
|
|
1306
|
+
this.subscriptions.batchNotifications(() => {
|
|
1307
|
+
for (const img of presentEntities) this.applyEntityImage(img)
|
|
1308
|
+
for (const img of presentRelations) this.applyRelationImage(img)
|
|
1309
|
+
for (const img of absentEntities) {
|
|
1310
|
+
const [type, id] = splitEntityKey(img.key)
|
|
1311
|
+
this.removeEntity(type, id)
|
|
1312
|
+
}
|
|
1313
|
+
for (const img of absentRelations) {
|
|
1314
|
+
if (img.kind === 'relation') this.relations.removeRelationState(img.key)
|
|
1315
|
+
else this.relations.removeHasManyState(img.key)
|
|
1316
|
+
}
|
|
1268
1317
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1318
|
+
this.subscriptions.notify()
|
|
1319
|
+
for (const img of images) this.notifyRestoredCell(img)
|
|
1320
|
+
})
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
private notifyRestoredCell(img: JournalCellImage): void {
|
|
1324
|
+
if (img.kind === 'entity') {
|
|
1325
|
+
this.subscriptions.notifyEntityDirect(img.key)
|
|
1326
|
+
return
|
|
1327
|
+
}
|
|
1328
|
+
this.subscriptions.notifyRelationDirect(img.key)
|
|
1329
|
+
this.subscriptions.notifyEntityDirect(entityKeyOfRelationKey(img.key))
|
|
1272
1330
|
}
|
|
1273
1331
|
|
|
1274
1332
|
private applyEntityImage(img: EntityCellImage): void {
|
|
@@ -1315,10 +1373,26 @@ export class SnapshotStore implements SnapshotVersionBumper, JournalTarget {
|
|
|
1315
1373
|
}]]))
|
|
1316
1374
|
} else {
|
|
1317
1375
|
const s = img.state!
|
|
1318
|
-
const live = this.relations.
|
|
1376
|
+
const live = this.relations.getHasManyState(img.key)
|
|
1377
|
+
// The editable layer (planned writes, manual ordering) comes from the image;
|
|
1378
|
+
// the server baseline always from the live state, which may have advanced
|
|
1379
|
+
// since the gesture. A view the image does not know did not exist before the
|
|
1380
|
+
// gesture — the gesture itself may have created it — so it had no arranged
|
|
1381
|
+
// order to restore and falls back to the default order of the restored writes.
|
|
1382
|
+
const views = new Map(live?.views ?? [])
|
|
1383
|
+
for (const [alias, view] of views) {
|
|
1384
|
+
const recorded = s.views.get(alias)
|
|
1385
|
+
views.set(alias, {
|
|
1386
|
+
serverIds: new Set(view.serverIds),
|
|
1387
|
+
orderedIds: recorded?.orderedIds ? [...recorded.orderedIds] : null,
|
|
1388
|
+
membership: view.membership,
|
|
1389
|
+
})
|
|
1390
|
+
}
|
|
1391
|
+
for (const [alias, recorded] of s.views) {
|
|
1392
|
+
if (!views.has(alias)) views.set(alias, recorded)
|
|
1393
|
+
}
|
|
1319
1394
|
this.relations.importHasManyStates(new Map([[img.key, {
|
|
1320
|
-
|
|
1321
|
-
orderedIds: s.orderedIds ? [...s.orderedIds] : null,
|
|
1395
|
+
views,
|
|
1322
1396
|
plannedRemovals: new Map(s.plannedRemovals),
|
|
1323
1397
|
plannedAdditions: new Map(s.plannedAdditions),
|
|
1324
1398
|
version: (live?.version ?? s.version) + 1,
|