@sylphx/lens-solid 2.3.4 → 2.3.6
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 +745 -304
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1069,6 +1069,217 @@ class ReconnectionMetricsTracker {
|
|
|
1069
1069
|
return sorted[Math.max(0, index)];
|
|
1070
1070
|
}
|
|
1071
1071
|
}
|
|
1072
|
+
class SubscriptionRegistry {
|
|
1073
|
+
subscriptions = new Map;
|
|
1074
|
+
entityIndex = new Map;
|
|
1075
|
+
add(sub) {
|
|
1076
|
+
const tracked = {
|
|
1077
|
+
...sub,
|
|
1078
|
+
state: "pending",
|
|
1079
|
+
lastDataHash: sub.lastData ? hashEntityState(sub.lastData) : null,
|
|
1080
|
+
createdAt: Date.now(),
|
|
1081
|
+
lastUpdateAt: null
|
|
1082
|
+
};
|
|
1083
|
+
this.subscriptions.set(sub.id, tracked);
|
|
1084
|
+
const entityKey = `${sub.entity}:${sub.entityId}`;
|
|
1085
|
+
let ids = this.entityIndex.get(entityKey);
|
|
1086
|
+
if (!ids) {
|
|
1087
|
+
ids = new Set;
|
|
1088
|
+
this.entityIndex.set(entityKey, ids);
|
|
1089
|
+
}
|
|
1090
|
+
ids.add(sub.id);
|
|
1091
|
+
}
|
|
1092
|
+
get(id2) {
|
|
1093
|
+
return this.subscriptions.get(id2);
|
|
1094
|
+
}
|
|
1095
|
+
has(id2) {
|
|
1096
|
+
return this.subscriptions.has(id2);
|
|
1097
|
+
}
|
|
1098
|
+
remove(id2) {
|
|
1099
|
+
const sub = this.subscriptions.get(id2);
|
|
1100
|
+
if (!sub)
|
|
1101
|
+
return;
|
|
1102
|
+
this.subscriptions.delete(id2);
|
|
1103
|
+
const entityKey = `${sub.entity}:${sub.entityId}`;
|
|
1104
|
+
const ids = this.entityIndex.get(entityKey);
|
|
1105
|
+
if (ids) {
|
|
1106
|
+
ids.delete(id2);
|
|
1107
|
+
if (ids.size === 0) {
|
|
1108
|
+
this.entityIndex.delete(entityKey);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
getByEntity(entity22, entityId) {
|
|
1113
|
+
const entityKey = `${entity22}:${entityId}`;
|
|
1114
|
+
const ids = this.entityIndex.get(entityKey);
|
|
1115
|
+
if (!ids)
|
|
1116
|
+
return [];
|
|
1117
|
+
const result = [];
|
|
1118
|
+
for (const id2 of ids) {
|
|
1119
|
+
const sub = this.subscriptions.get(id2);
|
|
1120
|
+
if (sub) {
|
|
1121
|
+
result.push(sub);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return result;
|
|
1125
|
+
}
|
|
1126
|
+
updateVersion(id2, version, data) {
|
|
1127
|
+
const sub = this.subscriptions.get(id2);
|
|
1128
|
+
if (!sub)
|
|
1129
|
+
return;
|
|
1130
|
+
sub.version = version;
|
|
1131
|
+
sub.lastUpdateAt = Date.now();
|
|
1132
|
+
if (data !== undefined) {
|
|
1133
|
+
sub.lastData = data;
|
|
1134
|
+
sub.lastDataHash = hashEntityState(data);
|
|
1135
|
+
}
|
|
1136
|
+
if (sub.state === "pending" || sub.state === "reconnecting") {
|
|
1137
|
+
sub.state = "active";
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
updateData(id2, data) {
|
|
1141
|
+
const sub = this.subscriptions.get(id2);
|
|
1142
|
+
if (!sub)
|
|
1143
|
+
return;
|
|
1144
|
+
sub.lastData = data;
|
|
1145
|
+
sub.lastDataHash = hashEntityState(data);
|
|
1146
|
+
}
|
|
1147
|
+
getLastData(id2) {
|
|
1148
|
+
return this.subscriptions.get(id2)?.lastData ?? null;
|
|
1149
|
+
}
|
|
1150
|
+
getVersion(id2) {
|
|
1151
|
+
return this.subscriptions.get(id2)?.version ?? null;
|
|
1152
|
+
}
|
|
1153
|
+
markActive(id2) {
|
|
1154
|
+
const sub = this.subscriptions.get(id2);
|
|
1155
|
+
if (sub) {
|
|
1156
|
+
sub.state = "active";
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
markError(id2) {
|
|
1160
|
+
const sub = this.subscriptions.get(id2);
|
|
1161
|
+
if (sub) {
|
|
1162
|
+
sub.state = "error";
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
markAllReconnecting() {
|
|
1166
|
+
for (const sub of this.subscriptions.values()) {
|
|
1167
|
+
if (sub.state === "active") {
|
|
1168
|
+
sub.state = "reconnecting";
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
getByState(state) {
|
|
1173
|
+
const result = [];
|
|
1174
|
+
for (const sub of this.subscriptions.values()) {
|
|
1175
|
+
if (sub.state === state) {
|
|
1176
|
+
result.push(sub);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return result;
|
|
1180
|
+
}
|
|
1181
|
+
getAllForReconnect() {
|
|
1182
|
+
const result = [];
|
|
1183
|
+
for (const sub of this.subscriptions.values()) {
|
|
1184
|
+
if (sub.state === "reconnecting" || sub.state === "active") {
|
|
1185
|
+
const reconnectSub = {
|
|
1186
|
+
id: sub.id,
|
|
1187
|
+
entity: sub.entity,
|
|
1188
|
+
entityId: sub.entityId,
|
|
1189
|
+
fields: sub.fields,
|
|
1190
|
+
version: sub.version,
|
|
1191
|
+
input: sub.input
|
|
1192
|
+
};
|
|
1193
|
+
if (sub.lastDataHash) {
|
|
1194
|
+
reconnectSub.dataHash = sub.lastDataHash;
|
|
1195
|
+
}
|
|
1196
|
+
result.push(reconnectSub);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return result;
|
|
1200
|
+
}
|
|
1201
|
+
processReconnectResult(id2, version, data) {
|
|
1202
|
+
const sub = this.subscriptions.get(id2);
|
|
1203
|
+
if (!sub)
|
|
1204
|
+
return;
|
|
1205
|
+
sub.version = version;
|
|
1206
|
+
sub.state = "active";
|
|
1207
|
+
sub.lastUpdateAt = Date.now();
|
|
1208
|
+
if (data !== undefined) {
|
|
1209
|
+
sub.lastData = data;
|
|
1210
|
+
sub.lastDataHash = hashEntityState(data);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
getObserver(id2) {
|
|
1214
|
+
return this.subscriptions.get(id2)?.observer;
|
|
1215
|
+
}
|
|
1216
|
+
updateObserver(id2, observer) {
|
|
1217
|
+
const sub = this.subscriptions.get(id2);
|
|
1218
|
+
if (sub) {
|
|
1219
|
+
sub.observer = observer;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
notifyNext(id2, data) {
|
|
1223
|
+
const sub = this.subscriptions.get(id2);
|
|
1224
|
+
sub?.observer.next?.({ data, version: sub.version });
|
|
1225
|
+
}
|
|
1226
|
+
notifyError(id2, error) {
|
|
1227
|
+
this.subscriptions.get(id2)?.observer.error?.(error);
|
|
1228
|
+
}
|
|
1229
|
+
notifyAllReconnectingError(error) {
|
|
1230
|
+
for (const sub of this.subscriptions.values()) {
|
|
1231
|
+
if (sub.state === "reconnecting") {
|
|
1232
|
+
sub.observer.error?.(error);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
get size() {
|
|
1237
|
+
return this.subscriptions.size;
|
|
1238
|
+
}
|
|
1239
|
+
getIds() {
|
|
1240
|
+
return Array.from(this.subscriptions.keys());
|
|
1241
|
+
}
|
|
1242
|
+
values() {
|
|
1243
|
+
return this.subscriptions.values();
|
|
1244
|
+
}
|
|
1245
|
+
getStats() {
|
|
1246
|
+
const byState = {
|
|
1247
|
+
pending: 0,
|
|
1248
|
+
active: 0,
|
|
1249
|
+
reconnecting: 0,
|
|
1250
|
+
error: 0
|
|
1251
|
+
};
|
|
1252
|
+
const byEntity = {};
|
|
1253
|
+
for (const sub of this.subscriptions.values()) {
|
|
1254
|
+
byState[sub.state]++;
|
|
1255
|
+
const entityKey = `${sub.entity}:${sub.entityId}`;
|
|
1256
|
+
byEntity[entityKey] = (byEntity[entityKey] ?? 0) + 1;
|
|
1257
|
+
}
|
|
1258
|
+
return {
|
|
1259
|
+
total: this.subscriptions.size,
|
|
1260
|
+
byState,
|
|
1261
|
+
byEntity
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
clear() {
|
|
1265
|
+
for (const sub of this.subscriptions.values()) {
|
|
1266
|
+
sub.observer.complete?.();
|
|
1267
|
+
}
|
|
1268
|
+
this.subscriptions.clear();
|
|
1269
|
+
this.entityIndex.clear();
|
|
1270
|
+
}
|
|
1271
|
+
clearErrors() {
|
|
1272
|
+
const toRemove = [];
|
|
1273
|
+
for (const [id2, sub] of this.subscriptions) {
|
|
1274
|
+
if (sub.state === "error") {
|
|
1275
|
+
toRemove.push(id2);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
for (const id2 of toRemove) {
|
|
1279
|
+
this.remove(id2);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1072
1283
|
function applyOps(state, ops) {
|
|
1073
1284
|
let result = state;
|
|
1074
1285
|
for (const op2 of ops) {
|
|
@@ -1310,6 +1521,282 @@ function isError(msg) {
|
|
|
1310
1521
|
}
|
|
1311
1522
|
|
|
1312
1523
|
// ../client/dist/index.js
|
|
1524
|
+
class SelectionRegistry {
|
|
1525
|
+
endpoints = new Map;
|
|
1526
|
+
addSubscriber(params) {
|
|
1527
|
+
const { endpointKey, subscriberId, selection, onData, onError } = params;
|
|
1528
|
+
let endpoint = this.endpoints.get(endpointKey);
|
|
1529
|
+
const previousSelection = endpoint ? { ...endpoint.mergedSelection } : {};
|
|
1530
|
+
if (!endpoint) {
|
|
1531
|
+
endpoint = {
|
|
1532
|
+
key: endpointKey,
|
|
1533
|
+
subscribers: new Map,
|
|
1534
|
+
mergedSelection: {},
|
|
1535
|
+
lastData: null,
|
|
1536
|
+
isSubscribed: false,
|
|
1537
|
+
createdAt: Date.now(),
|
|
1538
|
+
lastSelectionChangeAt: null
|
|
1539
|
+
};
|
|
1540
|
+
this.endpoints.set(endpointKey, endpoint);
|
|
1541
|
+
}
|
|
1542
|
+
const subscriberMeta = {
|
|
1543
|
+
id: subscriberId,
|
|
1544
|
+
selection,
|
|
1545
|
+
onData,
|
|
1546
|
+
createdAt: Date.now()
|
|
1547
|
+
};
|
|
1548
|
+
if (onError) {
|
|
1549
|
+
subscriberMeta.onError = onError;
|
|
1550
|
+
}
|
|
1551
|
+
endpoint.subscribers.set(subscriberId, subscriberMeta);
|
|
1552
|
+
const newSelection = this.computeMergedSelection(endpoint);
|
|
1553
|
+
const analysis = this.analyzeSelectionChange(previousSelection, newSelection);
|
|
1554
|
+
if (analysis.hasChanged) {
|
|
1555
|
+
endpoint.mergedSelection = newSelection;
|
|
1556
|
+
endpoint.lastSelectionChangeAt = Date.now();
|
|
1557
|
+
}
|
|
1558
|
+
return analysis;
|
|
1559
|
+
}
|
|
1560
|
+
removeSubscriber(endpointKey, subscriberId) {
|
|
1561
|
+
const endpoint = this.endpoints.get(endpointKey);
|
|
1562
|
+
if (!endpoint) {
|
|
1563
|
+
return this.noChangeAnalysis();
|
|
1564
|
+
}
|
|
1565
|
+
const previousSelection = { ...endpoint.mergedSelection };
|
|
1566
|
+
endpoint.subscribers.delete(subscriberId);
|
|
1567
|
+
if (endpoint.subscribers.size === 0) {
|
|
1568
|
+
this.endpoints.delete(endpointKey);
|
|
1569
|
+
return this.analyzeSelectionChange(previousSelection, {});
|
|
1570
|
+
}
|
|
1571
|
+
const newSelection = this.computeMergedSelection(endpoint);
|
|
1572
|
+
const analysis = this.analyzeSelectionChange(previousSelection, newSelection);
|
|
1573
|
+
if (analysis.hasChanged) {
|
|
1574
|
+
endpoint.mergedSelection = newSelection;
|
|
1575
|
+
endpoint.lastSelectionChangeAt = Date.now();
|
|
1576
|
+
}
|
|
1577
|
+
return analysis;
|
|
1578
|
+
}
|
|
1579
|
+
getMergedSelection(endpointKey) {
|
|
1580
|
+
return this.endpoints.get(endpointKey)?.mergedSelection ?? null;
|
|
1581
|
+
}
|
|
1582
|
+
getSubscriberIds(endpointKey) {
|
|
1583
|
+
const endpoint = this.endpoints.get(endpointKey);
|
|
1584
|
+
return endpoint ? Array.from(endpoint.subscribers.keys()) : [];
|
|
1585
|
+
}
|
|
1586
|
+
getSubscriberCount(endpointKey) {
|
|
1587
|
+
return this.endpoints.get(endpointKey)?.subscribers.size ?? 0;
|
|
1588
|
+
}
|
|
1589
|
+
hasSubscribers(endpointKey) {
|
|
1590
|
+
return (this.endpoints.get(endpointKey)?.subscribers.size ?? 0) > 0;
|
|
1591
|
+
}
|
|
1592
|
+
distributeData(endpointKey, data) {
|
|
1593
|
+
const endpoint = this.endpoints.get(endpointKey);
|
|
1594
|
+
if (!endpoint)
|
|
1595
|
+
return;
|
|
1596
|
+
endpoint.lastData = data;
|
|
1597
|
+
for (const subscriber of endpoint.subscribers.values()) {
|
|
1598
|
+
try {
|
|
1599
|
+
const filteredData = filterToSelection(data, subscriber.selection);
|
|
1600
|
+
subscriber.onData(filteredData);
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
if (subscriber.onError) {
|
|
1603
|
+
subscriber.onError(error instanceof Error ? error : new Error(String(error)));
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
distributeError(endpointKey, error) {
|
|
1609
|
+
const endpoint = this.endpoints.get(endpointKey);
|
|
1610
|
+
if (!endpoint)
|
|
1611
|
+
return;
|
|
1612
|
+
for (const subscriber of endpoint.subscribers.values()) {
|
|
1613
|
+
subscriber.onError?.(error);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
getLastData(endpointKey) {
|
|
1617
|
+
return this.endpoints.get(endpointKey)?.lastData ?? null;
|
|
1618
|
+
}
|
|
1619
|
+
markSubscribed(endpointKey) {
|
|
1620
|
+
const endpoint = this.endpoints.get(endpointKey);
|
|
1621
|
+
if (endpoint) {
|
|
1622
|
+
endpoint.isSubscribed = true;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
markUnsubscribed(endpointKey) {
|
|
1626
|
+
const endpoint = this.endpoints.get(endpointKey);
|
|
1627
|
+
if (endpoint) {
|
|
1628
|
+
endpoint.isSubscribed = false;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
isSubscribed(endpointKey) {
|
|
1632
|
+
return this.endpoints.get(endpointKey)?.isSubscribed ?? false;
|
|
1633
|
+
}
|
|
1634
|
+
getEndpointKeys() {
|
|
1635
|
+
return Array.from(this.endpoints.keys());
|
|
1636
|
+
}
|
|
1637
|
+
clear() {
|
|
1638
|
+
this.endpoints.clear();
|
|
1639
|
+
}
|
|
1640
|
+
getStats() {
|
|
1641
|
+
let totalSubscribers = 0;
|
|
1642
|
+
for (const endpoint of this.endpoints.values()) {
|
|
1643
|
+
totalSubscribers += endpoint.subscribers.size;
|
|
1644
|
+
}
|
|
1645
|
+
return {
|
|
1646
|
+
endpointCount: this.endpoints.size,
|
|
1647
|
+
totalSubscribers,
|
|
1648
|
+
avgSubscribersPerEndpoint: this.endpoints.size > 0 ? totalSubscribers / this.endpoints.size : 0
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
computeMergedSelection(endpoint) {
|
|
1652
|
+
const selections = Array.from(endpoint.subscribers.values()).map((s) => s.selection);
|
|
1653
|
+
return mergeSelections(selections);
|
|
1654
|
+
}
|
|
1655
|
+
analyzeSelectionChange(previous, next) {
|
|
1656
|
+
const previousFields = this.flattenSelectionKeys(previous);
|
|
1657
|
+
const nextFields = this.flattenSelectionKeys(next);
|
|
1658
|
+
const addedFields = new Set;
|
|
1659
|
+
const removedFields = new Set;
|
|
1660
|
+
for (const field of nextFields) {
|
|
1661
|
+
if (!previousFields.has(field)) {
|
|
1662
|
+
addedFields.add(field);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
for (const field of previousFields) {
|
|
1666
|
+
if (!nextFields.has(field)) {
|
|
1667
|
+
removedFields.add(field);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
const hasChanged = addedFields.size > 0 || removedFields.size > 0;
|
|
1671
|
+
return {
|
|
1672
|
+
hasChanged,
|
|
1673
|
+
previousSelection: previous,
|
|
1674
|
+
newSelection: next,
|
|
1675
|
+
addedFields,
|
|
1676
|
+
removedFields,
|
|
1677
|
+
isExpanded: addedFields.size > 0,
|
|
1678
|
+
isShrunk: removedFields.size > 0
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
flattenSelectionKeys(selection, prefix = "") {
|
|
1682
|
+
const keys = new Set;
|
|
1683
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
1684
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
1685
|
+
keys.add(path);
|
|
1686
|
+
if (typeof value === "boolean") {
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
if (typeof value === "object" && value !== null) {
|
|
1690
|
+
let nestedSelection;
|
|
1691
|
+
if ("select" in value && typeof value.select === "object") {
|
|
1692
|
+
nestedSelection = value.select;
|
|
1693
|
+
} else if (!("input" in value)) {
|
|
1694
|
+
nestedSelection = value;
|
|
1695
|
+
}
|
|
1696
|
+
if (nestedSelection) {
|
|
1697
|
+
const nestedKeys = this.flattenSelectionKeys(nestedSelection, path);
|
|
1698
|
+
for (const nestedKey of nestedKeys) {
|
|
1699
|
+
keys.add(nestedKey);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return keys;
|
|
1705
|
+
}
|
|
1706
|
+
noChangeAnalysis() {
|
|
1707
|
+
return {
|
|
1708
|
+
hasChanged: false,
|
|
1709
|
+
previousSelection: {},
|
|
1710
|
+
newSelection: {},
|
|
1711
|
+
addedFields: new Set,
|
|
1712
|
+
removedFields: new Set,
|
|
1713
|
+
isExpanded: false,
|
|
1714
|
+
isShrunk: false
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
function mergeSelections(selections) {
|
|
1719
|
+
if (selections.length === 0)
|
|
1720
|
+
return {};
|
|
1721
|
+
if (selections.length === 1)
|
|
1722
|
+
return selections[0];
|
|
1723
|
+
const merged = {};
|
|
1724
|
+
const allKeys = new Set;
|
|
1725
|
+
for (const selection of selections) {
|
|
1726
|
+
for (const key of Object.keys(selection)) {
|
|
1727
|
+
allKeys.add(key);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
for (const key of allKeys) {
|
|
1731
|
+
const values = selections.map((s) => s[key]).filter((v) => v !== undefined && v !== null);
|
|
1732
|
+
if (values.length === 0)
|
|
1733
|
+
continue;
|
|
1734
|
+
if (values.some((v) => v === true)) {
|
|
1735
|
+
merged[key] = true;
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
const nestedSelections = [];
|
|
1739
|
+
let lastInput;
|
|
1740
|
+
for (const value of values) {
|
|
1741
|
+
if (typeof value === "object" && value !== null) {
|
|
1742
|
+
if ("select" in value && typeof value.select === "object") {
|
|
1743
|
+
nestedSelections.push(value.select);
|
|
1744
|
+
if ("input" in value) {
|
|
1745
|
+
lastInput = value.input;
|
|
1746
|
+
}
|
|
1747
|
+
} else if ("input" in value) {
|
|
1748
|
+
lastInput = value.input;
|
|
1749
|
+
merged[key] = { input: lastInput };
|
|
1750
|
+
} else {
|
|
1751
|
+
nestedSelections.push(value);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
if (nestedSelections.length > 0) {
|
|
1756
|
+
const mergedNested = mergeSelections(nestedSelections);
|
|
1757
|
+
if (lastInput !== undefined) {
|
|
1758
|
+
merged[key] = { input: lastInput, select: mergedNested };
|
|
1759
|
+
} else {
|
|
1760
|
+
merged[key] = mergedNested;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return merged;
|
|
1765
|
+
}
|
|
1766
|
+
function filterToSelection(data, selection) {
|
|
1767
|
+
if (data == null)
|
|
1768
|
+
return data;
|
|
1769
|
+
if (Array.isArray(data)) {
|
|
1770
|
+
return data.map((item) => filterToSelection(item, selection));
|
|
1771
|
+
}
|
|
1772
|
+
if (typeof data !== "object")
|
|
1773
|
+
return data;
|
|
1774
|
+
const obj = data;
|
|
1775
|
+
const result = {};
|
|
1776
|
+
if ("id" in obj) {
|
|
1777
|
+
result.id = obj.id;
|
|
1778
|
+
}
|
|
1779
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
1780
|
+
if (!(key in obj))
|
|
1781
|
+
continue;
|
|
1782
|
+
if (value === true) {
|
|
1783
|
+
result[key] = obj[key];
|
|
1784
|
+
} else if (typeof value === "object" && value !== null) {
|
|
1785
|
+
let nestedSelection = null;
|
|
1786
|
+
if ("select" in value && typeof value.select === "object") {
|
|
1787
|
+
nestedSelection = value.select;
|
|
1788
|
+
} else if (!("input" in value)) {
|
|
1789
|
+
nestedSelection = value;
|
|
1790
|
+
}
|
|
1791
|
+
if (nestedSelection) {
|
|
1792
|
+
result[key] = filterToSelection(obj[key], nestedSelection);
|
|
1793
|
+
} else {
|
|
1794
|
+
result[key] = obj[key];
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return result;
|
|
1799
|
+
}
|
|
1313
1800
|
function hasAnySubscription(entities, entityName, select, visited = new Set) {
|
|
1314
1801
|
if (!entities)
|
|
1315
1802
|
return false;
|
|
@@ -1341,13 +1828,19 @@ function hasAnySubscription(entities, entityName, select, visited = new Set) {
|
|
|
1341
1828
|
}
|
|
1342
1829
|
return false;
|
|
1343
1830
|
}
|
|
1831
|
+
var subscriberIdCounter = 0;
|
|
1832
|
+
function generateSubscriberId() {
|
|
1833
|
+
return `sub_${Date.now()}_${++subscriberIdCounter}`;
|
|
1834
|
+
}
|
|
1344
1835
|
|
|
1345
1836
|
class ClientImpl {
|
|
1346
1837
|
transport;
|
|
1347
1838
|
plugins;
|
|
1348
1839
|
metadata = null;
|
|
1349
1840
|
connectPromise = null;
|
|
1350
|
-
|
|
1841
|
+
endpoints = new Map;
|
|
1842
|
+
pendingBatches = new Map;
|
|
1843
|
+
batchScheduled = false;
|
|
1351
1844
|
queryResultCache = new Map;
|
|
1352
1845
|
observerEntries = new WeakMap;
|
|
1353
1846
|
constructor(config) {
|
|
@@ -1463,7 +1956,7 @@ class ClientImpl {
|
|
|
1463
1956
|
return `${type}-${path}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1464
1957
|
}
|
|
1465
1958
|
inputHashCache = new WeakMap;
|
|
1466
|
-
|
|
1959
|
+
makeEndpointKey(path, input) {
|
|
1467
1960
|
if (input === undefined || input === null) {
|
|
1468
1961
|
return `${path}:null`;
|
|
1469
1962
|
}
|
|
@@ -1478,69 +1971,231 @@ class ClientImpl {
|
|
|
1478
1971
|
}
|
|
1479
1972
|
return `${path}:${hash}`;
|
|
1480
1973
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1974
|
+
makeQueryResultKey(endpointKey, select) {
|
|
1975
|
+
if (!select)
|
|
1976
|
+
return endpointKey;
|
|
1977
|
+
return `${endpointKey}:${JSON.stringify(select)}`;
|
|
1978
|
+
}
|
|
1979
|
+
getOrCreateEndpoint(key) {
|
|
1980
|
+
let endpoint = this.endpoints.get(key);
|
|
1981
|
+
if (!endpoint) {
|
|
1982
|
+
endpoint = {
|
|
1489
1983
|
data: null,
|
|
1490
1984
|
error: null,
|
|
1491
1985
|
completed: false,
|
|
1492
|
-
observers: new
|
|
1493
|
-
|
|
1494
|
-
|
|
1986
|
+
observers: new Map,
|
|
1987
|
+
mergedSelection: undefined,
|
|
1988
|
+
isSubscribed: false
|
|
1989
|
+
};
|
|
1990
|
+
this.endpoints.set(key, endpoint);
|
|
1991
|
+
}
|
|
1992
|
+
return endpoint;
|
|
1993
|
+
}
|
|
1994
|
+
addObserver(key, observer) {
|
|
1995
|
+
const endpoint = this.getOrCreateEndpoint(key);
|
|
1996
|
+
const previousSelection = endpoint.mergedSelection;
|
|
1997
|
+
endpoint.observers.set(observer.id, observer);
|
|
1998
|
+
const selections = Array.from(endpoint.observers.values()).map((o) => o.selection).filter((s) => s !== undefined);
|
|
1999
|
+
endpoint.mergedSelection = selections.length > 0 ? mergeSelections(selections) : undefined;
|
|
2000
|
+
const selectionChanged = JSON.stringify(previousSelection) !== JSON.stringify(endpoint.mergedSelection);
|
|
2001
|
+
const isExpanded = selectionChanged && this.isSelectionExpanded(previousSelection, endpoint.mergedSelection);
|
|
2002
|
+
return { endpoint, selectionChanged, isExpanded };
|
|
2003
|
+
}
|
|
2004
|
+
removeObserver(key, observerId) {
|
|
2005
|
+
const endpoint = this.endpoints.get(key);
|
|
2006
|
+
if (!endpoint)
|
|
2007
|
+
return { endpoint: undefined, shouldUnsubscribe: false };
|
|
2008
|
+
endpoint.observers.delete(observerId);
|
|
2009
|
+
if (endpoint.observers.size === 0) {
|
|
2010
|
+
return { endpoint, shouldUnsubscribe: true };
|
|
2011
|
+
}
|
|
2012
|
+
const selections = Array.from(endpoint.observers.values()).map((o) => o.selection).filter((s) => s !== undefined);
|
|
2013
|
+
endpoint.mergedSelection = selections.length > 0 ? mergeSelections(selections) : undefined;
|
|
2014
|
+
return { endpoint, shouldUnsubscribe: false };
|
|
2015
|
+
}
|
|
2016
|
+
isSelectionExpanded(previous, current) {
|
|
2017
|
+
if (!previous)
|
|
2018
|
+
return current !== undefined;
|
|
2019
|
+
if (!current)
|
|
2020
|
+
return false;
|
|
2021
|
+
const previousKeys = this.flattenSelectionKeys(previous);
|
|
2022
|
+
const currentKeys = this.flattenSelectionKeys(current);
|
|
2023
|
+
for (const key of currentKeys) {
|
|
2024
|
+
if (!previousKeys.has(key))
|
|
2025
|
+
return true;
|
|
2026
|
+
}
|
|
2027
|
+
return false;
|
|
2028
|
+
}
|
|
2029
|
+
flattenSelectionKeys(selection, prefix = "") {
|
|
2030
|
+
const keys = new Set;
|
|
2031
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
2032
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
2033
|
+
keys.add(path);
|
|
2034
|
+
if (typeof value === "boolean") {
|
|
2035
|
+
continue;
|
|
2036
|
+
}
|
|
2037
|
+
if (typeof value === "object" && value !== null) {
|
|
2038
|
+
const nested = "select" in value ? value.select : value;
|
|
2039
|
+
if (nested && typeof nested === "object") {
|
|
2040
|
+
for (const nestedKey of this.flattenSelectionKeys(nested, path)) {
|
|
2041
|
+
keys.add(nestedKey);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return keys;
|
|
2047
|
+
}
|
|
2048
|
+
distributeData(endpoint, data) {
|
|
2049
|
+
endpoint.data = data;
|
|
2050
|
+
endpoint.error = null;
|
|
2051
|
+
for (const observer of endpoint.observers.values()) {
|
|
2052
|
+
if (observer.next) {
|
|
2053
|
+
const filteredData = observer.selection ? filterToSelection(data, observer.selection) : data;
|
|
2054
|
+
observer.next(filteredData);
|
|
2055
|
+
}
|
|
1495
2056
|
}
|
|
1496
|
-
|
|
2057
|
+
}
|
|
2058
|
+
distributeError(endpoint, error) {
|
|
2059
|
+
endpoint.error = error;
|
|
2060
|
+
for (const observer of endpoint.observers.values()) {
|
|
2061
|
+
observer.error?.(error);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
scheduleBatchedQuery(key, path, input, selection) {
|
|
2065
|
+
return new Promise((resolve, reject) => {
|
|
2066
|
+
const observerId = generateSubscriberId();
|
|
2067
|
+
let batch = this.pendingBatches.get(key);
|
|
2068
|
+
if (!batch) {
|
|
2069
|
+
batch = {
|
|
2070
|
+
path,
|
|
2071
|
+
input,
|
|
2072
|
+
observers: [],
|
|
2073
|
+
mergedSelection: undefined
|
|
2074
|
+
};
|
|
2075
|
+
this.pendingBatches.set(key, batch);
|
|
2076
|
+
}
|
|
2077
|
+
batch.observers.push({ id: observerId, selection, resolve, reject });
|
|
2078
|
+
const selections = batch.observers.map((o) => o.selection).filter((s) => s !== undefined);
|
|
2079
|
+
batch.mergedSelection = selections.length > 0 ? mergeSelections(selections) : undefined;
|
|
2080
|
+
if (!this.batchScheduled) {
|
|
2081
|
+
this.batchScheduled = true;
|
|
2082
|
+
queueMicrotask(() => this.flushBatches());
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
async flushBatches() {
|
|
2087
|
+
this.batchScheduled = false;
|
|
2088
|
+
const batches = Array.from(this.pendingBatches.entries());
|
|
2089
|
+
this.pendingBatches.clear();
|
|
2090
|
+
await Promise.all(batches.map(async ([key, batch]) => {
|
|
2091
|
+
try {
|
|
2092
|
+
const op2 = {
|
|
2093
|
+
id: this.generateId("query", batch.path),
|
|
2094
|
+
path: batch.path,
|
|
2095
|
+
type: "query",
|
|
2096
|
+
input: batch.input,
|
|
2097
|
+
meta: batch.mergedSelection ? { select: batch.mergedSelection } : {}
|
|
2098
|
+
};
|
|
2099
|
+
const response = await this.execute(op2);
|
|
2100
|
+
if (isError(response)) {
|
|
2101
|
+
const error = new Error(response.error);
|
|
2102
|
+
for (const observer of batch.observers) {
|
|
2103
|
+
observer.reject(error);
|
|
2104
|
+
}
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
if (isSnapshot(response)) {
|
|
2108
|
+
const endpoint = this.getOrCreateEndpoint(key);
|
|
2109
|
+
endpoint.data = response.data;
|
|
2110
|
+
for (const observer of batch.observers) {
|
|
2111
|
+
const filteredData = observer.selection ? filterToSelection(response.data, observer.selection) : response.data;
|
|
2112
|
+
observer.resolve(filteredData);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2117
|
+
for (const observer of batch.observers) {
|
|
2118
|
+
observer.reject(err);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
}));
|
|
2122
|
+
}
|
|
2123
|
+
executeQuery(path, input, select) {
|
|
2124
|
+
const key = this.makeEndpointKey(path, input);
|
|
2125
|
+
const cacheKey = this.makeQueryResultKey(key, select);
|
|
2126
|
+
const cached = this.queryResultCache.get(cacheKey);
|
|
2127
|
+
if (cached) {
|
|
2128
|
+
return cached;
|
|
2129
|
+
}
|
|
2130
|
+
const endpoint = this.getOrCreateEndpoint(key);
|
|
1497
2131
|
const result = {
|
|
1498
2132
|
get value() {
|
|
1499
|
-
|
|
2133
|
+
if (endpoint.data === null)
|
|
2134
|
+
return null;
|
|
2135
|
+
return select ? filterToSelection(endpoint.data, select) : endpoint.data;
|
|
1500
2136
|
},
|
|
1501
2137
|
subscribe: (observerOrCallback) => {
|
|
2138
|
+
const observerId = generateSubscriberId();
|
|
1502
2139
|
let entry;
|
|
1503
2140
|
if (typeof observerOrCallback === "function") {
|
|
1504
2141
|
const callback = observerOrCallback;
|
|
1505
|
-
entry = {
|
|
2142
|
+
entry = {
|
|
2143
|
+
id: observerId,
|
|
2144
|
+
selection: select,
|
|
2145
|
+
next: (data) => callback(data)
|
|
2146
|
+
};
|
|
1506
2147
|
} else if (observerOrCallback && typeof observerOrCallback === "object") {
|
|
1507
2148
|
const observer = observerOrCallback;
|
|
1508
2149
|
entry = {
|
|
2150
|
+
id: observerId,
|
|
2151
|
+
selection: select,
|
|
1509
2152
|
next: observer.next ? (data) => observer.next(data) : undefined,
|
|
1510
2153
|
error: observer.error,
|
|
1511
2154
|
complete: observer.complete
|
|
1512
2155
|
};
|
|
1513
2156
|
} else {
|
|
1514
|
-
entry = {};
|
|
2157
|
+
entry = { id: observerId, selection: select };
|
|
1515
2158
|
}
|
|
1516
2159
|
if (observerOrCallback) {
|
|
1517
2160
|
this.observerEntries.set(observerOrCallback, entry);
|
|
1518
2161
|
}
|
|
1519
|
-
|
|
1520
|
-
if (
|
|
1521
|
-
|
|
1522
|
-
} else if (
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
}
|
|
1528
|
-
if (!sub.unsubscribe) {
|
|
2162
|
+
const { endpoint: ep, isExpanded } = this.addObserver(key, entry);
|
|
2163
|
+
if (!ep.isSubscribed) {
|
|
2164
|
+
this.startSubscription(path, input, key);
|
|
2165
|
+
} else if (isExpanded) {
|
|
2166
|
+
if (ep.unsubscribe) {
|
|
2167
|
+
ep.unsubscribe();
|
|
2168
|
+
}
|
|
2169
|
+
ep.isSubscribed = false;
|
|
1529
2170
|
this.startSubscription(path, input, key);
|
|
2171
|
+
} else {
|
|
2172
|
+
if (ep.error && entry.error) {
|
|
2173
|
+
entry.error(ep.error);
|
|
2174
|
+
} else if (ep.data !== null && entry.next) {
|
|
2175
|
+
const filteredData = select ? filterToSelection(ep.data, select) : ep.data;
|
|
2176
|
+
entry.next(filteredData);
|
|
2177
|
+
}
|
|
2178
|
+
if (ep.completed && entry.complete) {
|
|
2179
|
+
entry.complete();
|
|
2180
|
+
}
|
|
1530
2181
|
}
|
|
1531
2182
|
return () => {
|
|
1532
2183
|
if (observerOrCallback) {
|
|
1533
2184
|
const storedEntry = this.observerEntries.get(observerOrCallback);
|
|
1534
2185
|
if (storedEntry) {
|
|
1535
|
-
|
|
2186
|
+
const { shouldUnsubscribe } = this.removeObserver(key, storedEntry.id);
|
|
2187
|
+
if (shouldUnsubscribe) {
|
|
2188
|
+
ep.unsubscribe?.();
|
|
2189
|
+
ep.isSubscribed = false;
|
|
2190
|
+
this.endpoints.delete(key);
|
|
2191
|
+
for (const [k] of this.queryResultCache) {
|
|
2192
|
+
if (k.startsWith(key)) {
|
|
2193
|
+
this.queryResultCache.delete(k);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
1536
2197
|
}
|
|
1537
2198
|
}
|
|
1538
|
-
if (sub.observers.size === 0 && sub.unsubscribe) {
|
|
1539
|
-
sub.unsubscribe();
|
|
1540
|
-
sub.unsubscribe = undefined;
|
|
1541
|
-
this.subscriptions.delete(key);
|
|
1542
|
-
this.queryResultCache.delete(key);
|
|
1543
|
-
}
|
|
1544
2199
|
};
|
|
1545
2200
|
},
|
|
1546
2201
|
select: (selection) => {
|
|
@@ -1548,30 +2203,17 @@ class ClientImpl {
|
|
|
1548
2203
|
},
|
|
1549
2204
|
then: async (onfulfilled, onrejected) => {
|
|
1550
2205
|
try {
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
input,
|
|
1556
|
-
meta: select ? { select } : {}
|
|
1557
|
-
};
|
|
1558
|
-
const response = await this.execute(op2);
|
|
1559
|
-
if (isError(response)) {
|
|
1560
|
-
throw new Error(response.error);
|
|
2206
|
+
const data = await this.scheduleBatchedQuery(key, path, input, select);
|
|
2207
|
+
const ep = this.getOrCreateEndpoint(key);
|
|
2208
|
+
if (ep.data === null) {
|
|
2209
|
+
ep.data = data;
|
|
1561
2210
|
}
|
|
1562
|
-
|
|
1563
|
-
sub.data = response.data;
|
|
1564
|
-
for (const observer of sub.observers) {
|
|
1565
|
-
observer.next?.(response.data);
|
|
1566
|
-
}
|
|
1567
|
-
return onfulfilled ? onfulfilled(response.data) : response.data;
|
|
1568
|
-
}
|
|
1569
|
-
return onfulfilled ? onfulfilled(sub.data) : sub.data;
|
|
2211
|
+
return onfulfilled ? onfulfilled(data) : data;
|
|
1570
2212
|
} catch (error) {
|
|
1571
2213
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
2214
|
+
const ep = this.endpoints.get(key);
|
|
2215
|
+
if (ep) {
|
|
2216
|
+
ep.error = err;
|
|
1575
2217
|
}
|
|
1576
2218
|
if (onrejected) {
|
|
1577
2219
|
return onrejected(error);
|
|
@@ -1580,78 +2222,77 @@ class ClientImpl {
|
|
|
1580
2222
|
}
|
|
1581
2223
|
}
|
|
1582
2224
|
};
|
|
1583
|
-
|
|
1584
|
-
this.queryResultCache.set(key, result);
|
|
1585
|
-
}
|
|
2225
|
+
this.queryResultCache.set(cacheKey, result);
|
|
1586
2226
|
return result;
|
|
1587
2227
|
}
|
|
1588
2228
|
async startSubscription(path, input, key) {
|
|
1589
|
-
const
|
|
1590
|
-
if (!
|
|
2229
|
+
const endpoint = this.endpoints.get(key);
|
|
2230
|
+
if (!endpoint)
|
|
1591
2231
|
return;
|
|
1592
|
-
|
|
1593
|
-
|
|
2232
|
+
endpoint.isSubscribed = true;
|
|
2233
|
+
try {
|
|
2234
|
+
await this.ensureConnected();
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
endpoint.isSubscribed = false;
|
|
2237
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2238
|
+
this.distributeError(endpoint, err);
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
const meta = this.getOperationMeta(path);
|
|
2242
|
+
if (meta?.type === "mutation") {
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
const isSubscription = this.requiresSubscription(path, endpoint.mergedSelection);
|
|
1594
2246
|
if (isSubscription) {
|
|
1595
2247
|
const op2 = {
|
|
1596
2248
|
id: this.generateId("subscription", path),
|
|
1597
2249
|
path,
|
|
1598
2250
|
type: "subscription",
|
|
1599
|
-
input
|
|
2251
|
+
input,
|
|
2252
|
+
meta: endpoint.mergedSelection ? { select: endpoint.mergedSelection } : {}
|
|
1600
2253
|
};
|
|
1601
2254
|
const resultOrObservable = this.transport.execute(op2);
|
|
1602
2255
|
if (this.isObservable(resultOrObservable)) {
|
|
1603
2256
|
const subscription = resultOrObservable.subscribe({
|
|
1604
2257
|
next: (message) => {
|
|
1605
2258
|
if (isSnapshot(message)) {
|
|
1606
|
-
|
|
1607
|
-
sub.error = null;
|
|
1608
|
-
for (const observer of sub.observers) {
|
|
1609
|
-
observer.next?.(message.data);
|
|
1610
|
-
}
|
|
2259
|
+
this.distributeData(endpoint, message.data);
|
|
1611
2260
|
} else if (isOps(message)) {
|
|
1612
2261
|
try {
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
for (const observer of sub.observers) {
|
|
1616
|
-
observer.next?.(sub.data);
|
|
1617
|
-
}
|
|
2262
|
+
const newData = applyOps(endpoint.data, message.ops);
|
|
2263
|
+
this.distributeData(endpoint, newData);
|
|
1618
2264
|
} catch (updateErr) {
|
|
1619
2265
|
const err = updateErr instanceof Error ? updateErr : new Error(String(updateErr));
|
|
1620
|
-
|
|
1621
|
-
for (const observer of sub.observers) {
|
|
1622
|
-
observer.error?.(err);
|
|
1623
|
-
}
|
|
2266
|
+
this.distributeError(endpoint, err);
|
|
1624
2267
|
}
|
|
1625
2268
|
} else if (isError(message)) {
|
|
1626
|
-
|
|
1627
|
-
sub.error = err;
|
|
1628
|
-
for (const observer of sub.observers) {
|
|
1629
|
-
observer.error?.(err);
|
|
1630
|
-
}
|
|
2269
|
+
this.distributeError(endpoint, new Error(message.error));
|
|
1631
2270
|
}
|
|
1632
2271
|
},
|
|
1633
2272
|
error: (err) => {
|
|
1634
|
-
|
|
1635
|
-
for (const observer of sub.observers) {
|
|
1636
|
-
observer.error?.(err);
|
|
1637
|
-
}
|
|
2273
|
+
this.distributeError(endpoint, err);
|
|
1638
2274
|
},
|
|
1639
2275
|
complete: () => {
|
|
1640
|
-
|
|
1641
|
-
for (const observer of
|
|
2276
|
+
endpoint.completed = true;
|
|
2277
|
+
for (const observer of endpoint.observers.values()) {
|
|
1642
2278
|
observer.complete?.();
|
|
1643
2279
|
}
|
|
1644
2280
|
}
|
|
1645
2281
|
});
|
|
1646
|
-
|
|
2282
|
+
endpoint.unsubscribe = () => subscription.unsubscribe();
|
|
1647
2283
|
}
|
|
1648
2284
|
} else {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
2285
|
+
try {
|
|
2286
|
+
const data = await this.scheduleBatchedQuery(key, path, input, endpoint.mergedSelection);
|
|
2287
|
+
this.distributeData(endpoint, data);
|
|
2288
|
+
endpoint.completed = true;
|
|
2289
|
+
for (const observer of endpoint.observers.values()) {
|
|
1652
2290
|
observer.complete?.();
|
|
1653
2291
|
}
|
|
1654
|
-
})
|
|
2292
|
+
} catch (error) {
|
|
2293
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2294
|
+
this.distributeError(endpoint, err);
|
|
2295
|
+
}
|
|
1655
2296
|
}
|
|
1656
2297
|
}
|
|
1657
2298
|
async executeMutation(path, input, select) {
|
|
@@ -1699,6 +2340,17 @@ class ClientImpl {
|
|
|
1699
2340
|
return queryResult;
|
|
1700
2341
|
};
|
|
1701
2342
|
}
|
|
2343
|
+
getStats() {
|
|
2344
|
+
let totalObservers = 0;
|
|
2345
|
+
for (const endpoint of this.endpoints.values()) {
|
|
2346
|
+
totalObservers += endpoint.observers.size;
|
|
2347
|
+
}
|
|
2348
|
+
return {
|
|
2349
|
+
endpointCount: this.endpoints.size,
|
|
2350
|
+
totalObservers,
|
|
2351
|
+
pendingBatches: this.pendingBatches.size
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
1702
2354
|
}
|
|
1703
2355
|
function createClient(config) {
|
|
1704
2356
|
const impl = new ClientImpl(config);
|
|
@@ -1883,217 +2535,6 @@ http.server = function httpServer(options) {
|
|
|
1883
2535
|
}
|
|
1884
2536
|
};
|
|
1885
2537
|
};
|
|
1886
|
-
class SubscriptionRegistry {
|
|
1887
|
-
subscriptions = new Map;
|
|
1888
|
-
entityIndex = new Map;
|
|
1889
|
-
add(sub) {
|
|
1890
|
-
const tracked = {
|
|
1891
|
-
...sub,
|
|
1892
|
-
state: "pending",
|
|
1893
|
-
lastDataHash: sub.lastData ? hashEntityState(sub.lastData) : null,
|
|
1894
|
-
createdAt: Date.now(),
|
|
1895
|
-
lastUpdateAt: null
|
|
1896
|
-
};
|
|
1897
|
-
this.subscriptions.set(sub.id, tracked);
|
|
1898
|
-
const entityKey = `${sub.entity}:${sub.entityId}`;
|
|
1899
|
-
let ids = this.entityIndex.get(entityKey);
|
|
1900
|
-
if (!ids) {
|
|
1901
|
-
ids = new Set;
|
|
1902
|
-
this.entityIndex.set(entityKey, ids);
|
|
1903
|
-
}
|
|
1904
|
-
ids.add(sub.id);
|
|
1905
|
-
}
|
|
1906
|
-
get(id) {
|
|
1907
|
-
return this.subscriptions.get(id);
|
|
1908
|
-
}
|
|
1909
|
-
has(id) {
|
|
1910
|
-
return this.subscriptions.has(id);
|
|
1911
|
-
}
|
|
1912
|
-
remove(id) {
|
|
1913
|
-
const sub = this.subscriptions.get(id);
|
|
1914
|
-
if (!sub)
|
|
1915
|
-
return;
|
|
1916
|
-
this.subscriptions.delete(id);
|
|
1917
|
-
const entityKey = `${sub.entity}:${sub.entityId}`;
|
|
1918
|
-
const ids = this.entityIndex.get(entityKey);
|
|
1919
|
-
if (ids) {
|
|
1920
|
-
ids.delete(id);
|
|
1921
|
-
if (ids.size === 0) {
|
|
1922
|
-
this.entityIndex.delete(entityKey);
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
}
|
|
1926
|
-
getByEntity(entity3, entityId) {
|
|
1927
|
-
const entityKey = `${entity3}:${entityId}`;
|
|
1928
|
-
const ids = this.entityIndex.get(entityKey);
|
|
1929
|
-
if (!ids)
|
|
1930
|
-
return [];
|
|
1931
|
-
const result = [];
|
|
1932
|
-
for (const id of ids) {
|
|
1933
|
-
const sub = this.subscriptions.get(id);
|
|
1934
|
-
if (sub) {
|
|
1935
|
-
result.push(sub);
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
return result;
|
|
1939
|
-
}
|
|
1940
|
-
updateVersion(id, version, data) {
|
|
1941
|
-
const sub = this.subscriptions.get(id);
|
|
1942
|
-
if (!sub)
|
|
1943
|
-
return;
|
|
1944
|
-
sub.version = version;
|
|
1945
|
-
sub.lastUpdateAt = Date.now();
|
|
1946
|
-
if (data !== undefined) {
|
|
1947
|
-
sub.lastData = data;
|
|
1948
|
-
sub.lastDataHash = hashEntityState(data);
|
|
1949
|
-
}
|
|
1950
|
-
if (sub.state === "pending" || sub.state === "reconnecting") {
|
|
1951
|
-
sub.state = "active";
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
updateData(id, data) {
|
|
1955
|
-
const sub = this.subscriptions.get(id);
|
|
1956
|
-
if (!sub)
|
|
1957
|
-
return;
|
|
1958
|
-
sub.lastData = data;
|
|
1959
|
-
sub.lastDataHash = hashEntityState(data);
|
|
1960
|
-
}
|
|
1961
|
-
getLastData(id) {
|
|
1962
|
-
return this.subscriptions.get(id)?.lastData ?? null;
|
|
1963
|
-
}
|
|
1964
|
-
getVersion(id) {
|
|
1965
|
-
return this.subscriptions.get(id)?.version ?? null;
|
|
1966
|
-
}
|
|
1967
|
-
markActive(id) {
|
|
1968
|
-
const sub = this.subscriptions.get(id);
|
|
1969
|
-
if (sub) {
|
|
1970
|
-
sub.state = "active";
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
markError(id) {
|
|
1974
|
-
const sub = this.subscriptions.get(id);
|
|
1975
|
-
if (sub) {
|
|
1976
|
-
sub.state = "error";
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
markAllReconnecting() {
|
|
1980
|
-
for (const sub of this.subscriptions.values()) {
|
|
1981
|
-
if (sub.state === "active") {
|
|
1982
|
-
sub.state = "reconnecting";
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
getByState(state) {
|
|
1987
|
-
const result = [];
|
|
1988
|
-
for (const sub of this.subscriptions.values()) {
|
|
1989
|
-
if (sub.state === state) {
|
|
1990
|
-
result.push(sub);
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
return result;
|
|
1994
|
-
}
|
|
1995
|
-
getAllForReconnect() {
|
|
1996
|
-
const result = [];
|
|
1997
|
-
for (const sub of this.subscriptions.values()) {
|
|
1998
|
-
if (sub.state === "reconnecting" || sub.state === "active") {
|
|
1999
|
-
const reconnectSub = {
|
|
2000
|
-
id: sub.id,
|
|
2001
|
-
entity: sub.entity,
|
|
2002
|
-
entityId: sub.entityId,
|
|
2003
|
-
fields: sub.fields,
|
|
2004
|
-
version: sub.version,
|
|
2005
|
-
input: sub.input
|
|
2006
|
-
};
|
|
2007
|
-
if (sub.lastDataHash) {
|
|
2008
|
-
reconnectSub.dataHash = sub.lastDataHash;
|
|
2009
|
-
}
|
|
2010
|
-
result.push(reconnectSub);
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
return result;
|
|
2014
|
-
}
|
|
2015
|
-
processReconnectResult(id, version, data) {
|
|
2016
|
-
const sub = this.subscriptions.get(id);
|
|
2017
|
-
if (!sub)
|
|
2018
|
-
return;
|
|
2019
|
-
sub.version = version;
|
|
2020
|
-
sub.state = "active";
|
|
2021
|
-
sub.lastUpdateAt = Date.now();
|
|
2022
|
-
if (data !== undefined) {
|
|
2023
|
-
sub.lastData = data;
|
|
2024
|
-
sub.lastDataHash = hashEntityState(data);
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
getObserver(id) {
|
|
2028
|
-
return this.subscriptions.get(id)?.observer;
|
|
2029
|
-
}
|
|
2030
|
-
updateObserver(id, observer) {
|
|
2031
|
-
const sub = this.subscriptions.get(id);
|
|
2032
|
-
if (sub) {
|
|
2033
|
-
sub.observer = observer;
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
notifyNext(id, data) {
|
|
2037
|
-
const sub = this.subscriptions.get(id);
|
|
2038
|
-
sub?.observer.next?.({ data, version: sub.version });
|
|
2039
|
-
}
|
|
2040
|
-
notifyError(id, error) {
|
|
2041
|
-
this.subscriptions.get(id)?.observer.error?.(error);
|
|
2042
|
-
}
|
|
2043
|
-
notifyAllReconnectingError(error) {
|
|
2044
|
-
for (const sub of this.subscriptions.values()) {
|
|
2045
|
-
if (sub.state === "reconnecting") {
|
|
2046
|
-
sub.observer.error?.(error);
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
get size() {
|
|
2051
|
-
return this.subscriptions.size;
|
|
2052
|
-
}
|
|
2053
|
-
getIds() {
|
|
2054
|
-
return Array.from(this.subscriptions.keys());
|
|
2055
|
-
}
|
|
2056
|
-
values() {
|
|
2057
|
-
return this.subscriptions.values();
|
|
2058
|
-
}
|
|
2059
|
-
getStats() {
|
|
2060
|
-
const byState = {
|
|
2061
|
-
pending: 0,
|
|
2062
|
-
active: 0,
|
|
2063
|
-
reconnecting: 0,
|
|
2064
|
-
error: 0
|
|
2065
|
-
};
|
|
2066
|
-
const byEntity = {};
|
|
2067
|
-
for (const sub of this.subscriptions.values()) {
|
|
2068
|
-
byState[sub.state]++;
|
|
2069
|
-
const entityKey = `${sub.entity}:${sub.entityId}`;
|
|
2070
|
-
byEntity[entityKey] = (byEntity[entityKey] ?? 0) + 1;
|
|
2071
|
-
}
|
|
2072
|
-
return {
|
|
2073
|
-
total: this.subscriptions.size,
|
|
2074
|
-
byState,
|
|
2075
|
-
byEntity
|
|
2076
|
-
};
|
|
2077
|
-
}
|
|
2078
|
-
clear() {
|
|
2079
|
-
for (const sub of this.subscriptions.values()) {
|
|
2080
|
-
sub.observer.complete?.();
|
|
2081
|
-
}
|
|
2082
|
-
this.subscriptions.clear();
|
|
2083
|
-
this.entityIndex.clear();
|
|
2084
|
-
}
|
|
2085
|
-
clearErrors() {
|
|
2086
|
-
const toRemove = [];
|
|
2087
|
-
for (const [id, sub] of this.subscriptions) {
|
|
2088
|
-
if (sub.state === "error") {
|
|
2089
|
-
toRemove.push(id);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
for (const id of toRemove) {
|
|
2093
|
-
this.remove(id);
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
2538
|
|
|
2098
2539
|
// src/create.ts
|
|
2099
2540
|
import { createEffect, createSignal, on, onCleanup } from "solid-js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/lens-solid",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.6",
|
|
4
4
|
"description": "SolidJS bindings for Lens API framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"author": "SylphxAI",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@sylphx/lens-client": "^2.
|
|
35
|
-
"@sylphx/lens-core": "^2.12.
|
|
34
|
+
"@sylphx/lens-client": "^2.7.1",
|
|
35
|
+
"@sylphx/lens-core": "^2.12.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"solid-js": ">=1.8.0"
|