@dxos/halo-react 0.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.
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Context from 'effect/Context';
6
+ import React, { type PropsWithChildren, createContext, useContext } from 'react';
7
+
8
+ import { type Identity, type Space } from '@dxos/halo';
9
+
10
+ /**
11
+ * The HALO services, as the tag union carried in an Effect service {@link Context.Context}.
12
+ */
13
+ export type HaloServices = Identity.Service | Space.Service;
14
+
15
+ /**
16
+ * React context carrying the HALO services. Hooks read it, provide it into a service stream, and
17
+ * surface the result through the default {@link Atom} runtime.
18
+ */
19
+ const HaloServicesContext = createContext<Context.Context<HaloServices> | undefined>(undefined);
20
+
21
+ export type HaloProviderProps = PropsWithChildren<{
22
+ /**
23
+ * The HALO services context. Build it from a concrete backing — e.g. the client adapter's
24
+ * `makeIdentityService` / `makeSpaceService` from `@dxos/halo-adapter-client` — and pass it
25
+ * here.
26
+ */
27
+ services: Context.Context<HaloServices>;
28
+ }>;
29
+
30
+ /**
31
+ * Provides the HALO services to the hook library. Wrap the application (or the subtree that uses
32
+ * the hooks) in this provider. Replaces `ClientProvider` for HALO concerns.
33
+ */
34
+ export const HaloProvider = ({ services, children }: HaloProviderProps) => (
35
+ <HaloServicesContext.Provider value={services}>{children}</HaloServicesContext.Provider>
36
+ );
37
+
38
+ /**
39
+ * Returns the HALO services context. Throws if no {@link HaloProvider} is above in the tree.
40
+ */
41
+ export const useHaloServices = (): Context.Context<HaloServices> => {
42
+ const services = useContext(HaloServicesContext);
43
+ if (!services) {
44
+ throw new Error('Missing HaloProvider.');
45
+ }
46
+ return services;
47
+ };
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './HaloProvider';
6
+ export * from './useCredentials';
7
+ export * from './useDevices';
8
+ export * from './useIdentity';
9
+ export * from './useInvitations';
10
+ export * from './useMembers';
11
+ export * from './useSpaces';
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom, Result, useAtomValue } from '@effect-atom/atom-react';
6
+ import * as Stream from 'effect/Stream';
7
+ import { useMemo } from 'react';
8
+
9
+ import { Identity } from '@dxos/halo';
10
+
11
+ import { useHaloServices } from './HaloProvider';
12
+
13
+ const EMPTY: readonly Identity.Credential[] = [];
14
+
15
+ /**
16
+ * Returns the local identity's HALO credentials. Reactive: re-renders when credentials are
17
+ * written. Replaces `@dxos/react-client`'s `useCredentials`.
18
+ */
19
+ export const useCredentials = (): readonly Identity.Credential[] => {
20
+ const services = useHaloServices();
21
+ const atom = useMemo(() => Atom.make(Identity.credentials.pipe(Stream.provideContext(services))), [services]);
22
+ return Result.getOrElse(useAtomValue(atom), () => EMPTY);
23
+ };
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom, Result, useAtomValue } from '@effect-atom/atom-react';
6
+ import * as Stream from 'effect/Stream';
7
+ import { useMemo } from 'react';
8
+
9
+ import { Identity } from '@dxos/halo';
10
+
11
+ import { useHaloServices } from './HaloProvider';
12
+
13
+ const EMPTY: readonly Identity.DeviceInfo[] = [];
14
+
15
+ /**
16
+ * Returns the devices belonging to the local identity. Reactive. Replaces
17
+ * `@dxos/react-client`'s `useDevices`.
18
+ */
19
+ export const useDevices = (): readonly Identity.DeviceInfo[] => {
20
+ const services = useHaloServices();
21
+ const atom = useMemo(() => Atom.make(Identity.devices.pipe(Stream.provideContext(services))), [services]);
22
+ return Result.getOrElse(useAtomValue(atom), () => EMPTY);
23
+ };
@@ -0,0 +1,25 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom, Result, useAtomValue } from '@effect-atom/atom-react';
6
+ import * as Option from 'effect/Option';
7
+ import * as Stream from 'effect/Stream';
8
+ import { useMemo } from 'react';
9
+
10
+ import { Identity } from '@dxos/halo';
11
+
12
+ import { useHaloServices } from './HaloProvider';
13
+
14
+ /**
15
+ * Returns the local identity, or `undefined` if none exists. Reactive: re-renders when the
16
+ * identity is created or its profile changes. Replaces `@dxos/react-client`'s `useIdentity`.
17
+ */
18
+ export const useIdentity = (): Identity.Info | undefined => {
19
+ const services = useHaloServices();
20
+ const atom = useMemo(
21
+ () => Atom.make(Identity.identity.pipe(Stream.provideContext(services), Stream.map(Option.getOrUndefined))),
22
+ [services],
23
+ );
24
+ return Result.getOrElse(useAtomValue(atom), () => undefined);
25
+ };
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom, Result, useAtomValue } from '@effect-atom/atom-react';
6
+ import * as Stream from 'effect/Stream';
7
+ import { useMemo } from 'react';
8
+
9
+ import { Identity, type Invitation, Space } from '@dxos/halo';
10
+ import { type SpaceId } from '@dxos/keys';
11
+
12
+ import { useHaloServices } from './HaloProvider';
13
+
14
+ const EMPTY: readonly Invitation.Flow[] = [];
15
+
16
+ /**
17
+ * Returns the active (host-created) invitation flows for a space. Reactive. Replaces
18
+ * `@dxos/react-client`'s `useSpaceInvitations`. Drive an individual flow through the
19
+ * `Invitation` verbs (`events` / `authenticate` / `cancel` / `code`).
20
+ */
21
+ export const useSpaceInvitations = (spaceId?: SpaceId): readonly Invitation.Flow[] => {
22
+ const services = useHaloServices();
23
+ const atom = useMemo(
24
+ () => Atom.make((spaceId ? Space.invitations(spaceId) : Stream.empty).pipe(Stream.provideContext(services))),
25
+ [services, spaceId],
26
+ );
27
+ return Result.getOrElse(useAtomValue(atom), () => EMPTY);
28
+ };
29
+
30
+ /**
31
+ * Returns the active device-invitation flows for the local identity. Reactive. Replaces
32
+ * `useHaloInvitations`.
33
+ */
34
+ export const useHaloInvitations = (): readonly Invitation.Flow[] => {
35
+ const services = useHaloServices();
36
+ const atom = useMemo(() => Atom.make(Identity.invitations.pipe(Stream.provideContext(services))), [services]);
37
+ return Result.getOrElse(useAtomValue(atom), () => EMPTY);
38
+ };
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom, Result, useAtomValue } from '@effect-atom/atom-react';
6
+ import * as Stream from 'effect/Stream';
7
+ import { useMemo } from 'react';
8
+
9
+ import { Space } from '@dxos/halo';
10
+ import { type SpaceId } from '@dxos/keys';
11
+
12
+ import { useHaloServices } from './HaloProvider';
13
+
14
+ const EMPTY: readonly Space.Member[] = [];
15
+
16
+ /**
17
+ * Returns the membership of a space. Reactive: re-renders on join/leave/role/presence changes.
18
+ * Replaces `@dxos/react-client`'s `useMembers` (which took a `PublicKey`; this takes a
19
+ * {@link SpaceId}).
20
+ */
21
+ export const useMembers = (spaceId?: SpaceId): readonly Space.Member[] => {
22
+ const services = useHaloServices();
23
+ const atom = useMemo(
24
+ () => Atom.make((spaceId ? Space.members(spaceId) : Stream.empty).pipe(Stream.provideContext(services))),
25
+ [services, spaceId],
26
+ );
27
+ return Result.getOrElse(useAtomValue(atom), () => EMPTY);
28
+ };
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Atom, Result, useAtomValue } from '@effect-atom/atom-react';
6
+ import * as Stream from 'effect/Stream';
7
+ import { useMemo } from 'react';
8
+
9
+ import { Space } from '@dxos/halo';
10
+ import { type SpaceId } from '@dxos/keys';
11
+
12
+ import { useHaloServices } from './HaloProvider';
13
+
14
+ const EMPTY: readonly Space.Info[] = [];
15
+
16
+ export type UseSpacesOptions = {
17
+ /** Include spaces that are not yet `ready` (default: only `ready` spaces). */
18
+ all?: boolean;
19
+ };
20
+
21
+ /**
22
+ * Returns the spaces known to the local identity. By default only `ready` spaces are returned;
23
+ * pass `{ all: true }` for every space. Reactive. Replaces `@dxos/react-client`'s `useSpaces`.
24
+ */
25
+ export const useSpaces = ({ all = false }: UseSpacesOptions = {}): readonly Space.Info[] => {
26
+ const services = useHaloServices();
27
+ const atom = useMemo(() => Atom.make(Space.spaces.pipe(Stream.provideContext(services))), [services]);
28
+ const spaces = Result.getOrElse(useAtomValue(atom), () => EMPTY);
29
+ return useMemo(() => (all ? spaces : spaces.filter((space) => space.state === 'ready')), [spaces, all]);
30
+ };
31
+
32
+ /**
33
+ * Resolves a single space by id, or `undefined`. Reactive. Replaces `useSpace`. Unlike the
34
+ * legacy hook this takes a {@link SpaceId} only (no `PublicKey`).
35
+ */
36
+ export const useSpace = (spaceId?: SpaceId): Space.Info | undefined => {
37
+ const spaces = useSpaces({ all: true });
38
+ return useMemo(() => (spaceId ? spaces.find((space) => space.id === spaceId) : undefined), [spaces, spaceId]);
39
+ };