@sylphx/lens-solid 2.3.4 → 2.3.5
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 +737 -303
- 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
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
distributeError(endpoint, error) {
|
|
2059
|
+
endpoint.error = error;
|
|
2060
|
+
for (const observer of endpoint.observers.values()) {
|
|
2061
|
+
observer.error?.(error);
|
|
1495
2062
|
}
|
|
1496
|
-
|
|
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
|
-
entry.error(sub.error);
|
|
1522
|
-
} else if (sub.data !== null && entry.next) {
|
|
1523
|
-
entry.next(sub.data);
|
|
1524
|
-
}
|
|
1525
|
-
if (sub.completed && entry.complete) {
|
|
1526
|
-
entry.complete();
|
|
1527
|
-
}
|
|
1528
|
-
if (!sub.unsubscribe) {
|
|
2162
|
+
const { endpoint: ep, isExpanded } = this.addObserver(key, entry);
|
|
2163
|
+
if (!ep.isSubscribed) {
|
|
1529
2164
|
this.startSubscription(path, input, key);
|
|
2165
|
+
} else if (isExpanded) {
|
|
2166
|
+
if (ep.unsubscribe) {
|
|
2167
|
+
ep.unsubscribe();
|
|
2168
|
+
}
|
|
2169
|
+
ep.isSubscribed = false;
|
|
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);
|
|
1561
|
-
}
|
|
1562
|
-
if (isSnapshot(response)) {
|
|
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;
|
|
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;
|
|
1568
2210
|
}
|
|
1569
|
-
return onfulfilled ? onfulfilled(
|
|
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,70 @@ 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
2232
|
await this.ensureConnected();
|
|
1593
|
-
const
|
|
2233
|
+
const meta = this.getOperationMeta(path);
|
|
2234
|
+
if (meta?.type === "mutation") {
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
const isSubscription = this.requiresSubscription(path, endpoint.mergedSelection);
|
|
2238
|
+
endpoint.isSubscribed = true;
|
|
1594
2239
|
if (isSubscription) {
|
|
1595
2240
|
const op2 = {
|
|
1596
2241
|
id: this.generateId("subscription", path),
|
|
1597
2242
|
path,
|
|
1598
2243
|
type: "subscription",
|
|
1599
|
-
input
|
|
2244
|
+
input,
|
|
2245
|
+
meta: endpoint.mergedSelection ? { select: endpoint.mergedSelection } : {}
|
|
1600
2246
|
};
|
|
1601
2247
|
const resultOrObservable = this.transport.execute(op2);
|
|
1602
2248
|
if (this.isObservable(resultOrObservable)) {
|
|
1603
2249
|
const subscription = resultOrObservable.subscribe({
|
|
1604
2250
|
next: (message) => {
|
|
1605
2251
|
if (isSnapshot(message)) {
|
|
1606
|
-
|
|
1607
|
-
sub.error = null;
|
|
1608
|
-
for (const observer of sub.observers) {
|
|
1609
|
-
observer.next?.(message.data);
|
|
1610
|
-
}
|
|
2252
|
+
this.distributeData(endpoint, message.data);
|
|
1611
2253
|
} else if (isOps(message)) {
|
|
1612
2254
|
try {
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
for (const observer of sub.observers) {
|
|
1616
|
-
observer.next?.(sub.data);
|
|
1617
|
-
}
|
|
2255
|
+
const newData = applyOps(endpoint.data, message.ops);
|
|
2256
|
+
this.distributeData(endpoint, newData);
|
|
1618
2257
|
} catch (updateErr) {
|
|
1619
2258
|
const err = updateErr instanceof Error ? updateErr : new Error(String(updateErr));
|
|
1620
|
-
|
|
1621
|
-
for (const observer of sub.observers) {
|
|
1622
|
-
observer.error?.(err);
|
|
1623
|
-
}
|
|
2259
|
+
this.distributeError(endpoint, err);
|
|
1624
2260
|
}
|
|
1625
2261
|
} else if (isError(message)) {
|
|
1626
|
-
|
|
1627
|
-
sub.error = err;
|
|
1628
|
-
for (const observer of sub.observers) {
|
|
1629
|
-
observer.error?.(err);
|
|
1630
|
-
}
|
|
2262
|
+
this.distributeError(endpoint, new Error(message.error));
|
|
1631
2263
|
}
|
|
1632
2264
|
},
|
|
1633
2265
|
error: (err) => {
|
|
1634
|
-
|
|
1635
|
-
for (const observer of sub.observers) {
|
|
1636
|
-
observer.error?.(err);
|
|
1637
|
-
}
|
|
2266
|
+
this.distributeError(endpoint, err);
|
|
1638
2267
|
},
|
|
1639
2268
|
complete: () => {
|
|
1640
|
-
|
|
1641
|
-
for (const observer of
|
|
2269
|
+
endpoint.completed = true;
|
|
2270
|
+
for (const observer of endpoint.observers.values()) {
|
|
1642
2271
|
observer.complete?.();
|
|
1643
2272
|
}
|
|
1644
2273
|
}
|
|
1645
2274
|
});
|
|
1646
|
-
|
|
2275
|
+
endpoint.unsubscribe = () => subscription.unsubscribe();
|
|
1647
2276
|
}
|
|
1648
2277
|
} else {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
2278
|
+
try {
|
|
2279
|
+
const data = await this.scheduleBatchedQuery(key, path, input, endpoint.mergedSelection);
|
|
2280
|
+
this.distributeData(endpoint, data);
|
|
2281
|
+
endpoint.completed = true;
|
|
2282
|
+
for (const observer of endpoint.observers.values()) {
|
|
1652
2283
|
observer.complete?.();
|
|
1653
2284
|
}
|
|
1654
|
-
})
|
|
2285
|
+
} catch (error) {
|
|
2286
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
2287
|
+
this.distributeError(endpoint, err);
|
|
2288
|
+
}
|
|
1655
2289
|
}
|
|
1656
2290
|
}
|
|
1657
2291
|
async executeMutation(path, input, select) {
|
|
@@ -1699,6 +2333,17 @@ class ClientImpl {
|
|
|
1699
2333
|
return queryResult;
|
|
1700
2334
|
};
|
|
1701
2335
|
}
|
|
2336
|
+
getStats() {
|
|
2337
|
+
let totalObservers = 0;
|
|
2338
|
+
for (const endpoint of this.endpoints.values()) {
|
|
2339
|
+
totalObservers += endpoint.observers.size;
|
|
2340
|
+
}
|
|
2341
|
+
return {
|
|
2342
|
+
endpointCount: this.endpoints.size,
|
|
2343
|
+
totalObservers,
|
|
2344
|
+
pendingBatches: this.pendingBatches.size
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
1702
2347
|
}
|
|
1703
2348
|
function createClient(config) {
|
|
1704
2349
|
const impl = new ClientImpl(config);
|
|
@@ -1883,217 +2528,6 @@ http.server = function httpServer(options) {
|
|
|
1883
2528
|
}
|
|
1884
2529
|
};
|
|
1885
2530
|
};
|
|
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
2531
|
|
|
2098
2532
|
// src/create.ts
|
|
2099
2533
|
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.5",
|
|
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.6.
|
|
35
|
-
"@sylphx/lens-core": "^2.12.
|
|
34
|
+
"@sylphx/lens-client": "^2.6.1",
|
|
35
|
+
"@sylphx/lens-core": "^2.12.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"solid-js": ">=1.8.0"
|