@calimero-network/mero-react 5.0.0 → 6.0.0
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.cjs +230 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -7
- package/dist/index.d.ts +122 -7
- package/dist/index.js +227 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1266,6 +1266,185 @@ function base58ToHex(input) {
|
|
|
1266
1266
|
}
|
|
1267
1267
|
|
|
1268
1268
|
// src/hooks/index.ts
|
|
1269
|
+
var NO_EPHEMERAL_SURFACE_MESSAGE = "useEphemeral: this client has no `mero.ephemeral` surface. Ephemeral presence requires @calimero-network/mero-js >= 9.1.0; upgrade the installed @calimero-network/mero-js (and any lockfile pin) to use it.";
|
|
1270
|
+
var COULD_NOT_RESOLVE_IDENTITY_MESSAGE = "useEphemeral: could not resolve a local context identity, so your own presence cannot be filtered out. Pass includeSelf: true to accept this.";
|
|
1271
|
+
var REPLAY_RECONCILE_MS = 50;
|
|
1272
|
+
function useEphemeral(contextId, options = {}) {
|
|
1273
|
+
const { includeSelf = false } = options;
|
|
1274
|
+
const { mero } = useMero();
|
|
1275
|
+
const ephemeral = mero ? mero.ephemeral ?? null : null;
|
|
1276
|
+
const [peers, setPeers] = react.useState(/* @__PURE__ */ new Map());
|
|
1277
|
+
const [latchedError, setLatchedError] = react.useState(null);
|
|
1278
|
+
const contextKeyRef = react.useRef(contextId);
|
|
1279
|
+
contextKeyRef.current = contextId;
|
|
1280
|
+
const setSourceError = react.useCallback(
|
|
1281
|
+
(source, err, key) => {
|
|
1282
|
+
if (key !== contextKeyRef.current) return;
|
|
1283
|
+
setLatchedError({ contextKey: key, source, error: err });
|
|
1284
|
+
},
|
|
1285
|
+
[]
|
|
1286
|
+
);
|
|
1287
|
+
const clearSourceError = react.useCallback((source, key) => {
|
|
1288
|
+
setLatchedError(
|
|
1289
|
+
(prev) => prev && prev.source === source && prev.contextKey === key ? null : prev
|
|
1290
|
+
);
|
|
1291
|
+
}, []);
|
|
1292
|
+
const surfaceError = react.useMemo(
|
|
1293
|
+
() => mero && !ephemeral ? new Error(NO_EPHEMERAL_SURFACE_MESSAGE) : null,
|
|
1294
|
+
[mero, ephemeral]
|
|
1295
|
+
);
|
|
1296
|
+
const error = surfaceError ?? (latchedError && latchedError.contextKey === contextId && !(latchedError.source === "identity" && includeSelf) ? latchedError.error : null);
|
|
1297
|
+
const receivedAtRef = react.useRef(/* @__PURE__ */ new Map());
|
|
1298
|
+
const codecRef = react.useRef(options.codec);
|
|
1299
|
+
codecRef.current = options.codec;
|
|
1300
|
+
const initialRef = react.useRef(options.initial);
|
|
1301
|
+
initialRef.current = options.initial;
|
|
1302
|
+
const selfRef = react.useRef(null);
|
|
1303
|
+
const lastLocalRef = react.useRef(options.initial);
|
|
1304
|
+
const pendingRef = react.useRef(void 0);
|
|
1305
|
+
const timerRef = react.useRef(null);
|
|
1306
|
+
const lastSentAtRef = react.useRef(Date.now());
|
|
1307
|
+
const replaySeenRef = react.useRef(null);
|
|
1308
|
+
const replayTimerRef = react.useRef(null);
|
|
1309
|
+
const ageOf = react.useCallback((author) => {
|
|
1310
|
+
const at = receivedAtRef.current.get(author);
|
|
1311
|
+
return at === void 0 ? void 0 : Date.now() - at;
|
|
1312
|
+
}, []);
|
|
1313
|
+
react.useEffect(() => {
|
|
1314
|
+
setPeers(/* @__PURE__ */ new Map());
|
|
1315
|
+
receivedAtRef.current = /* @__PURE__ */ new Map();
|
|
1316
|
+
if (timerRef.current) {
|
|
1317
|
+
clearTimeout(timerRef.current);
|
|
1318
|
+
timerRef.current = null;
|
|
1319
|
+
}
|
|
1320
|
+
if (replayTimerRef.current) {
|
|
1321
|
+
clearTimeout(replayTimerRef.current);
|
|
1322
|
+
replayTimerRef.current = null;
|
|
1323
|
+
}
|
|
1324
|
+
replaySeenRef.current = null;
|
|
1325
|
+
pendingRef.current = void 0;
|
|
1326
|
+
lastLocalRef.current = initialRef.current;
|
|
1327
|
+
selfRef.current = null;
|
|
1328
|
+
setLatchedError(null);
|
|
1329
|
+
}, [contextId]);
|
|
1330
|
+
react.useEffect(() => {
|
|
1331
|
+
if (!mero || !contextId) return;
|
|
1332
|
+
const key = contextId;
|
|
1333
|
+
let cancelled = false;
|
|
1334
|
+
mero.admin.getContextIdentitiesOwned(contextId).then((res) => {
|
|
1335
|
+
if (cancelled) return;
|
|
1336
|
+
const self = res.identities?.[0] ?? null;
|
|
1337
|
+
selfRef.current = self;
|
|
1338
|
+
if (!self) {
|
|
1339
|
+
setSourceError("identity", new Error(COULD_NOT_RESOLVE_IDENTITY_MESSAGE), key);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
clearSourceError("identity", key);
|
|
1343
|
+
if (!includeSelf) {
|
|
1344
|
+
receivedAtRef.current.delete(self);
|
|
1345
|
+
setPeers((prev) => {
|
|
1346
|
+
if (!prev.has(self)) return prev;
|
|
1347
|
+
const next = new Map(prev);
|
|
1348
|
+
next.delete(self);
|
|
1349
|
+
return next;
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
}).catch((err) => {
|
|
1353
|
+
if (!cancelled) {
|
|
1354
|
+
setSourceError("identity", toError(err), key);
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
return () => {
|
|
1358
|
+
cancelled = true;
|
|
1359
|
+
};
|
|
1360
|
+
}, [mero, contextId, includeSelf, setSourceError, clearSourceError]);
|
|
1361
|
+
react.useEffect(() => {
|
|
1362
|
+
if (!ephemeral || !contextId) return;
|
|
1363
|
+
const closeReplayWindow = () => {
|
|
1364
|
+
replayTimerRef.current = null;
|
|
1365
|
+
const seen = replaySeenRef.current;
|
|
1366
|
+
replaySeenRef.current = null;
|
|
1367
|
+
if (!seen) return;
|
|
1368
|
+
setPeers((prev) => {
|
|
1369
|
+
let next = null;
|
|
1370
|
+
for (const author of prev.keys()) {
|
|
1371
|
+
if (seen.has(author)) continue;
|
|
1372
|
+
if (!next) next = new Map(prev);
|
|
1373
|
+
next.delete(author);
|
|
1374
|
+
receivedAtRef.current.delete(author);
|
|
1375
|
+
}
|
|
1376
|
+
return next ?? prev;
|
|
1377
|
+
});
|
|
1378
|
+
};
|
|
1379
|
+
const unsubscribe = ephemeral.subscribe(
|
|
1380
|
+
contextId,
|
|
1381
|
+
(entry) => {
|
|
1382
|
+
const { author, state } = entry;
|
|
1383
|
+
if (entry.ageMs !== void 0) {
|
|
1384
|
+
if (!replaySeenRef.current) replaySeenRef.current = /* @__PURE__ */ new Set();
|
|
1385
|
+
if (replayTimerRef.current) clearTimeout(replayTimerRef.current);
|
|
1386
|
+
replayTimerRef.current = setTimeout(closeReplayWindow, REPLAY_RECONCILE_MS);
|
|
1387
|
+
}
|
|
1388
|
+
if (entry.removed || state === void 0) {
|
|
1389
|
+
replaySeenRef.current?.delete(author);
|
|
1390
|
+
receivedAtRef.current.delete(author);
|
|
1391
|
+
setPeers((prev) => {
|
|
1392
|
+
if (!prev.has(author)) return prev;
|
|
1393
|
+
const next = new Map(prev);
|
|
1394
|
+
next.delete(author);
|
|
1395
|
+
return next;
|
|
1396
|
+
});
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (!includeSelf && author === selfRef.current) return;
|
|
1400
|
+
replaySeenRef.current?.add(author);
|
|
1401
|
+
receivedAtRef.current.set(author, Date.now() - (entry.ageMs ?? 0));
|
|
1402
|
+
setPeers((prev) => new Map(prev).set(author, state));
|
|
1403
|
+
},
|
|
1404
|
+
codecRef.current
|
|
1405
|
+
);
|
|
1406
|
+
return () => {
|
|
1407
|
+
if (replayTimerRef.current) {
|
|
1408
|
+
clearTimeout(replayTimerRef.current);
|
|
1409
|
+
replayTimerRef.current = null;
|
|
1410
|
+
}
|
|
1411
|
+
replaySeenRef.current = null;
|
|
1412
|
+
unsubscribe();
|
|
1413
|
+
};
|
|
1414
|
+
}, [ephemeral, contextId, includeSelf]);
|
|
1415
|
+
const publish = react.useCallback((value) => {
|
|
1416
|
+
if (!ephemeral || !contextId) return;
|
|
1417
|
+
const key = contextId;
|
|
1418
|
+
lastSentAtRef.current = Date.now();
|
|
1419
|
+
void ephemeral.set(key, value, codecRef.current).then(
|
|
1420
|
+
() => clearSourceError("publish", key),
|
|
1421
|
+
(err) => setSourceError("publish", toError(err), key)
|
|
1422
|
+
);
|
|
1423
|
+
}, [ephemeral, contextId, setSourceError, clearSourceError]);
|
|
1424
|
+
const setPresence = react.useCallback((partial) => {
|
|
1425
|
+
const next = { ...lastLocalRef.current ?? {}, ...partial };
|
|
1426
|
+
lastLocalRef.current = next;
|
|
1427
|
+
const throttleMs = options.throttleMs ?? 30;
|
|
1428
|
+
if (throttleMs <= 0) {
|
|
1429
|
+
publish(next);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
pendingRef.current = next;
|
|
1433
|
+
if (timerRef.current) return;
|
|
1434
|
+
const elapsed = Date.now() - lastSentAtRef.current;
|
|
1435
|
+
const delay = Math.max(throttleMs - elapsed, 0);
|
|
1436
|
+
timerRef.current = setTimeout(() => {
|
|
1437
|
+
timerRef.current = null;
|
|
1438
|
+
const queued = pendingRef.current;
|
|
1439
|
+
pendingRef.current = void 0;
|
|
1440
|
+
if (queued !== void 0) publish(queued);
|
|
1441
|
+
}, delay);
|
|
1442
|
+
}, [publish, options.throttleMs]);
|
|
1443
|
+
react.useEffect(() => () => {
|
|
1444
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1445
|
+
}, []);
|
|
1446
|
+
return { peers, setPresence, ageOf, error };
|
|
1447
|
+
}
|
|
1269
1448
|
function toError(err) {
|
|
1270
1449
|
return err instanceof Error ? err : new Error(String(err));
|
|
1271
1450
|
}
|
|
@@ -1811,6 +1990,15 @@ function useNamespace(namespaceId) {
|
|
|
1811
1990
|
);
|
|
1812
1991
|
return { namespace: data, loading, error, refetch };
|
|
1813
1992
|
}
|
|
1993
|
+
function useNodeIdentity() {
|
|
1994
|
+
const { mero } = useMero();
|
|
1995
|
+
const { data, loading, error, refetch } = useAsyncResource(
|
|
1996
|
+
mero ? () => mero.admin.getNodeIdentity() : null,
|
|
1997
|
+
null,
|
|
1998
|
+
[mero]
|
|
1999
|
+
);
|
|
2000
|
+
return { identity: data, loading, error, refetch };
|
|
2001
|
+
}
|
|
1814
2002
|
function useNamespaceIdentity(namespaceId) {
|
|
1815
2003
|
const { mero } = useMero();
|
|
1816
2004
|
const { data, loading, error, refetch } = useAsyncResource(
|
|
@@ -2180,6 +2368,35 @@ function useDetachContextFromGroup() {
|
|
|
2180
2368
|
);
|
|
2181
2369
|
return { detachContextFromGroup, loading, error };
|
|
2182
2370
|
}
|
|
2371
|
+
var DEFAULT_MIGRATION_POLL_INTERVAL_MS = 5e3;
|
|
2372
|
+
function useMigrationEvents(namespaceId, onEvent) {
|
|
2373
|
+
const { mero } = useMero();
|
|
2374
|
+
const mountedRef = useMountedRef();
|
|
2375
|
+
const [live, setLive] = react.useState(false);
|
|
2376
|
+
const handlerRef = react.useRef(onEvent);
|
|
2377
|
+
handlerRef.current = onEvent;
|
|
2378
|
+
react.useEffect(() => {
|
|
2379
|
+
if (!mero || !namespaceId) return;
|
|
2380
|
+
const sse = mero.events;
|
|
2381
|
+
const unsubscribe = sse.onMigrationEvent((event) => handlerRef.current(event));
|
|
2382
|
+
let cancelled = false;
|
|
2383
|
+
void (async () => {
|
|
2384
|
+
try {
|
|
2385
|
+
await sse.connect();
|
|
2386
|
+
await sse.subscribe({ groupIds: [namespaceId] });
|
|
2387
|
+
if (!cancelled && mountedRef.current) setLive(true);
|
|
2388
|
+
} catch {
|
|
2389
|
+
if (!cancelled && mountedRef.current) setLive(false);
|
|
2390
|
+
}
|
|
2391
|
+
})();
|
|
2392
|
+
return () => {
|
|
2393
|
+
cancelled = true;
|
|
2394
|
+
if (mountedRef.current) setLive(false);
|
|
2395
|
+
unsubscribe();
|
|
2396
|
+
};
|
|
2397
|
+
}, [mero, namespaceId, mountedRef]);
|
|
2398
|
+
return live;
|
|
2399
|
+
}
|
|
2183
2400
|
function useMigrationStatus(namespaceId, options) {
|
|
2184
2401
|
const { mero } = useMero();
|
|
2185
2402
|
const [status, setStatus] = react.useState(null);
|
|
@@ -2224,7 +2441,8 @@ function useMigrationStatus(namespaceId, options) {
|
|
|
2224
2441
|
react.useEffect(() => {
|
|
2225
2442
|
void refetch();
|
|
2226
2443
|
}, [refetch]);
|
|
2227
|
-
const
|
|
2444
|
+
const sseLive = useMigrationEvents(namespaceId, () => void refetch());
|
|
2445
|
+
const pollIntervalMs = options?.pollIntervalMs ?? (sseLive ? void 0 : DEFAULT_MIGRATION_POLL_INTERVAL_MS);
|
|
2228
2446
|
react.useEffect(() => {
|
|
2229
2447
|
if (!pollIntervalMs || !mero || !namespaceId) return;
|
|
2230
2448
|
const handle = setInterval(() => void refetch(), pollIntervalMs);
|
|
@@ -2237,6 +2455,12 @@ function useMigrationStatus(namespaceId, options) {
|
|
|
2237
2455
|
rollup,
|
|
2238
2456
|
members,
|
|
2239
2457
|
allMigrated: rollup?.allMigrated ?? false,
|
|
2458
|
+
/**
|
|
2459
|
+
* When this node watched the fleet converge, or `null`. Durable, unlike
|
|
2460
|
+
* `allMigrated`, which is recomputed from in-TTL heartbeats and lapses back
|
|
2461
|
+
* to false when a member simply goes quiet.
|
|
2462
|
+
*/
|
|
2463
|
+
fleetCompletedAt: status?.fleetCompletedAt ?? null,
|
|
2240
2464
|
membersPendingSignature: rollup?.membersPendingSignature ?? 0,
|
|
2241
2465
|
/** Members whose migrate aborted (migration-check failed or apply errored). */
|
|
2242
2466
|
failed: rollup?.failed ?? 0,
|
|
@@ -2356,7 +2580,6 @@ var EMPTY_GROUP_APP_VERSION = {
|
|
|
2356
2580
|
version: null,
|
|
2357
2581
|
applicationId: null,
|
|
2358
2582
|
pendingApply: false,
|
|
2359
|
-
upgradePolicy: null,
|
|
2360
2583
|
activeUpgrade: null
|
|
2361
2584
|
};
|
|
2362
2585
|
function isZeroAppKey(appKey) {
|
|
@@ -2388,17 +2611,14 @@ function useGroupAppVersion(groupId) {
|
|
|
2388
2611
|
const namespace = namespaces.find((entry) => entry.namespaceId === groupId);
|
|
2389
2612
|
let appKey;
|
|
2390
2613
|
let applicationId;
|
|
2391
|
-
let upgradePolicy;
|
|
2392
2614
|
let activeUpgrade = null;
|
|
2393
2615
|
if (namespace) {
|
|
2394
2616
|
appKey = namespace.appKey;
|
|
2395
2617
|
applicationId = namespace.targetApplicationId;
|
|
2396
|
-
upgradePolicy = namespace.upgradePolicy ?? null;
|
|
2397
2618
|
} else {
|
|
2398
2619
|
const info = await mero.admin.getGroupInfo(groupId);
|
|
2399
2620
|
appKey = info.appKey;
|
|
2400
2621
|
applicationId = info.targetApplicationId;
|
|
2401
|
-
upgradePolicy = info.upgradePolicy ?? null;
|
|
2402
2622
|
activeUpgrade = info.activeUpgrade ?? null;
|
|
2403
2623
|
}
|
|
2404
2624
|
let version = null;
|
|
@@ -2412,7 +2632,7 @@ function useGroupAppVersion(groupId) {
|
|
|
2412
2632
|
}
|
|
2413
2633
|
}
|
|
2414
2634
|
if (mountedRef.current && seq === reqRef.current) {
|
|
2415
|
-
setData({ version, applicationId, pendingApply,
|
|
2635
|
+
setData({ version, applicationId, pendingApply, activeUpgrade });
|
|
2416
2636
|
}
|
|
2417
2637
|
} catch (err) {
|
|
2418
2638
|
const errorValue = toError(err);
|
|
@@ -2623,6 +2843,7 @@ exports.AppMode = AppMode;
|
|
|
2623
2843
|
exports.CalimeroLogo = CalimeroLogo;
|
|
2624
2844
|
exports.ConnectButton = ConnectButton;
|
|
2625
2845
|
exports.ConnectionType = ConnectionType;
|
|
2846
|
+
exports.DEFAULT_MIGRATION_POLL_INTERVAL_MS = DEFAULT_MIGRATION_POLL_INTERVAL_MS;
|
|
2626
2847
|
exports.LoginModal = LoginModal;
|
|
2627
2848
|
exports.MERO_CSS_VARS = MERO_CSS_VARS;
|
|
2628
2849
|
exports.MeroContext = MeroContext;
|
|
@@ -2665,6 +2886,7 @@ exports.useDeleteContext = useDeleteContext;
|
|
|
2665
2886
|
exports.useDeleteGroup = useDeleteGroup;
|
|
2666
2887
|
exports.useDeleteNamespace = useDeleteNamespace;
|
|
2667
2888
|
exports.useDetachContextFromGroup = useDetachContextFromGroup;
|
|
2889
|
+
exports.useEphemeral = useEphemeral;
|
|
2668
2890
|
exports.useExecute = useExecute;
|
|
2669
2891
|
exports.useGroupAppVersion = useGroupAppVersion;
|
|
2670
2892
|
exports.useGroupCapabilities = useGroupCapabilities;
|
|
@@ -2682,6 +2904,7 @@ exports.useJoinSubgroupInheritance = useJoinSubgroupInheritance;
|
|
|
2682
2904
|
exports.useLatestVersion = useLatestVersion;
|
|
2683
2905
|
exports.useMemberMetadata = useMemberMetadata;
|
|
2684
2906
|
exports.useMero = useMero;
|
|
2907
|
+
exports.useMigrationEvents = useMigrationEvents;
|
|
2685
2908
|
exports.useMigrationStatus = useMigrationStatus;
|
|
2686
2909
|
exports.useMyAuthoredMigration = useMyAuthoredMigration;
|
|
2687
2910
|
exports.useNamespace = useNamespace;
|
|
@@ -2689,6 +2912,7 @@ exports.useNamespaceGroups = useNamespaceGroups;
|
|
|
2689
2912
|
exports.useNamespaceIdentity = useNamespaceIdentity;
|
|
2690
2913
|
exports.useNamespaces = useNamespaces;
|
|
2691
2914
|
exports.useNamespacesForApplication = useNamespacesForApplication;
|
|
2915
|
+
exports.useNodeIdentity = useNodeIdentity;
|
|
2692
2916
|
exports.useRemoveGroupMembers = useRemoveGroupMembers;
|
|
2693
2917
|
exports.useReparentGroup = useReparentGroup;
|
|
2694
2918
|
exports.useResyncContext = useResyncContext;
|