@calimero-network/mero-react 8.0.0 → 9.1.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/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
@@ -1949,9 +1949,9 @@ function useDeleteGroup() {
1949
1949
  const { mero } = useMero();
1950
1950
  const { loading, error, run } = useAsyncMutation();
1951
1951
  const deleteGroup = react.useCallback(
1952
- async (groupId, request) => {
1952
+ async (groupId) => {
1953
1953
  if (!mero) return null;
1954
- return run(() => mero.admin.deleteGroup(groupId, request));
1954
+ return run(() => mero.admin.deleteGroup(groupId));
1955
1955
  },
1956
1956
  [mero, run]
1957
1957
  );
@@ -1961,9 +1961,9 @@ function useSyncGroup() {
1961
1961
  const { mero } = useMero();
1962
1962
  const { loading, error, run } = useAsyncMutation();
1963
1963
  const syncGroup = react.useCallback(
1964
- async (groupId, request) => {
1964
+ async (groupId) => {
1965
1965
  if (!mero) return null;
1966
- return run(() => mero.admin.syncGroup(groupId, request));
1966
+ return run(() => mero.admin.syncGroup(groupId));
1967
1967
  },
1968
1968
  [mero, run]
1969
1969
  );
@@ -2054,9 +2054,9 @@ function useDeleteNamespace() {
2054
2054
  const { mero } = useMero();
2055
2055
  const { loading, error, run } = useAsyncMutation();
2056
2056
  const deleteNamespace = react.useCallback(
2057
- async (namespaceId, request) => {
2057
+ async (namespaceId) => {
2058
2058
  if (!mero) return null;
2059
- return run(() => mero.admin.deleteNamespace(namespaceId, request));
2059
+ return run(() => mero.admin.deleteNamespace(namespaceId));
2060
2060
  },
2061
2061
  [mero, run]
2062
2062
  );
@@ -2277,9 +2277,9 @@ function useInstallFromRegistry() {
2277
2277
  const { mero } = useMero();
2278
2278
  const { loading, error, run } = useAsyncMutation();
2279
2279
  const installFromRegistry = react.useCallback(
2280
- async (registryUrl, packageName, version) => {
2280
+ async (packageName, version) => {
2281
2281
  if (!mero) return null;
2282
- return run(() => mero.admin.installFromRegistry(registryUrl, packageName, version));
2282
+ return run(() => mero.admin.installApplication({ package: packageName, version }));
2283
2283
  },
2284
2284
  [mero, run]
2285
2285
  );
@@ -2348,9 +2348,9 @@ function useRetryGroupUpgrade() {
2348
2348
  const { mero } = useMero();
2349
2349
  const { loading, error, run } = useAsyncMutation();
2350
2350
  const retryGroupUpgrade = react.useCallback(
2351
- async (groupId, request) => {
2351
+ async (groupId) => {
2352
2352
  if (!mero) return null;
2353
- return run(() => mero.admin.retryGroupUpgrade(groupId, request));
2353
+ return run(() => mero.admin.retryGroupUpgrade(groupId));
2354
2354
  },
2355
2355
  [mero, run]
2356
2356
  );
@@ -2381,9 +2381,9 @@ function useDetachContextFromGroup() {
2381
2381
  const { mero } = useMero();
2382
2382
  const { loading, error, run } = useAsyncMutation();
2383
2383
  const detachContextFromGroup = react.useCallback(
2384
- async (groupId, contextId, request) => {
2384
+ async (groupId, contextId) => {
2385
2385
  if (!mero) return null;
2386
- return run(() => mero.admin.detachContextFromGroup(groupId, contextId, request));
2386
+ return run(() => mero.admin.detachContextFromGroup(groupId, contextId));
2387
2387
  },
2388
2388
  [mero, run]
2389
2389
  );
@@ -2730,6 +2730,80 @@ function useMyAuthoredMigration(contextId) {
2730
2730
  refresh
2731
2731
  };
2732
2732
  }
2733
+ function isBlobNotFound(error) {
2734
+ return !!error && error.status === 404;
2735
+ }
2736
+ function useBlobInfo(blobId, options) {
2737
+ const { mero } = useMero();
2738
+ const contextId = options?.contextId;
2739
+ const { data, loading, error, refetch } = useAsyncResource(
2740
+ mero && blobId ? () => mero.admin.getBlobInfo(blobId, contextId ? { contextId } : void 0) : null,
2741
+ null,
2742
+ [mero, blobId, contextId]
2743
+ );
2744
+ return { info: data, notFound: isBlobNotFound(error), loading, error, refetch };
2745
+ }
2746
+ function useBlobUrl(blobId, options) {
2747
+ const { mero } = useMero();
2748
+ const contextId = options?.contextId;
2749
+ const type = options?.type;
2750
+ const mountedRef = useMountedRef();
2751
+ const [url, setUrl] = react.useState(null);
2752
+ const [loading, setLoading] = react.useState(false);
2753
+ const [error, setError] = react.useState(null);
2754
+ const reqRef = react.useRef(0);
2755
+ const urlRef = react.useRef(null);
2756
+ const revoke = react.useCallback(() => {
2757
+ if (urlRef.current) {
2758
+ URL.revokeObjectURL(urlRef.current);
2759
+ urlRef.current = null;
2760
+ }
2761
+ }, []);
2762
+ const refetch = react.useCallback(async () => {
2763
+ const seq = ++reqRef.current;
2764
+ if (!mero || !blobId) return;
2765
+ if (mountedRef.current) {
2766
+ setLoading(true);
2767
+ setError(null);
2768
+ }
2769
+ try {
2770
+ const bytes = await mero.admin.getBlob(blobId, contextId ? { contextId } : void 0);
2771
+ if (!mountedRef.current || seq !== reqRef.current) return;
2772
+ revoke();
2773
+ const objectUrl = URL.createObjectURL(new Blob([bytes], type ? { type } : void 0));
2774
+ urlRef.current = objectUrl;
2775
+ setUrl(objectUrl);
2776
+ } catch (err) {
2777
+ if (mountedRef.current && seq === reqRef.current) setError(toError(err));
2778
+ } finally {
2779
+ if (mountedRef.current && seq === reqRef.current) setLoading(false);
2780
+ }
2781
+ }, [mero, blobId, contextId, type, revoke, mountedRef]);
2782
+ react.useEffect(() => {
2783
+ reqRef.current += 1;
2784
+ revoke();
2785
+ setUrl(null);
2786
+ setError(null);
2787
+ setLoading(false);
2788
+ }, [mero, blobId, contextId, type, revoke]);
2789
+ react.useEffect(() => {
2790
+ void refetch();
2791
+ }, [refetch]);
2792
+ react.useEffect(() => revoke, [revoke]);
2793
+ return { url, notFound: isBlobNotFound(error), loading, error, refetch };
2794
+ }
2795
+ function useUploadBlob() {
2796
+ const { mero } = useMero();
2797
+ const { loading, error, run } = useAsyncMutation();
2798
+ const uploadBlob = react.useCallback(
2799
+ async (request) => {
2800
+ if (!mero) return null;
2801
+ return run(() => mero.admin.uploadBlob(request));
2802
+ },
2803
+ [mero, run]
2804
+ );
2805
+ return { uploadBlob, loading, error };
2806
+ }
2733
2807
  function MigrationPendingBanner({
2734
2808
  contextId,
2735
2809
  className,
@@ -2895,6 +2969,8 @@ exports.themeToCssVars = themeToCssVars;
2895
2969
  exports.useAddGroupMembers = useAddGroupMembers;
2896
2970
  exports.useAppVersion = useAppVersion;
2897
2971
  exports.useApplicationContexts = useApplicationContexts;
2972
+ exports.useBlobInfo = useBlobInfo;
2973
+ exports.useBlobUrl = useBlobUrl;
2898
2974
  exports.useContextDiscovery = useContextDiscovery;
2899
2975
  exports.useContextGroup = useContextGroup;
2900
2976
  exports.useContexts = useContexts;
@@ -2950,6 +3026,7 @@ exports.useSubscription = useSubscription;
2950
3026
  exports.useSyncGroup = useSyncGroup;
2951
3027
  exports.useUpdateMemberRole = useUpdateMemberRole;
2952
3028
  exports.useUpgradeGroup = useUpgradeGroup;
3029
+ exports.useUploadBlob = useUploadBlob;
2953
3030
  Object.keys(meroJs).forEach(function (k) {
2954
3031
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
2955
3032
  enumerable: true,