@arcote.tech/arc-adapter-db-postgres 0.7.15 → 0.7.17
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/index.js +233 -290
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1470,6 +1470,42 @@ class AuthAdapter {
|
|
|
1470
1470
|
this.scopes.clear();
|
|
1471
1471
|
}
|
|
1472
1472
|
}
|
|
1473
|
+
var DEFAULT_TIMEOUT_MS = 8000;
|
|
1474
|
+
var provider = null;
|
|
1475
|
+
var latestSync = null;
|
|
1476
|
+
var syncSeq = 0;
|
|
1477
|
+
function triggerModuleSync(scope, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1478
|
+
if (!provider) {
|
|
1479
|
+
latestSync = Promise.resolve();
|
|
1480
|
+
return latestSync;
|
|
1481
|
+
}
|
|
1482
|
+
const seq = ++syncSeq;
|
|
1483
|
+
const run = (async () => {
|
|
1484
|
+
try {
|
|
1485
|
+
await provider(scope);
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
console.warn("[arc] module sync failed during setToken:", err);
|
|
1488
|
+
}
|
|
1489
|
+
})();
|
|
1490
|
+
const guarded = new Promise((resolve) => {
|
|
1491
|
+
let settled = false;
|
|
1492
|
+
const done = () => {
|
|
1493
|
+
if (settled)
|
|
1494
|
+
return;
|
|
1495
|
+
settled = true;
|
|
1496
|
+
resolve();
|
|
1497
|
+
};
|
|
1498
|
+
const timer = setTimeout(() => {
|
|
1499
|
+
console.warn(`[arc] module sync did not complete within ${timeoutMs}ms; proceeding anyway.`);
|
|
1500
|
+
done();
|
|
1501
|
+
}, timeoutMs);
|
|
1502
|
+
timer?.unref?.();
|
|
1503
|
+
run.then(done, done).finally(() => clearTimeout(timer));
|
|
1504
|
+
});
|
|
1505
|
+
if (seq === syncSeq)
|
|
1506
|
+
latestSync = guarded;
|
|
1507
|
+
return guarded;
|
|
1508
|
+
}
|
|
1473
1509
|
var eventWireInstanceCounter = 0;
|
|
1474
1510
|
|
|
1475
1511
|
class EventWire {
|
|
@@ -1484,7 +1520,8 @@ class EventWire {
|
|
|
1484
1520
|
onSyncedCallback;
|
|
1485
1521
|
reconnectTimeout;
|
|
1486
1522
|
syncRequested = false;
|
|
1487
|
-
|
|
1523
|
+
querySubscriptions = new Map;
|
|
1524
|
+
querySubCounter = 0;
|
|
1488
1525
|
enableEventSync;
|
|
1489
1526
|
constructor(baseUrl, options) {
|
|
1490
1527
|
this.baseUrl = baseUrl;
|
|
@@ -1539,7 +1576,7 @@ class EventWire {
|
|
|
1539
1576
|
this.requestSync();
|
|
1540
1577
|
}
|
|
1541
1578
|
this.flushPendingEvents();
|
|
1542
|
-
this.
|
|
1579
|
+
this.sendAllQuerySubscriptions();
|
|
1543
1580
|
} else {
|
|
1544
1581
|
console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
|
|
1545
1582
|
}
|
|
@@ -1615,24 +1652,29 @@ class EventWire {
|
|
|
1615
1652
|
onSynced(callback) {
|
|
1616
1653
|
this.onSyncedCallback = callback;
|
|
1617
1654
|
}
|
|
1618
|
-
|
|
1619
|
-
const
|
|
1620
|
-
this.
|
|
1655
|
+
subscribeQuery(descriptor, scope, callbacks) {
|
|
1656
|
+
const subscriptionId = `qs_${this.instanceId}_${++this.querySubCounter}`;
|
|
1657
|
+
this.querySubscriptions.set(subscriptionId, {
|
|
1658
|
+
descriptor,
|
|
1659
|
+
scope,
|
|
1660
|
+
callbacks
|
|
1661
|
+
});
|
|
1621
1662
|
if (this.state === "connected" && this.ws) {
|
|
1622
1663
|
this.ws.send(JSON.stringify({
|
|
1623
|
-
type: "subscribe-
|
|
1624
|
-
|
|
1664
|
+
type: "subscribe-query",
|
|
1665
|
+
subscriptionId,
|
|
1666
|
+
descriptor,
|
|
1625
1667
|
scope
|
|
1626
1668
|
}));
|
|
1627
1669
|
}
|
|
1670
|
+
return subscriptionId;
|
|
1628
1671
|
}
|
|
1629
|
-
|
|
1630
|
-
this.
|
|
1672
|
+
unsubscribeQuery(subscriptionId) {
|
|
1673
|
+
this.querySubscriptions.delete(subscriptionId);
|
|
1631
1674
|
if (this.state === "connected" && this.ws) {
|
|
1632
1675
|
this.ws.send(JSON.stringify({
|
|
1633
|
-
type: "unsubscribe-
|
|
1634
|
-
|
|
1635
|
-
scope
|
|
1676
|
+
type: "unsubscribe-query",
|
|
1677
|
+
subscriptionId
|
|
1636
1678
|
}));
|
|
1637
1679
|
}
|
|
1638
1680
|
}
|
|
@@ -1667,17 +1709,17 @@ class EventWire {
|
|
|
1667
1709
|
this.lastHostEventId = message.lastHostEventId;
|
|
1668
1710
|
}
|
|
1669
1711
|
break;
|
|
1670
|
-
case "
|
|
1671
|
-
const sub = this.
|
|
1712
|
+
case "query-snapshot": {
|
|
1713
|
+
const sub = this.querySubscriptions.get(message.subscriptionId);
|
|
1672
1714
|
if (sub) {
|
|
1673
|
-
sub.onSnapshot(message.
|
|
1715
|
+
sub.callbacks.onSnapshot(message.result ?? null);
|
|
1674
1716
|
}
|
|
1675
1717
|
break;
|
|
1676
1718
|
}
|
|
1677
|
-
case "
|
|
1678
|
-
const sub = this.
|
|
1719
|
+
case "query-changes": {
|
|
1720
|
+
const sub = this.querySubscriptions.get(message.subscriptionId);
|
|
1679
1721
|
if (sub && Array.isArray(message.changes)) {
|
|
1680
|
-
sub.onChanges(message.changes);
|
|
1722
|
+
sub.callbacks.onChanges(message.changes);
|
|
1681
1723
|
}
|
|
1682
1724
|
break;
|
|
1683
1725
|
}
|
|
@@ -1705,17 +1747,15 @@ class EventWire {
|
|
|
1705
1747
|
this.pendingEvents = [];
|
|
1706
1748
|
}
|
|
1707
1749
|
}
|
|
1708
|
-
|
|
1750
|
+
sendAllQuerySubscriptions() {
|
|
1709
1751
|
if (!this.ws || this.state !== "connected")
|
|
1710
1752
|
return;
|
|
1711
|
-
for (const
|
|
1712
|
-
const sepIdx = key.indexOf(":");
|
|
1713
|
-
const scope = key.slice(0, sepIdx);
|
|
1714
|
-
const element = key.slice(sepIdx + 1);
|
|
1753
|
+
for (const [subscriptionId, sub] of this.querySubscriptions) {
|
|
1715
1754
|
this.ws.send(JSON.stringify({
|
|
1716
|
-
type: "subscribe-
|
|
1717
|
-
|
|
1718
|
-
|
|
1755
|
+
type: "subscribe-query",
|
|
1756
|
+
subscriptionId,
|
|
1757
|
+
descriptor: sub.descriptor,
|
|
1758
|
+
scope: sub.scope
|
|
1719
1759
|
}));
|
|
1720
1760
|
}
|
|
1721
1761
|
}
|
|
@@ -1739,19 +1779,12 @@ class LocalEventPublisher {
|
|
|
1739
1779
|
views = [];
|
|
1740
1780
|
syncCallback;
|
|
1741
1781
|
subscribers = new Map;
|
|
1742
|
-
viewChangesCallbacks = new Set;
|
|
1743
1782
|
constructor(dataStorage) {
|
|
1744
1783
|
this.dataStorage = dataStorage;
|
|
1745
1784
|
}
|
|
1746
1785
|
onPublish(callback) {
|
|
1747
1786
|
this.syncCallback = callback;
|
|
1748
1787
|
}
|
|
1749
|
-
onViewChanges(callback) {
|
|
1750
|
-
this.viewChangesCallbacks.add(callback);
|
|
1751
|
-
return () => {
|
|
1752
|
-
this.viewChangesCallbacks.delete(callback);
|
|
1753
|
-
};
|
|
1754
|
-
}
|
|
1755
1788
|
registerViews(views) {
|
|
1756
1789
|
this.views = views;
|
|
1757
1790
|
}
|
|
@@ -1819,19 +1852,7 @@ class LocalEventPublisher {
|
|
|
1819
1852
|
});
|
|
1820
1853
|
const viewChanges = await this.collectViewChanges(event);
|
|
1821
1854
|
allChanges.push(...viewChanges);
|
|
1822
|
-
|
|
1823
|
-
const committed = await this.dataStorage.commitChanges(allChanges, {
|
|
1824
|
-
captureRowsFor: viewStoreNames
|
|
1825
|
-
});
|
|
1826
|
-
if (committed.length > 0 && this.viewChangesCallbacks.size > 0) {
|
|
1827
|
-
for (const callback of this.viewChangesCallbacks) {
|
|
1828
|
-
try {
|
|
1829
|
-
callback(committed);
|
|
1830
|
-
} catch (error) {
|
|
1831
|
-
console.error(`[EventPublisher] onViewChanges callback error:`, error);
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1855
|
+
await this.dataStorage.commitChanges(allChanges);
|
|
1835
1856
|
await this.notifySubscribers(event);
|
|
1836
1857
|
if (this.syncCallback) {
|
|
1837
1858
|
this.syncCallback(event);
|
|
@@ -2471,9 +2492,8 @@ class ArcObject extends ArcAbstract {
|
|
|
2471
2492
|
}
|
|
2472
2493
|
}
|
|
2473
2494
|
class DataStorage {
|
|
2474
|
-
async commitChanges(changes
|
|
2495
|
+
async commitChanges(changes) {
|
|
2475
2496
|
await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
|
|
2476
|
-
return [];
|
|
2477
2497
|
}
|
|
2478
2498
|
}
|
|
2479
2499
|
|
|
@@ -3564,6 +3584,48 @@ function deepMerge(target, source) {
|
|
|
3564
3584
|
function isPlainObject(item) {
|
|
3565
3585
|
return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
|
|
3566
3586
|
}
|
|
3587
|
+
function murmurHash(key, seed = 0) {
|
|
3588
|
+
let remainder, bytes, h1, h1b, c1, c2, k1, i;
|
|
3589
|
+
remainder = key.length & 3;
|
|
3590
|
+
bytes = key.length - remainder;
|
|
3591
|
+
h1 = seed;
|
|
3592
|
+
c1 = 3432918353;
|
|
3593
|
+
c2 = 461845907;
|
|
3594
|
+
i = 0;
|
|
3595
|
+
while (i < bytes) {
|
|
3596
|
+
k1 = key.charCodeAt(i) & 255 | (key.charCodeAt(++i) & 255) << 8 | (key.charCodeAt(++i) & 255) << 16 | (key.charCodeAt(++i) & 255) << 24;
|
|
3597
|
+
++i;
|
|
3598
|
+
k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
|
|
3599
|
+
k1 = k1 << 15 | k1 >>> 17;
|
|
3600
|
+
k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
|
|
3601
|
+
h1 ^= k1;
|
|
3602
|
+
h1 = h1 << 13 | h1 >>> 19;
|
|
3603
|
+
h1b = (h1 & 65535) * 5 + (((h1 >>> 16) * 5 & 65535) << 16) & 4294967295;
|
|
3604
|
+
h1 = (h1b & 65535) + 27492 + (((h1b >>> 16) + 58964 & 65535) << 16);
|
|
3605
|
+
}
|
|
3606
|
+
k1 = 0;
|
|
3607
|
+
if (remainder >= 3) {
|
|
3608
|
+
k1 ^= (key.charCodeAt(i + 2) & 255) << 16;
|
|
3609
|
+
}
|
|
3610
|
+
if (remainder >= 2) {
|
|
3611
|
+
k1 ^= (key.charCodeAt(i + 1) & 255) << 8;
|
|
3612
|
+
}
|
|
3613
|
+
if (remainder >= 1) {
|
|
3614
|
+
k1 ^= key.charCodeAt(i) & 255;
|
|
3615
|
+
k1 = (k1 & 65535) * c1 + (((k1 >>> 16) * c1 & 65535) << 16) & 4294967295;
|
|
3616
|
+
k1 = k1 << 15 | k1 >>> 17;
|
|
3617
|
+
k1 = (k1 & 65535) * c2 + (((k1 >>> 16) * c2 & 65535) << 16) & 4294967295;
|
|
3618
|
+
h1 ^= k1;
|
|
3619
|
+
}
|
|
3620
|
+
h1 ^= key.length;
|
|
3621
|
+
h1 ^= h1 >>> 16;
|
|
3622
|
+
h1 = (h1 & 65535) * 2246822507 + (((h1 >>> 16) * 2246822507 & 65535) << 16) & 4294967295;
|
|
3623
|
+
h1 ^= h1 >>> 13;
|
|
3624
|
+
h1 = (h1 & 65535) * 3266489909 + (((h1 >>> 16) * 3266489909 & 65535) << 16) & 4294967295;
|
|
3625
|
+
h1 ^= h1 >>> 16;
|
|
3626
|
+
return h1 >>> 0;
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3567
3629
|
class ForkedStoreState extends StoreState {
|
|
3568
3630
|
master;
|
|
3569
3631
|
changedItems = new Map;
|
|
@@ -3719,12 +3781,8 @@ class MasterStoreState extends StoreState {
|
|
|
3719
3781
|
}
|
|
3720
3782
|
return transaction.find(this.storeName, { where: { _id: id2 } }).then((results) => results[0]);
|
|
3721
3783
|
}
|
|
3722
|
-
async applyChangeAndReturnEvent(transaction, change, transactionCache
|
|
3784
|
+
async applyChangeAndReturnEvent(transaction, change, transactionCache) {
|
|
3723
3785
|
if (change.type === "set") {
|
|
3724
|
-
let existing;
|
|
3725
|
-
if (options?.captureRows) {
|
|
3726
|
-
existing = await this.readExisting(transaction, change.data._id, transactionCache);
|
|
3727
|
-
}
|
|
3728
3786
|
await transaction.set(this.storeName, change.data);
|
|
3729
3787
|
const item = this.deserialize ? this.deserialize(change.data) : change.data;
|
|
3730
3788
|
if (transactionCache) {
|
|
@@ -3737,16 +3795,10 @@ class MasterStoreState extends StoreState {
|
|
|
3737
3795
|
type: "set",
|
|
3738
3796
|
item: change.data,
|
|
3739
3797
|
id: change.data._id
|
|
3740
|
-
}
|
|
3741
|
-
oldRow: existing ?? null,
|
|
3742
|
-
newRow: change.data
|
|
3798
|
+
}
|
|
3743
3799
|
};
|
|
3744
3800
|
}
|
|
3745
3801
|
if (change.type === "delete") {
|
|
3746
|
-
let existing;
|
|
3747
|
-
if (options?.captureRows) {
|
|
3748
|
-
existing = await this.readExisting(transaction, change.id, transactionCache);
|
|
3749
|
-
}
|
|
3750
3802
|
await transaction.remove(this.storeName, change.id);
|
|
3751
3803
|
if (transactionCache) {
|
|
3752
3804
|
transactionCache.delete(`${this.storeName}:${change.id}`);
|
|
@@ -3758,9 +3810,7 @@ class MasterStoreState extends StoreState {
|
|
|
3758
3810
|
type: "delete",
|
|
3759
3811
|
item: null,
|
|
3760
3812
|
id: change.id
|
|
3761
|
-
}
|
|
3762
|
-
oldRow: existing ?? null,
|
|
3763
|
-
newRow: null
|
|
3813
|
+
}
|
|
3764
3814
|
};
|
|
3765
3815
|
}
|
|
3766
3816
|
if (change.type === "modify") {
|
|
@@ -3778,9 +3828,7 @@ class MasterStoreState extends StoreState {
|
|
|
3778
3828
|
type: "set",
|
|
3779
3829
|
item,
|
|
3780
3830
|
id: change.id
|
|
3781
|
-
}
|
|
3782
|
-
oldRow: existing ?? null,
|
|
3783
|
-
newRow: updated
|
|
3831
|
+
}
|
|
3784
3832
|
};
|
|
3785
3833
|
}
|
|
3786
3834
|
if (change.type === "mutate") {
|
|
@@ -3798,9 +3846,7 @@ class MasterStoreState extends StoreState {
|
|
|
3798
3846
|
type: "set",
|
|
3799
3847
|
item,
|
|
3800
3848
|
id: change.id
|
|
3801
|
-
}
|
|
3802
|
-
oldRow: existing ?? null,
|
|
3803
|
-
newRow: updated
|
|
3849
|
+
}
|
|
3804
3850
|
};
|
|
3805
3851
|
}
|
|
3806
3852
|
throw new Error("Unknown change type");
|
|
@@ -3878,22 +3924,17 @@ class MasterDataStorage extends DataStorage {
|
|
|
3878
3924
|
applySerializedChanges(changes) {
|
|
3879
3925
|
return Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applySerializedChanges(changes2)));
|
|
3880
3926
|
}
|
|
3881
|
-
async commitChanges(changes
|
|
3927
|
+
async commitChanges(changes) {
|
|
3882
3928
|
const transaction = await this.getReadWriteTransaction();
|
|
3883
3929
|
const transactionCache = new Map;
|
|
3884
3930
|
const eventsByStore = new Map;
|
|
3885
|
-
const committed = [];
|
|
3886
3931
|
for (const { store, changes: storeChanges } of changes) {
|
|
3887
3932
|
const storeState = this.getStore(store);
|
|
3888
3933
|
const storeEvents = [];
|
|
3889
|
-
const capture = options?.captureRowsFor?.has(store) ?? false;
|
|
3890
3934
|
for (const change of storeChanges) {
|
|
3891
|
-
const { event: event3
|
|
3935
|
+
const { event: event3 } = await storeState.applyChangeAndReturnEvent(transaction, change, transactionCache);
|
|
3892
3936
|
if (event3)
|
|
3893
3937
|
storeEvents.push(event3);
|
|
3894
|
-
if (capture) {
|
|
3895
|
-
committed.push({ store, id: event3.id, oldRow, newRow });
|
|
3896
|
-
}
|
|
3897
3938
|
}
|
|
3898
3939
|
if (storeEvents.length > 0) {
|
|
3899
3940
|
eventsByStore.set(store, storeEvents);
|
|
@@ -3904,7 +3945,6 @@ class MasterDataStorage extends DataStorage {
|
|
|
3904
3945
|
const storeState = this.getStore(store);
|
|
3905
3946
|
storeState.notifyListenersPublic(events);
|
|
3906
3947
|
}
|
|
3907
|
-
return committed;
|
|
3908
3948
|
}
|
|
3909
3949
|
fork() {
|
|
3910
3950
|
return new ForkedDataStorage(this);
|
|
@@ -4019,8 +4059,8 @@ class ObservableDataStorage {
|
|
|
4019
4059
|
getReadWriteTransaction() {
|
|
4020
4060
|
return this.source.getReadWriteTransaction();
|
|
4021
4061
|
}
|
|
4022
|
-
commitChanges(changes
|
|
4023
|
-
return this.source.commitChanges(changes
|
|
4062
|
+
commitChanges(changes) {
|
|
4063
|
+
return this.source.commitChanges(changes);
|
|
4024
4064
|
}
|
|
4025
4065
|
trackQuery(storeName, options, result, listener4) {
|
|
4026
4066
|
const key = this.getQueryKey(storeName, options);
|
|
@@ -4033,7 +4073,8 @@ class ObservableDataStorage {
|
|
|
4033
4073
|
}
|
|
4034
4074
|
handleStoreChange(storeName, events) {
|
|
4035
4075
|
let hasChanges = false;
|
|
4036
|
-
|
|
4076
|
+
const staleKeys = [];
|
|
4077
|
+
for (const [key, query] of this.trackedQueries) {
|
|
4037
4078
|
if (query.storeName !== storeName)
|
|
4038
4079
|
continue;
|
|
4039
4080
|
let currentResult = query.result;
|
|
@@ -4045,10 +4086,20 @@ class ObservableDataStorage {
|
|
|
4045
4086
|
queryChanged = true;
|
|
4046
4087
|
}
|
|
4047
4088
|
}
|
|
4048
|
-
if (queryChanged)
|
|
4049
|
-
|
|
4089
|
+
if (!queryChanged)
|
|
4090
|
+
continue;
|
|
4091
|
+
if (query.options.limit !== undefined && query.result.length === query.options.limit && currentResult.length < query.options.limit) {
|
|
4092
|
+
staleKeys.push(key);
|
|
4050
4093
|
hasChanges = true;
|
|
4094
|
+
continue;
|
|
4051
4095
|
}
|
|
4096
|
+
query.result = currentResult;
|
|
4097
|
+
hasChanges = true;
|
|
4098
|
+
}
|
|
4099
|
+
for (const key of staleKeys) {
|
|
4100
|
+
const query = this.trackedQueries.get(key);
|
|
4101
|
+
this.source.getStore(query.storeName).unsubscribe(query.listener);
|
|
4102
|
+
this.trackedQueries.delete(key);
|
|
4052
4103
|
}
|
|
4053
4104
|
if (hasChanges) {
|
|
4054
4105
|
this.onChange();
|
|
@@ -4222,6 +4273,7 @@ class ScopedModel {
|
|
|
4222
4273
|
for (const listener4 of this.tokenListeners) {
|
|
4223
4274
|
listener4();
|
|
4224
4275
|
}
|
|
4276
|
+
return triggerModuleSync(this.scopeName);
|
|
4225
4277
|
}
|
|
4226
4278
|
getToken() {
|
|
4227
4279
|
return this.authAdapter.getToken();
|
|
@@ -4319,244 +4371,135 @@ class Model {
|
|
|
4319
4371
|
return s;
|
|
4320
4372
|
}
|
|
4321
4373
|
}
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
static UNSUBSCRIBE_DELAY_MS = 5000;
|
|
4330
|
-
storeKey(viewName, scope) {
|
|
4331
|
-
return `${scope ?? DEFAULT_SCOPE}:${viewName}`;
|
|
4332
|
-
}
|
|
4333
|
-
registerViews(views) {
|
|
4334
|
-
this.views = views;
|
|
4335
|
-
}
|
|
4336
|
-
getStore(viewName, scope) {
|
|
4337
|
-
const key = this.storeKey(viewName, scope);
|
|
4338
|
-
if (!this.stores.has(key)) {
|
|
4339
|
-
this.stores.set(key, new StreamingStore);
|
|
4374
|
+
function applyQueryChanges(result, changes) {
|
|
4375
|
+
const next = [...result];
|
|
4376
|
+
for (const change of changes) {
|
|
4377
|
+
if (change.type === "delete") {
|
|
4378
|
+
const idx = next.findIndex((it) => it._id === change.id);
|
|
4379
|
+
if (idx !== -1)
|
|
4380
|
+
next.splice(idx, 1);
|
|
4340
4381
|
}
|
|
4341
|
-
return this.stores.get(key);
|
|
4342
|
-
}
|
|
4343
|
-
hasData(viewName, scope) {
|
|
4344
|
-
const store = this.stores.get(this.storeKey(viewName, scope));
|
|
4345
|
-
return store ? store.hasData() : false;
|
|
4346
4382
|
}
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4383
|
+
for (const change of changes) {
|
|
4384
|
+
if (change.type === "set") {
|
|
4385
|
+
const idx = next.findIndex((it) => it._id === change.id);
|
|
4386
|
+
if (idx !== -1)
|
|
4387
|
+
next.splice(idx, 1);
|
|
4388
|
+
next.splice(change.index, 0, change.item);
|
|
4352
4389
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4390
|
+
}
|
|
4391
|
+
return next;
|
|
4392
|
+
}
|
|
4393
|
+
class StreamingQueryCache {
|
|
4394
|
+
entries = new Map;
|
|
4395
|
+
static UNSUBSCRIBE_DELAY_MS = 5000;
|
|
4396
|
+
entryKey(descriptor, scope) {
|
|
4397
|
+
return `${scope ?? "default"}:${murmurHash(JSON.stringify(descriptor))}`;
|
|
4398
|
+
}
|
|
4399
|
+
subscribe(descriptor, scope, eventWire, onChange) {
|
|
4400
|
+
const key = this.entryKey(descriptor, scope);
|
|
4401
|
+
let entry = this.entries.get(key);
|
|
4402
|
+
if (entry) {
|
|
4403
|
+
if (entry.pendingUnsub) {
|
|
4404
|
+
clearTimeout(entry.pendingUnsub);
|
|
4405
|
+
entry.pendingUnsub = undefined;
|
|
4406
|
+
}
|
|
4407
|
+
entry.refCount++;
|
|
4408
|
+
} else {
|
|
4409
|
+
const newEntry = {
|
|
4410
|
+
result: undefined,
|
|
4411
|
+
hasResult: false,
|
|
4412
|
+
listeners: new Set,
|
|
4413
|
+
refCount: 1,
|
|
4414
|
+
subscriptionId: ""
|
|
4359
4415
|
};
|
|
4416
|
+
newEntry.subscriptionId = eventWire.subscribeQuery(descriptor, scope, {
|
|
4417
|
+
onSnapshot: (result) => {
|
|
4418
|
+
newEntry.result = result;
|
|
4419
|
+
newEntry.hasResult = true;
|
|
4420
|
+
this.notify(newEntry);
|
|
4421
|
+
},
|
|
4422
|
+
onChanges: (changes) => {
|
|
4423
|
+
if (!newEntry.hasResult || !Array.isArray(newEntry.result)) {
|
|
4424
|
+
return;
|
|
4425
|
+
}
|
|
4426
|
+
newEntry.result = applyQueryChanges(newEntry.result, changes);
|
|
4427
|
+
this.notify(newEntry);
|
|
4428
|
+
}
|
|
4429
|
+
});
|
|
4430
|
+
this.entries.set(key, newEntry);
|
|
4431
|
+
entry = newEntry;
|
|
4360
4432
|
}
|
|
4361
|
-
const
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
refCount: 1
|
|
4365
|
-
});
|
|
4433
|
+
const subscribed = entry;
|
|
4434
|
+
subscribed.listeners.add(onChange);
|
|
4435
|
+
let active = true;
|
|
4366
4436
|
return {
|
|
4367
|
-
|
|
4368
|
-
|
|
4437
|
+
read: () => ({
|
|
4438
|
+
result: subscribed.result,
|
|
4439
|
+
loading: !subscribed.hasResult
|
|
4440
|
+
}),
|
|
4441
|
+
unsubscribe: () => {
|
|
4442
|
+
if (!active)
|
|
4443
|
+
return;
|
|
4444
|
+
active = false;
|
|
4445
|
+
subscribed.listeners.delete(onChange);
|
|
4446
|
+
subscribed.refCount--;
|
|
4447
|
+
if (subscribed.refCount > 0)
|
|
4448
|
+
return;
|
|
4449
|
+
subscribed.pendingUnsub = setTimeout(() => {
|
|
4450
|
+
subscribed.pendingUnsub = undefined;
|
|
4451
|
+
if (subscribed.refCount > 0)
|
|
4452
|
+
return;
|
|
4453
|
+
eventWire.unsubscribeQuery(subscribed.subscriptionId);
|
|
4454
|
+
this.entries.delete(key);
|
|
4455
|
+
}, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
|
|
4456
|
+
}
|
|
4369
4457
|
};
|
|
4370
4458
|
}
|
|
4371
|
-
|
|
4372
|
-
const stream = this.activeStreams.get(key);
|
|
4373
|
-
if (!stream)
|
|
4374
|
-
return;
|
|
4375
|
-
stream.refCount--;
|
|
4376
|
-
if (stream.refCount <= 0) {
|
|
4377
|
-
const timeout = setTimeout(() => {
|
|
4378
|
-
this.pendingUnsubscribes.delete(key);
|
|
4379
|
-
const current2 = this.activeStreams.get(key);
|
|
4380
|
-
if (current2 && current2.refCount <= 0) {
|
|
4381
|
-
current2.unsubscribe();
|
|
4382
|
-
this.activeStreams.delete(key);
|
|
4383
|
-
}
|
|
4384
|
-
}, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
|
|
4385
|
-
this.pendingUnsubscribes.set(key, timeout);
|
|
4386
|
-
}
|
|
4387
|
-
}
|
|
4388
|
-
subscribeView(viewName, eventWire, scope) {
|
|
4389
|
-
const key = this.storeKey(viewName, scope);
|
|
4390
|
-
const { unsubscribe } = this.registerStream(key, () => {
|
|
4391
|
-
const store = this.stores.get(key) ?? new StreamingStore;
|
|
4392
|
-
this.stores.set(key, store);
|
|
4393
|
-
eventWire.subscribeView(viewName, scope ?? DEFAULT_SCOPE, {
|
|
4394
|
-
onSnapshot: (items) => store.setAll(items),
|
|
4395
|
-
onChanges: (changes) => store.applyChanges(changes)
|
|
4396
|
-
});
|
|
4397
|
-
return {
|
|
4398
|
-
unsubscribe: () => eventWire.unsubscribeView(viewName, scope ?? DEFAULT_SCOPE)
|
|
4399
|
-
};
|
|
4400
|
-
});
|
|
4401
|
-
return unsubscribe;
|
|
4402
|
-
}
|
|
4403
|
-
invalidateScope(scope) {
|
|
4459
|
+
invalidateScope(scope, eventWire) {
|
|
4404
4460
|
const prefix = `${scope}:`;
|
|
4405
|
-
for (const [key,
|
|
4406
|
-
if (!key.startsWith(prefix))
|
|
4407
|
-
continue;
|
|
4408
|
-
clearTimeout(timeout);
|
|
4409
|
-
this.pendingUnsubscribes.delete(key);
|
|
4410
|
-
}
|
|
4411
|
-
for (const [key, stream] of this.activeStreams) {
|
|
4461
|
+
for (const [key, entry] of this.entries) {
|
|
4412
4462
|
if (!key.startsWith(prefix))
|
|
4413
4463
|
continue;
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
this.
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
continue;
|
|
4422
|
-
store.clear();
|
|
4464
|
+
if (entry.pendingUnsub)
|
|
4465
|
+
clearTimeout(entry.pendingUnsub);
|
|
4466
|
+
eventWire?.unsubscribeQuery(entry.subscriptionId);
|
|
4467
|
+
this.entries.delete(key);
|
|
4468
|
+
entry.result = undefined;
|
|
4469
|
+
entry.hasResult = false;
|
|
4470
|
+
this.notify(entry);
|
|
4423
4471
|
}
|
|
4424
4472
|
}
|
|
4425
|
-
|
|
4426
|
-
for (const
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
continue;
|
|
4431
|
-
const suffix = `:${view3.name}`;
|
|
4432
|
-
for (const [key, store] of this.stores) {
|
|
4433
|
-
if (!key.endsWith(suffix))
|
|
4434
|
-
continue;
|
|
4435
|
-
const ctx = {
|
|
4436
|
-
set: async (id3, data) => {
|
|
4437
|
-
store.set(String(id3), { _id: String(id3), ...data });
|
|
4438
|
-
},
|
|
4439
|
-
modify: async (id3, data) => {
|
|
4440
|
-
store.modify(String(id3), data);
|
|
4441
|
-
},
|
|
4442
|
-
remove: async (id3) => {
|
|
4443
|
-
store.remove(String(id3));
|
|
4444
|
-
},
|
|
4445
|
-
find: async (options) => {
|
|
4446
|
-
return store.find(options);
|
|
4447
|
-
},
|
|
4448
|
-
findOne: async (where) => {
|
|
4449
|
-
return store.findOne(where);
|
|
4450
|
-
},
|
|
4451
|
-
$auth: {}
|
|
4452
|
-
};
|
|
4453
|
-
await handler(ctx, event3);
|
|
4454
|
-
}
|
|
4473
|
+
clear(eventWire) {
|
|
4474
|
+
for (const entry of this.entries.values()) {
|
|
4475
|
+
if (entry.pendingUnsub)
|
|
4476
|
+
clearTimeout(entry.pendingUnsub);
|
|
4477
|
+
eventWire?.unsubscribeQuery(entry.subscriptionId);
|
|
4455
4478
|
}
|
|
4479
|
+
this.entries.clear();
|
|
4456
4480
|
}
|
|
4457
|
-
|
|
4458
|
-
for (const
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
clearTimeout(timeout);
|
|
4464
|
-
}
|
|
4465
|
-
this.pendingUnsubscribes.clear();
|
|
4466
|
-
for (const store of this.stores.values()) {
|
|
4467
|
-
store.clear();
|
|
4468
|
-
}
|
|
4469
|
-
}
|
|
4470
|
-
}
|
|
4471
|
-
|
|
4472
|
-
class StreamingStore {
|
|
4473
|
-
data = new Map;
|
|
4474
|
-
listeners = new Set;
|
|
4475
|
-
initialized = false;
|
|
4476
|
-
hasData() {
|
|
4477
|
-
return this.initialized;
|
|
4478
|
-
}
|
|
4479
|
-
setAll(items) {
|
|
4480
|
-
this.initialized = true;
|
|
4481
|
-
this.data.clear();
|
|
4482
|
-
for (const item of items) {
|
|
4483
|
-
this.data.set(item._id, item);
|
|
4484
|
-
}
|
|
4485
|
-
this.notifyListeners(null);
|
|
4486
|
-
}
|
|
4487
|
-
applyChanges(events) {
|
|
4488
|
-
if (events.length === 0)
|
|
4489
|
-
return;
|
|
4490
|
-
for (const event3 of events) {
|
|
4491
|
-
if (event3.type === "set" && event3.item) {
|
|
4492
|
-
this.data.set(event3.id, event3.item);
|
|
4493
|
-
} else if (event3.type === "delete") {
|
|
4494
|
-
this.data.delete(event3.id);
|
|
4481
|
+
notify(entry) {
|
|
4482
|
+
for (const listener4 of entry.listeners) {
|
|
4483
|
+
try {
|
|
4484
|
+
listener4();
|
|
4485
|
+
} catch (err) {
|
|
4486
|
+
console.error(`[Arc] Query cache listener error:`, err);
|
|
4495
4487
|
}
|
|
4496
4488
|
}
|
|
4497
|
-
this.notifyListeners(events);
|
|
4498
|
-
}
|
|
4499
|
-
set(id3, item) {
|
|
4500
|
-
this.data.set(id3, item);
|
|
4501
|
-
this.notifyListeners([{ type: "set", id: id3, item }]);
|
|
4502
|
-
}
|
|
4503
|
-
modify(id3, updates) {
|
|
4504
|
-
const existing = this.data.get(id3);
|
|
4505
|
-
if (existing) {
|
|
4506
|
-
const updated = { ...existing, ...updates };
|
|
4507
|
-
this.data.set(id3, updated);
|
|
4508
|
-
this.notifyListeners([{ type: "set", id: id3, item: updated }]);
|
|
4509
|
-
}
|
|
4510
|
-
}
|
|
4511
|
-
remove(id3) {
|
|
4512
|
-
if (this.data.delete(id3)) {
|
|
4513
|
-
this.notifyListeners([{ type: "delete", id: id3, item: null }]);
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
clear() {
|
|
4517
|
-
this.initialized = false;
|
|
4518
|
-
this.data.clear();
|
|
4519
|
-
this.notifyListeners(null);
|
|
4520
|
-
}
|
|
4521
|
-
find(options = {}) {
|
|
4522
|
-
let results = Array.from(this.data.values());
|
|
4523
|
-
if (options.where) {
|
|
4524
|
-
results = results.filter((item) => checkItemMatchesWhere(item, options.where));
|
|
4525
|
-
}
|
|
4526
|
-
return applyOrderByAndLimit(results, options);
|
|
4527
|
-
}
|
|
4528
|
-
findOne(where) {
|
|
4529
|
-
const results = this.find({ where });
|
|
4530
|
-
return results[0];
|
|
4531
|
-
}
|
|
4532
|
-
subscribe(listener4) {
|
|
4533
|
-
this.listeners.add(listener4);
|
|
4534
|
-
return () => {
|
|
4535
|
-
this.listeners.delete(listener4);
|
|
4536
|
-
};
|
|
4537
|
-
}
|
|
4538
|
-
notifyListeners(events) {
|
|
4539
|
-
for (const listener4 of this.listeners) {
|
|
4540
|
-
listener4(events);
|
|
4541
|
-
}
|
|
4542
4489
|
}
|
|
4543
4490
|
}
|
|
4544
4491
|
|
|
4545
4492
|
class StreamingEventPublisher {
|
|
4546
|
-
cache;
|
|
4547
4493
|
eventWire;
|
|
4548
4494
|
views = [];
|
|
4549
4495
|
subscribers = new Map;
|
|
4550
|
-
constructor(
|
|
4551
|
-
this.cache = cache;
|
|
4496
|
+
constructor(eventWire) {
|
|
4552
4497
|
this.eventWire = eventWire;
|
|
4553
4498
|
}
|
|
4554
4499
|
registerViews(views) {
|
|
4555
4500
|
this.views = views;
|
|
4556
|
-
this.cache.registerViews(views);
|
|
4557
4501
|
}
|
|
4558
4502
|
async publish(event3) {
|
|
4559
|
-
await this.cache.applyEvent(event3);
|
|
4560
4503
|
await this.notifySubscribers(event3);
|
|
4561
4504
|
this.eventWire.syncEvents([
|
|
4562
4505
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-adapter-db-postgres",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"postgres": "^3.4.4"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@arcote.tech/arc": "^0.7.
|
|
26
|
+
"@arcote.tech/arc": "^0.7.17"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/pg": "^8.11.0",
|