@instantdb/core 0.22.57 → 0.22.58
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/__tests__/src/Reactor.test.js +97 -21
- package/__tests__/src/utils/PersistedObject.test.ts +383 -0
- package/dist/commonjs/InMemoryStorage.d.ts +11 -0
- package/dist/commonjs/InMemoryStorage.d.ts.map +1 -0
- package/dist/commonjs/InMemoryStorage.js +48 -0
- package/dist/commonjs/InMemoryStorage.js.map +1 -0
- package/dist/commonjs/IndexedDBStorage.d.ts +10 -3
- package/dist/commonjs/IndexedDBStorage.d.ts.map +1 -1
- package/dist/commonjs/IndexedDBStorage.js +254 -8
- package/dist/commonjs/IndexedDBStorage.js.map +1 -1
- package/dist/commonjs/Reactor.d.ts +7 -16
- package/dist/commonjs/Reactor.d.ts.map +1 -1
- package/dist/commonjs/Reactor.js +199 -149
- package/dist/commonjs/Reactor.js.map +1 -1
- package/dist/commonjs/SyncTable.d.ts +3 -1
- package/dist/commonjs/SyncTable.d.ts.map +1 -1
- package/dist/commonjs/SyncTable.js +194 -168
- package/dist/commonjs/SyncTable.js.map +1 -1
- package/dist/commonjs/index.d.ts +2 -1
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +3 -1
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/utils/PersistedObject.d.ts +73 -29
- package/dist/commonjs/utils/PersistedObject.d.ts.map +1 -1
- package/dist/commonjs/utils/PersistedObject.js +366 -77
- package/dist/commonjs/utils/PersistedObject.js.map +1 -1
- package/dist/esm/InMemoryStorage.d.ts +11 -0
- package/dist/esm/InMemoryStorage.d.ts.map +1 -0
- package/dist/esm/InMemoryStorage.js +45 -0
- package/dist/esm/InMemoryStorage.js.map +1 -0
- package/dist/esm/IndexedDBStorage.d.ts +10 -3
- package/dist/esm/IndexedDBStorage.d.ts.map +1 -1
- package/dist/esm/IndexedDBStorage.js +254 -8
- package/dist/esm/IndexedDBStorage.js.map +1 -1
- package/dist/esm/Reactor.d.ts +7 -16
- package/dist/esm/Reactor.d.ts.map +1 -1
- package/dist/esm/Reactor.js +199 -149
- package/dist/esm/Reactor.js.map +1 -1
- package/dist/esm/SyncTable.d.ts +3 -1
- package/dist/esm/SyncTable.d.ts.map +1 -1
- package/dist/esm/SyncTable.js +194 -168
- package/dist/esm/SyncTable.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/PersistedObject.d.ts +73 -29
- package/dist/esm/utils/PersistedObject.d.ts.map +1 -1
- package/dist/esm/utils/PersistedObject.js +364 -76
- package/dist/esm/utils/PersistedObject.js.map +1 -1
- package/dist/standalone/index.js +1978 -1567
- package/dist/standalone/index.umd.cjs +3 -3
- package/package.json +2 -2
- package/src/InMemoryStorage.ts +34 -0
- package/src/IndexedDBStorage.ts +281 -9
- package/src/Reactor.js +212 -184
- package/src/SyncTable.ts +220 -191
- package/src/index.ts +8 -0
- package/src/utils/PersistedObject.ts +480 -109
- package/src/InMemoryStorage.js +0 -14
package/src/SyncTable.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Logger } from './Reactor.js';
|
|
|
6
6
|
import instaql, { compareOrder } from './instaql.js';
|
|
7
7
|
import { InstaQLResponse, ValidQuery } from './queryTypes.ts';
|
|
8
8
|
import { EntitiesDef, IContainEntitiesAndLinks } from './schemaTypes.ts';
|
|
9
|
+
import { StorageInterface } from './index.ts';
|
|
9
10
|
|
|
10
11
|
type SubState = {
|
|
11
12
|
txId?: number;
|
|
@@ -21,15 +22,16 @@ type Sub = {
|
|
|
21
22
|
orderDirection: 'asc' | 'desc';
|
|
22
23
|
orderFieldType: 'string' | 'number' | 'date' | 'boolean';
|
|
23
24
|
state?: SubState;
|
|
24
|
-
|
|
25
|
+
values?: {
|
|
26
|
+
attrs: Record<string, any>;
|
|
27
|
+
entities: Array<{ entity: any; store: any; serverCreatedAt: number }>;
|
|
28
|
+
};
|
|
25
29
|
createdAt: number;
|
|
26
30
|
updatedAt: number;
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
// We could make a better type for this if we had a return type for s.toJSON
|
|
30
|
-
type
|
|
31
|
-
|
|
32
|
-
type Subs = { [hash: string]: Sub };
|
|
34
|
+
type SubInStorage = Sub;
|
|
33
35
|
|
|
34
36
|
type StartMsg = {
|
|
35
37
|
op: 'start-sync';
|
|
@@ -39,6 +41,7 @@ type StartMsg = {
|
|
|
39
41
|
type EndMsg = {
|
|
40
42
|
op: 'remove-sync';
|
|
41
43
|
'subscription-id': string;
|
|
44
|
+
'keep-subscription': boolean;
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
type ResyncMsg = {
|
|
@@ -82,48 +85,52 @@ type TrySend = (eventId: string, msg: SendMsg) => void;
|
|
|
82
85
|
type Config = { useDateObjects: boolean };
|
|
83
86
|
|
|
84
87
|
// Modifies the data in place because it comes directly from storage
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
89
|
-
for (const key in parsed) {
|
|
90
|
-
const sub = parsed[key];
|
|
91
|
-
for (const e of sub.entities || []) {
|
|
88
|
+
function syncSubFromStorage(sub: SubInStorage, useDateObjects: boolean): Sub {
|
|
89
|
+
const values = sub.values;
|
|
90
|
+
if (values) {
|
|
91
|
+
for (const e of values.entities || []) {
|
|
92
92
|
e.store.useDateObjects = useDateObjects;
|
|
93
|
+
e.store.attrs = values.attrs;
|
|
93
94
|
e.store = s.fromJSON(e.store);
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
+
|
|
98
|
+
return sub;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const sub
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
jsonSubs[key] = { ...sub, entities };
|
|
109
|
-
} else {
|
|
110
|
-
jsonSubs[key] = sub;
|
|
101
|
+
function syncSubToStorage(_k: string, sub: Sub): SubInStorage {
|
|
102
|
+
if (sub.values?.entities) {
|
|
103
|
+
const entities = [];
|
|
104
|
+
for (const e of sub.values?.entities) {
|
|
105
|
+
const store = s.toJSON(e.store);
|
|
106
|
+
// We'll store the attrs once on values, and put the
|
|
107
|
+
// attrs back into the store on hydration
|
|
108
|
+
delete store['attrs'];
|
|
109
|
+
entities.push({ ...e, store });
|
|
111
110
|
}
|
|
111
|
+
return { ...sub, values: { ...sub.values, entities } };
|
|
112
|
+
} else {
|
|
113
|
+
return sub;
|
|
112
114
|
}
|
|
113
|
-
return jsonSubs;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
function onMergeSub(
|
|
118
|
+
_key: string,
|
|
119
|
+
storageSub: SubInStorage | null,
|
|
120
|
+
inMemorySub: Sub | null,
|
|
121
|
+
): Sub {
|
|
122
|
+
const storageTxId = storageSub?.state.txId;
|
|
123
|
+
const memoryTxId = inMemorySub?.state.txId;
|
|
124
|
+
|
|
125
|
+
if (storageTxId && (!memoryTxId || storageTxId > memoryTxId)) {
|
|
126
|
+
return storageSub;
|
|
120
127
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
128
|
+
|
|
129
|
+
if (memoryTxId && (!storageTxId || memoryTxId > storageTxId)) {
|
|
130
|
+
return inMemorySub;
|
|
125
131
|
}
|
|
126
|
-
|
|
132
|
+
|
|
133
|
+
return storageSub || inMemorySub;
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
function queryEntity(sub: Sub, store: any) {
|
|
@@ -198,11 +205,14 @@ function changedFieldsOfChanges(
|
|
|
198
205
|
return changedFields;
|
|
199
206
|
}
|
|
200
207
|
|
|
201
|
-
function subData(sub: Sub, entities: NonNullable<Sub['entities']>) {
|
|
208
|
+
function subData(sub: Sub, entities: NonNullable<Sub['values']['entities']>) {
|
|
202
209
|
return { [sub.table]: entities.map((e) => e.entity) };
|
|
203
210
|
}
|
|
204
211
|
|
|
205
|
-
function sortEntitiesInPlace(
|
|
212
|
+
function sortEntitiesInPlace(
|
|
213
|
+
sub: Sub,
|
|
214
|
+
entities: NonNullable<Sub['values']['entities']>,
|
|
215
|
+
) {
|
|
206
216
|
const dataType = sub.orderFieldType;
|
|
207
217
|
if (sub.orderField === 'serverCreatedAt') {
|
|
208
218
|
entities.sort(
|
|
@@ -362,7 +372,7 @@ export type SyncTableCallback<
|
|
|
362
372
|
|
|
363
373
|
export class SyncTable {
|
|
364
374
|
private trySend: TrySend;
|
|
365
|
-
private subs: PersistedObject<
|
|
375
|
+
private subs: PersistedObject<string, Sub, SubInStorage>;
|
|
366
376
|
// Using any for the SyncCallback because we'd need Reactor to be typed
|
|
367
377
|
private callbacks: { [hash: string]: SyncTableCallback<any, any, any>[] } =
|
|
368
378
|
{};
|
|
@@ -373,7 +383,7 @@ export class SyncTable {
|
|
|
373
383
|
|
|
374
384
|
constructor(
|
|
375
385
|
trySend: TrySend,
|
|
376
|
-
|
|
386
|
+
storage: StorageInterface,
|
|
377
387
|
config: Config,
|
|
378
388
|
log: Logger,
|
|
379
389
|
createStore: (triples: Triple[]) => any,
|
|
@@ -383,14 +393,20 @@ export class SyncTable {
|
|
|
383
393
|
this.log = log;
|
|
384
394
|
this.createStore = createStore;
|
|
385
395
|
|
|
386
|
-
this.subs = new PersistedObject<
|
|
387
|
-
persister,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
396
|
+
this.subs = new PersistedObject<string, Sub, SubInStorage>({
|
|
397
|
+
persister: storage,
|
|
398
|
+
merge: onMergeSub,
|
|
399
|
+
serialize: syncSubToStorage,
|
|
400
|
+
parse: (_key, x) => syncSubFromStorage(x, this.config.useDateObjects),
|
|
401
|
+
objectSize: (sub) => sub.values?.entities.length || 0,
|
|
402
|
+
logger: log,
|
|
403
|
+
gc: {
|
|
404
|
+
maxAgeMs: 1000 * 60 * 60 * 24 * 7 * 52, // 1 year
|
|
405
|
+
maxEntries: 1000,
|
|
406
|
+
// Size of each sub is the number of entity
|
|
407
|
+
maxSize: 1_000_000, // 1 million entities
|
|
408
|
+
},
|
|
409
|
+
});
|
|
394
410
|
}
|
|
395
411
|
|
|
396
412
|
public beforeUnload() {
|
|
@@ -424,17 +440,15 @@ export class SyncTable {
|
|
|
424
440
|
|
|
425
441
|
if (!cbs.length) {
|
|
426
442
|
delete this.callbacks[hash];
|
|
427
|
-
if (keepSubscription) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
443
|
const sub = this.subs.currentValue[hash];
|
|
431
444
|
if (sub?.state) {
|
|
432
|
-
this.clearSubscriptionData(sub.state.subscriptionId);
|
|
445
|
+
this.clearSubscriptionData(sub.state.subscriptionId, keepSubscription);
|
|
446
|
+
}
|
|
447
|
+
if (!keepSubscription) {
|
|
448
|
+
this.subs.updateInPlace((prev) => {
|
|
449
|
+
delete prev[hash];
|
|
450
|
+
});
|
|
433
451
|
}
|
|
434
|
-
this.subs.set((prev) => {
|
|
435
|
-
delete prev[hash];
|
|
436
|
-
return prev;
|
|
437
|
-
});
|
|
438
452
|
}
|
|
439
453
|
}
|
|
440
454
|
|
|
@@ -456,10 +470,11 @@ export class SyncTable {
|
|
|
456
470
|
});
|
|
457
471
|
}
|
|
458
472
|
|
|
459
|
-
private sendRemove(state: SubState) {
|
|
473
|
+
private sendRemove(state: SubState, keepSubscription: boolean) {
|
|
460
474
|
this.trySend(uuid(), {
|
|
461
475
|
op: 'remove-sync',
|
|
462
476
|
'subscription-id': state.subscriptionId,
|
|
477
|
+
'keep-subscription': keepSubscription,
|
|
463
478
|
});
|
|
464
479
|
}
|
|
465
480
|
|
|
@@ -469,17 +484,17 @@ export class SyncTable {
|
|
|
469
484
|
cb?: SyncTableCallback<any, any, any>,
|
|
470
485
|
) {
|
|
471
486
|
// Wait for storage to load so that we know if we already have an existing subscription
|
|
472
|
-
await this.subs.
|
|
487
|
+
await this.subs.waitForKeyToLoad(hash);
|
|
473
488
|
const existingSub = this.subs.currentValue[hash];
|
|
474
489
|
|
|
475
490
|
if (existingSub && existingSub.state && existingSub.state.txId) {
|
|
476
491
|
this.sendResync(existingSub, existingSub.state);
|
|
477
492
|
|
|
478
|
-
if (existingSub.entities && cb) {
|
|
493
|
+
if (existingSub.values?.entities && cb) {
|
|
479
494
|
const k = Object.keys(query)[0];
|
|
480
495
|
cb({
|
|
481
496
|
type: CallbackEventType.LoadFromStorage,
|
|
482
|
-
data: subData(existingSub, existingSub.entities),
|
|
497
|
+
data: subData(existingSub, existingSub.values?.entities),
|
|
483
498
|
});
|
|
484
499
|
}
|
|
485
500
|
|
|
@@ -500,7 +515,7 @@ export class SyncTable {
|
|
|
500
515
|
'checked-data-type'
|
|
501
516
|
];
|
|
502
517
|
|
|
503
|
-
this.subs.
|
|
518
|
+
this.subs.updateInPlace((prev) => {
|
|
504
519
|
prev[hash] = {
|
|
505
520
|
query,
|
|
506
521
|
hash: hash,
|
|
@@ -511,15 +526,14 @@ export class SyncTable {
|
|
|
511
526
|
createdAt: Date.now(),
|
|
512
527
|
updatedAt: Date.now(),
|
|
513
528
|
};
|
|
514
|
-
return prev;
|
|
515
529
|
});
|
|
516
530
|
|
|
517
531
|
this.sendStart(query);
|
|
518
532
|
}
|
|
519
533
|
|
|
520
534
|
public async flushPending() {
|
|
521
|
-
await this.subs.waitForLoaded();
|
|
522
535
|
for (const hash of Object.keys(this.callbacks)) {
|
|
536
|
+
await this.subs.waitForKeyToLoad(hash);
|
|
523
537
|
const sub = this.subs.currentValue[hash];
|
|
524
538
|
if (sub) {
|
|
525
539
|
await this.initSubscription(sub.query, sub.hash);
|
|
@@ -536,7 +550,7 @@ export class SyncTable {
|
|
|
536
550
|
|
|
537
551
|
this.idToHash[subscriptionId] = hash;
|
|
538
552
|
|
|
539
|
-
this.subs.
|
|
553
|
+
this.subs.updateInPlace((prev) => {
|
|
540
554
|
const sub = prev[hash];
|
|
541
555
|
if (!sub) {
|
|
542
556
|
this.log.error(
|
|
@@ -554,7 +568,6 @@ export class SyncTable {
|
|
|
554
568
|
subscriptionId: subscriptionId,
|
|
555
569
|
token: msg.token,
|
|
556
570
|
};
|
|
557
|
-
return prev;
|
|
558
571
|
});
|
|
559
572
|
}
|
|
560
573
|
|
|
@@ -574,37 +587,42 @@ export class SyncTable {
|
|
|
574
587
|
}
|
|
575
588
|
|
|
576
589
|
const batch = [];
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const entities = sub.entities ?? [];
|
|
585
|
-
sub.entities = entities;
|
|
586
|
-
|
|
587
|
-
for (const entRows of joinRows) {
|
|
588
|
-
const store = this.createStore(entRows);
|
|
589
|
-
const entity = queryEntity(sub, store);
|
|
590
|
-
entities.push({
|
|
591
|
-
store,
|
|
592
|
-
entity,
|
|
593
|
-
serverCreatedAt: getServerCreatedAt(sub, store, entity.id),
|
|
594
|
-
});
|
|
595
|
-
batch.push(entity);
|
|
596
|
-
}
|
|
590
|
+
const sub = this.subs.currentValue[hash];
|
|
591
|
+
if (!sub) {
|
|
592
|
+
this.log.error('Missing sub for hash', hash, msg);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
597
595
|
|
|
598
|
-
|
|
596
|
+
const values = sub.values ?? {
|
|
597
|
+
entities: [],
|
|
598
|
+
attrs: this.createStore([]).attrs,
|
|
599
|
+
};
|
|
600
|
+
sub.values = values;
|
|
601
|
+
const entities = values.entities;
|
|
602
|
+
|
|
603
|
+
for (const entRows of joinRows) {
|
|
604
|
+
const store = this.createStore(entRows);
|
|
605
|
+
values.attrs = store.attrs;
|
|
606
|
+
const entity = queryEntity(sub, store);
|
|
607
|
+
entities.push({
|
|
608
|
+
store,
|
|
609
|
+
entity,
|
|
610
|
+
serverCreatedAt: getServerCreatedAt(sub, store, entity.id),
|
|
611
|
+
});
|
|
612
|
+
batch.push(entity);
|
|
613
|
+
}
|
|
599
614
|
|
|
600
|
-
|
|
615
|
+
this.subs.updateInPlace((prev) => {
|
|
616
|
+
prev[hash] = sub;
|
|
617
|
+
// Make sure we write a field or mutative won't
|
|
618
|
+
// see the change because sub === prev[hash]
|
|
619
|
+
prev[hash].updatedAt = Date.now();
|
|
601
620
|
});
|
|
602
621
|
|
|
603
|
-
|
|
604
|
-
if (sub.entities) {
|
|
622
|
+
if (sub.values) {
|
|
605
623
|
this.notifyCbs(hash, {
|
|
606
624
|
type: CallbackEventType.InitialSyncBatch,
|
|
607
|
-
data: subData(sub, sub.entities),
|
|
625
|
+
data: subData(sub, sub.values.entities),
|
|
608
626
|
batch,
|
|
609
627
|
});
|
|
610
628
|
}
|
|
@@ -617,11 +635,11 @@ export class SyncTable {
|
|
|
617
635
|
this.log.error('Missing hash for subscription', msg);
|
|
618
636
|
return;
|
|
619
637
|
}
|
|
620
|
-
this.subs.
|
|
638
|
+
this.subs.updateInPlace((prev) => {
|
|
621
639
|
const sub = prev[hash];
|
|
622
640
|
if (!sub) {
|
|
623
641
|
this.log.error('Missing sub for hash', hash, msg);
|
|
624
|
-
return
|
|
642
|
+
return;
|
|
625
643
|
}
|
|
626
644
|
const state = sub.state;
|
|
627
645
|
if (!state) {
|
|
@@ -630,7 +648,6 @@ export class SyncTable {
|
|
|
630
648
|
}
|
|
631
649
|
state.txId = msg['tx-id'];
|
|
632
650
|
sub.updatedAt = Date.now();
|
|
633
|
-
return prev;
|
|
634
651
|
});
|
|
635
652
|
|
|
636
653
|
const sub = this.subs.currentValue[hash];
|
|
@@ -638,7 +655,7 @@ export class SyncTable {
|
|
|
638
655
|
if (sub) {
|
|
639
656
|
this.notifyCbs(hash, {
|
|
640
657
|
type: CallbackEventType.InitialSyncComplete,
|
|
641
|
-
data: subData(sub, sub.entities || []),
|
|
658
|
+
data: subData(sub, sub.values?.entities || []),
|
|
642
659
|
});
|
|
643
660
|
}
|
|
644
661
|
}
|
|
@@ -650,128 +667,140 @@ export class SyncTable {
|
|
|
650
667
|
this.log.error('Missing hash for subscription', msg);
|
|
651
668
|
return;
|
|
652
669
|
}
|
|
653
|
-
this.subs.set((prev) => {
|
|
654
|
-
const sub = prev[hash];
|
|
655
|
-
if (!sub) {
|
|
656
|
-
this.log.error('Missing sub for hash', hash, msg);
|
|
657
|
-
return prev;
|
|
658
|
-
}
|
|
659
670
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
671
|
+
const sub = this.subs.currentValue[hash];
|
|
672
|
+
if (!sub) {
|
|
673
|
+
this.log.error('Missing sub for hash', hash, msg);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
665
676
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const idxesToDelete = [];
|
|
672
|
-
// Note: this won't work as well when links are involved
|
|
673
|
-
const byEid: {
|
|
674
|
-
[eid: string]: SyncUpdateTriplesMsg['txes'][number]['changes'];
|
|
675
|
-
} = {};
|
|
676
|
-
for (const change of tx.changes) {
|
|
677
|
-
const eidChanges = byEid[change.triple[0]] ?? [];
|
|
678
|
-
byEid[change.triple[0]] = eidChanges;
|
|
679
|
-
eidChanges.push(change);
|
|
680
|
-
}
|
|
677
|
+
const state = sub.state;
|
|
678
|
+
if (!state) {
|
|
679
|
+
this.log.error('Missing state for sub', sub, msg);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
681
682
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
683
|
+
for (const tx of msg.txes) {
|
|
684
|
+
if (state.txId >= tx['tx-id']) {
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
state.txId = tx['tx-id'];
|
|
688
|
+
const idxesToDelete = [];
|
|
689
|
+
// Note: this won't work as well when links are involved
|
|
690
|
+
const byEid: {
|
|
691
|
+
[eid: string]: SyncUpdateTriplesMsg['txes'][number]['changes'];
|
|
692
|
+
} = {};
|
|
693
|
+
for (const change of tx.changes) {
|
|
694
|
+
const eidChanges = byEid[change.triple[0]] ?? [];
|
|
695
|
+
byEid[change.triple[0]] = eidChanges;
|
|
696
|
+
eidChanges.push(change);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const values = sub.values ?? {
|
|
700
|
+
entities: [],
|
|
701
|
+
attrs: this.createStore([]).attrs,
|
|
702
|
+
};
|
|
703
|
+
const entities = values.entities;
|
|
704
|
+
sub.values = values;
|
|
705
|
+
|
|
706
|
+
const updated: SyncTransaction<any, any, any>['updated'] = [];
|
|
707
|
+
// Update the existing stores, if we already know about this entity
|
|
708
|
+
eidLoop: for (const [eid, changes] of Object.entries(byEid)) {
|
|
709
|
+
for (const [entIdx, ent] of Object.entries(entities)) {
|
|
710
|
+
if (s.hasEntity(ent.store, eid)) {
|
|
711
|
+
applyChangesToStore(ent.store, changes);
|
|
712
|
+
const entity = queryEntity(sub, ent.store);
|
|
713
|
+
const changedFields = changedFieldsOfChanges(ent.store, changes)[
|
|
714
|
+
eid
|
|
715
|
+
];
|
|
716
|
+
for (const [k, { oldValue, newValue }] of Object.entries(
|
|
717
|
+
changedFields || {},
|
|
718
|
+
)) {
|
|
719
|
+
if (oldValue === newValue) {
|
|
720
|
+
delete changedFields[k];
|
|
711
721
|
}
|
|
712
|
-
delete byEid[eid];
|
|
713
|
-
continue eidLoop;
|
|
714
722
|
}
|
|
723
|
+
if (entity) {
|
|
724
|
+
updated.push({
|
|
725
|
+
oldEntity: ent.entity,
|
|
726
|
+
newEntity: entity,
|
|
727
|
+
changedFields: changedFields || {},
|
|
728
|
+
});
|
|
729
|
+
ent.entity = entity;
|
|
730
|
+
} else {
|
|
731
|
+
idxesToDelete.push(entIdx);
|
|
732
|
+
}
|
|
733
|
+
delete byEid[eid];
|
|
734
|
+
continue eidLoop;
|
|
715
735
|
}
|
|
716
736
|
}
|
|
737
|
+
}
|
|
717
738
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
});
|
|
730
|
-
1;
|
|
731
|
-
continue;
|
|
732
|
-
}
|
|
733
|
-
entities.push({
|
|
739
|
+
const added = [];
|
|
740
|
+
// If we have anything left in byEid, then this must be a new entity we don't know about
|
|
741
|
+
for (const [_eid, changes] of Object.entries(byEid)) {
|
|
742
|
+
const store = this.createStore([]);
|
|
743
|
+
values.attrs = store.attrs;
|
|
744
|
+
applyChangesToStore(store, changes);
|
|
745
|
+
const entity = queryEntity(sub, store);
|
|
746
|
+
if (!entity) {
|
|
747
|
+
this.log.error('No entity found after applying change', {
|
|
748
|
+
sub,
|
|
749
|
+
changes,
|
|
734
750
|
store,
|
|
735
|
-
entity,
|
|
736
|
-
serverCreatedAt: getServerCreatedAt(sub, store, entity.id),
|
|
737
751
|
});
|
|
738
|
-
|
|
752
|
+
continue;
|
|
739
753
|
}
|
|
754
|
+
entities.push({
|
|
755
|
+
store,
|
|
756
|
+
entity,
|
|
757
|
+
serverCreatedAt: getServerCreatedAt(sub, store, entity.id),
|
|
758
|
+
});
|
|
759
|
+
added.push(entity);
|
|
760
|
+
}
|
|
740
761
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
for (const idx of idxesToDelete.sort().reverse()) {
|
|
744
|
-
removed.push(entities[idx].entity);
|
|
745
|
-
entities.splice(idx, 1);
|
|
746
|
-
}
|
|
762
|
+
const removed = [];
|
|
747
763
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
data: subData(sub, sub.entities),
|
|
752
|
-
added,
|
|
753
|
-
removed,
|
|
754
|
-
updated,
|
|
755
|
-
});
|
|
756
|
-
sub.updatedAt = Date.now();
|
|
764
|
+
for (const idx of idxesToDelete.sort().reverse()) {
|
|
765
|
+
removed.push(entities[idx].entity);
|
|
766
|
+
entities.splice(idx, 1);
|
|
757
767
|
}
|
|
758
768
|
|
|
759
|
-
|
|
769
|
+
sortEntitiesInPlace(sub, entities);
|
|
770
|
+
this.notifyCbs(hash, {
|
|
771
|
+
type: CallbackEventType.SyncTransaction,
|
|
772
|
+
data: subData(sub, sub.values?.entities),
|
|
773
|
+
added,
|
|
774
|
+
removed,
|
|
775
|
+
updated,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
this.subs.updateInPlace((prev) => {
|
|
779
|
+
prev[hash] = sub;
|
|
780
|
+
// Make sure we write a field or mutative won't
|
|
781
|
+
// see the change because sub === prev[hash]
|
|
782
|
+
prev[hash].updatedAt = Date.now();
|
|
760
783
|
});
|
|
761
784
|
}
|
|
762
785
|
|
|
763
|
-
private clearSubscriptionData(
|
|
786
|
+
private clearSubscriptionData(
|
|
787
|
+
subscriptionId: string,
|
|
788
|
+
keepSubscription: boolean,
|
|
789
|
+
) {
|
|
764
790
|
const hash = this.idToHash[subscriptionId];
|
|
765
791
|
if (hash) {
|
|
766
792
|
delete this.idToHash[subscriptionId];
|
|
767
793
|
const sub = this.subs.currentValue[hash];
|
|
768
794
|
if (sub.state) {
|
|
769
|
-
this.sendRemove(sub.state);
|
|
795
|
+
this.sendRemove(sub.state, keepSubscription);
|
|
796
|
+
}
|
|
797
|
+
if (keepSubscription) {
|
|
798
|
+
this.subs.unloadKey(hash);
|
|
799
|
+
} else {
|
|
800
|
+
this.subs.updateInPlace((prev) => {
|
|
801
|
+
delete prev[hash];
|
|
802
|
+
});
|
|
770
803
|
}
|
|
771
|
-
this.subs.set((prev) => {
|
|
772
|
-
delete prev[hash];
|
|
773
|
-
return prev;
|
|
774
|
-
});
|
|
775
804
|
if (sub) {
|
|
776
805
|
return sub;
|
|
777
806
|
}
|
|
@@ -813,7 +842,7 @@ export class SyncTable {
|
|
|
813
842
|
// This can happen if the auth changed and we need to refetch with the
|
|
814
843
|
// new auth or if the subscription is too far behind.
|
|
815
844
|
const subscriptionId = msg['original-event']['subscription-id'];
|
|
816
|
-
const removedSub = this.clearSubscriptionData(subscriptionId);
|
|
845
|
+
const removedSub = this.clearSubscriptionData(subscriptionId, false);
|
|
817
846
|
if (removedSub) {
|
|
818
847
|
this.initSubscription(removedSub.query, removedSub.hash);
|
|
819
848
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
validateTransactions,
|
|
21
21
|
TransactionValidationError,
|
|
22
22
|
} from './transactionValidation.ts';
|
|
23
|
+
import {
|
|
24
|
+
StorageInterface,
|
|
25
|
+
type StorageInterfaceStoreName,
|
|
26
|
+
} from './utils/PersistedObject.ts';
|
|
23
27
|
|
|
24
28
|
import type {
|
|
25
29
|
PresenceOpts,
|
|
@@ -1013,4 +1017,8 @@ export {
|
|
|
1013
1017
|
|
|
1014
1018
|
// error types
|
|
1015
1019
|
type InstantIssue,
|
|
1020
|
+
|
|
1021
|
+
// storage (e.g. indexeddb) interface
|
|
1022
|
+
StorageInterface,
|
|
1023
|
+
type StorageInterfaceStoreName,
|
|
1016
1024
|
};
|