@calimero-network/mero-react 9.0.0 → 9.1.1
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/README.md +37 -0
- package/dist/index.cjs +88 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -2
- package/dist/index.d.ts +102 -2
- package/dist/index.js +86 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -208,6 +208,41 @@ const { contexts, loading, error, refetch } = useContexts(applicationId);
|
|
|
208
208
|
// contexts: Array<{ contextId: string; applicationId: string }>
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
### Blob hooks
|
|
212
|
+
|
|
213
|
+
`useBlobInfo` / `useBlobUrl` / `useUploadBlob` read and write the node's blob
|
|
214
|
+
store. The reads take an optional `contextId`, and it is what picks the mode:
|
|
215
|
+
|
|
216
|
+
- **without `contextId`** — local-only. The node answers from its own blob
|
|
217
|
+
store immediately, or 404s.
|
|
218
|
+
- **with `contextId`** — network discovery. The node probes that context's
|
|
219
|
+
peers (availability nodes first) for a holder.
|
|
220
|
+
|
|
221
|
+
Discovery is slow: core bounds the search by a **~30s deadline**, and for
|
|
222
|
+
`useBlobUrl` the byte transfer is on top of that. `loading` is real UI state on
|
|
223
|
+
these hooks, not decoration.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// Presence and size, no download — ask this before pulling something large.
|
|
227
|
+
const { info, notFound, loading, error } = useBlobInfo(blobId, { contextId });
|
|
228
|
+
// info: { blobId, size, hash?, mimeType?, source?: 'local' | 'peer' } | null
|
|
229
|
+
|
|
230
|
+
// Bytes as an object URL, revoked for you on unmount and on id/context change.
|
|
231
|
+
const { url } = useBlobUrl(blobId, { contextId, type: 'image/png' });
|
|
232
|
+
return url ? <img src={url} /> : null;
|
|
233
|
+
|
|
234
|
+
// Upload. Pass contextId to announce the blob so the context's peers can
|
|
235
|
+
// discover it later.
|
|
236
|
+
const { uploadBlob, loading, error } = useUploadBlob();
|
|
237
|
+
const result = await uploadBlob({ data: bytes, contextId });
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
A null/undefined `blobId` fetches nothing, like the other `string | null` read
|
|
241
|
+
hooks. `hash` and `mimeType` are genuinely optional — a peer probe carries only
|
|
242
|
+
presence and size, so a discovery-sourced hit has neither; read `info.source`
|
|
243
|
+
to tell a local answer from a peer one. `notFound` marks the node's legitimate
|
|
244
|
+
"no holder" 404 apart from a transport failure; `error` is set for both.
|
|
245
|
+
|
|
211
246
|
### Storage helpers
|
|
212
247
|
|
|
213
248
|
Persist/read node URL, application ID, context ID, and context identity in localStorage.
|
|
@@ -360,11 +395,13 @@ useDetachContextFromGroup
|
|
|
360
395
|
useNamespaces, useNamespace, useNamespaceGroups, useNamespaceIdentity
|
|
361
396
|
useNamespacesForApplication, useCreateNamespace, useDeleteNamespace
|
|
362
397
|
useJoinNamespace, useCreateNamespaceInvitation, useCreateGroupInNamespace
|
|
398
|
+
useBlobInfo, useBlobUrl, useUploadBlob
|
|
363
399
|
|
|
364
400
|
// Types (mero-react)
|
|
365
401
|
MeroContextValue, MeroProviderConfig, MeroProviderProps
|
|
366
402
|
CustomConnectionConfig, AppContext, ExecutionResult
|
|
367
403
|
ApplicationContextRecord, ContextDiscoveryOptions, ContextDiscoveryState
|
|
404
|
+
BlobHookOptions, UseBlobUrlOptions
|
|
368
405
|
|
|
369
406
|
// Storage (mero-react)
|
|
370
407
|
localStorageTokenStorage
|
package/dist/index.cjs
CHANGED
|
@@ -1546,11 +1546,13 @@ function useAsyncResource(fetcher, initialValue, deps) {
|
|
|
1546
1546
|
if (mountedRef.current && seq === reqRef.current) setLoading(false);
|
|
1547
1547
|
}
|
|
1548
1548
|
}, deps);
|
|
1549
|
-
react.
|
|
1549
|
+
const prevDeps = react.useRef(deps);
|
|
1550
|
+
if (deps.length !== prevDeps.current.length || deps.some((d, i) => !Object.is(d, prevDeps.current[i]))) {
|
|
1551
|
+
prevDeps.current = deps;
|
|
1550
1552
|
reqRef.current += 1;
|
|
1551
1553
|
setData(initialRef.current);
|
|
1552
1554
|
setError(null);
|
|
1553
|
-
}
|
|
1555
|
+
}
|
|
1554
1556
|
react.useEffect(() => {
|
|
1555
1557
|
void refetch();
|
|
1556
1558
|
}, [refetch]);
|
|
@@ -2223,6 +2225,13 @@ function useMemberMetadata(groupId, identity) {
|
|
|
2223
2225
|
const [metadata, setMetadata] = react.useState(null);
|
|
2224
2226
|
const [loading, setLoading] = react.useState(false);
|
|
2225
2227
|
const [error, setError] = react.useState(null);
|
|
2228
|
+
const prevKey = react.useRef(null);
|
|
2229
|
+
const key = `${groupId ?? ""}:${identity ?? ""}`;
|
|
2230
|
+
if (prevKey.current !== null && prevKey.current !== key) {
|
|
2231
|
+
setMetadata(null);
|
|
2232
|
+
setError(null);
|
|
2233
|
+
}
|
|
2234
|
+
prevKey.current = key;
|
|
2226
2235
|
const run = react.useCallback(
|
|
2227
2236
|
async (signal) => {
|
|
2228
2237
|
if (!mero || !groupId || !identity) {
|
|
@@ -2730,6 +2739,80 @@ function useMyAuthoredMigration(contextId) {
|
|
|
2730
2739
|
refresh
|
|
2731
2740
|
};
|
|
2732
2741
|
}
|
|
2742
|
+
function isBlobNotFound(error) {
|
|
2743
|
+
return !!error && error.status === 404;
|
|
2744
|
+
}
|
|
2745
|
+
function useBlobInfo(blobId, options) {
|
|
2746
|
+
const { mero } = useMero();
|
|
2747
|
+
const contextId = options?.contextId;
|
|
2748
|
+
const { data, loading, error, refetch } = useAsyncResource(
|
|
2749
|
+
mero && blobId ? () => mero.admin.getBlobInfo(blobId, contextId ? { contextId } : void 0) : null,
|
|
2750
|
+
null,
|
|
2751
|
+
[mero, blobId, contextId]
|
|
2752
|
+
);
|
|
2753
|
+
return { info: data, notFound: isBlobNotFound(error), loading, error, refetch };
|
|
2754
|
+
}
|
|
2755
|
+
function useBlobUrl(blobId, options) {
|
|
2756
|
+
const { mero } = useMero();
|
|
2757
|
+
const contextId = options?.contextId;
|
|
2758
|
+
const type = options?.type;
|
|
2759
|
+
const mountedRef = useMountedRef();
|
|
2760
|
+
const [url, setUrl] = react.useState(null);
|
|
2761
|
+
const [loading, setLoading] = react.useState(false);
|
|
2762
|
+
const [error, setError] = react.useState(null);
|
|
2763
|
+
const reqRef = react.useRef(0);
|
|
2764
|
+
const urlRef = react.useRef(null);
|
|
2765
|
+
const revoke = react.useCallback(() => {
|
|
2766
|
+
if (urlRef.current) {
|
|
2767
|
+
URL.revokeObjectURL(urlRef.current);
|
|
2768
|
+
urlRef.current = null;
|
|
2769
|
+
}
|
|
2770
|
+
}, []);
|
|
2771
|
+
const refetch = react.useCallback(async () => {
|
|
2772
|
+
const seq = ++reqRef.current;
|
|
2773
|
+
if (!mero || !blobId) return;
|
|
2774
|
+
if (mountedRef.current) {
|
|
2775
|
+
setLoading(true);
|
|
2776
|
+
setError(null);
|
|
2777
|
+
}
|
|
2778
|
+
try {
|
|
2779
|
+
const bytes = await mero.admin.getBlob(blobId, contextId ? { contextId } : void 0);
|
|
2780
|
+
if (!mountedRef.current || seq !== reqRef.current) return;
|
|
2781
|
+
revoke();
|
|
2782
|
+
const objectUrl = URL.createObjectURL(new Blob([bytes], type ? { type } : void 0));
|
|
2783
|
+
urlRef.current = objectUrl;
|
|
2784
|
+
setUrl(objectUrl);
|
|
2785
|
+
} catch (err) {
|
|
2786
|
+
if (mountedRef.current && seq === reqRef.current) setError(toError(err));
|
|
2787
|
+
} finally {
|
|
2788
|
+
if (mountedRef.current && seq === reqRef.current) setLoading(false);
|
|
2789
|
+
}
|
|
2790
|
+
}, [mero, blobId, contextId, type, revoke, mountedRef]);
|
|
2791
|
+
react.useEffect(() => {
|
|
2792
|
+
reqRef.current += 1;
|
|
2793
|
+
revoke();
|
|
2794
|
+
setUrl(null);
|
|
2795
|
+
setError(null);
|
|
2796
|
+
setLoading(false);
|
|
2797
|
+
}, [mero, blobId, contextId, type, revoke]);
|
|
2798
|
+
react.useEffect(() => {
|
|
2799
|
+
void refetch();
|
|
2800
|
+
}, [refetch]);
|
|
2801
|
+
react.useEffect(() => revoke, [revoke]);
|
|
2802
|
+
return { url, notFound: isBlobNotFound(error), loading, error, refetch };
|
|
2803
|
+
}
|
|
2804
|
+
function useUploadBlob() {
|
|
2805
|
+
const { mero } = useMero();
|
|
2806
|
+
const { loading, error, run } = useAsyncMutation();
|
|
2807
|
+
const uploadBlob = react.useCallback(
|
|
2808
|
+
async (request) => {
|
|
2809
|
+
if (!mero) return null;
|
|
2810
|
+
return run(() => mero.admin.uploadBlob(request));
|
|
2811
|
+
},
|
|
2812
|
+
[mero, run]
|
|
2813
|
+
);
|
|
2814
|
+
return { uploadBlob, loading, error };
|
|
2815
|
+
}
|
|
2733
2816
|
function MigrationPendingBanner({
|
|
2734
2817
|
contextId,
|
|
2735
2818
|
className,
|
|
@@ -2895,6 +2978,8 @@ exports.themeToCssVars = themeToCssVars;
|
|
|
2895
2978
|
exports.useAddGroupMembers = useAddGroupMembers;
|
|
2896
2979
|
exports.useAppVersion = useAppVersion;
|
|
2897
2980
|
exports.useApplicationContexts = useApplicationContexts;
|
|
2981
|
+
exports.useBlobInfo = useBlobInfo;
|
|
2982
|
+
exports.useBlobUrl = useBlobUrl;
|
|
2898
2983
|
exports.useContextDiscovery = useContextDiscovery;
|
|
2899
2984
|
exports.useContextGroup = useContextGroup;
|
|
2900
2985
|
exports.useContexts = useContexts;
|
|
@@ -2950,6 +3035,7 @@ exports.useSubscription = useSubscription;
|
|
|
2950
3035
|
exports.useSyncGroup = useSyncGroup;
|
|
2951
3036
|
exports.useUpdateMemberRole = useUpdateMemberRole;
|
|
2952
3037
|
exports.useUpgradeGroup = useUpgradeGroup;
|
|
3038
|
+
exports.useUploadBlob = useUploadBlob;
|
|
2953
3039
|
Object.keys(meroJs).forEach(function (k) {
|
|
2954
3040
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2955
3041
|
enumerable: true,
|