@dxos/react-client 0.8.4-main.1da679c → 0.8.4-main.21d9917

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.
Files changed (114) hide show
  1. package/.storybook/main.mts +11 -0
  2. package/.storybook/preview.mts +8 -0
  3. package/dist/lib/browser/chunk-5UKA2BMA.mjs +179 -0
  4. package/dist/lib/browser/chunk-5UKA2BMA.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-BXOLJYAY.mjs → chunk-LO3TU5CK.mjs} +4 -4
  6. package/dist/lib/browser/chunk-LO3TU5CK.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-PFF63FPR.mjs +16 -0
  8. package/dist/lib/browser/chunk-PFF63FPR.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-TSF6Y5VB.mjs +8 -0
  10. package/dist/lib/browser/chunk-TSF6Y5VB.mjs.map +7 -0
  11. package/dist/lib/browser/devtools/index.mjs +2 -1
  12. package/dist/lib/browser/devtools/index.mjs.map +1 -1
  13. package/dist/lib/browser/echo/index.mjs +46 -93
  14. package/dist/lib/browser/echo/index.mjs.map +4 -4
  15. package/dist/lib/browser/halo/index.mjs +8 -21
  16. package/dist/lib/browser/halo/index.mjs.map +2 -2
  17. package/dist/lib/browser/index.mjs +114 -12
  18. package/dist/lib/browser/index.mjs.map +4 -4
  19. package/dist/lib/browser/invitations/index.mjs +1 -1
  20. package/dist/lib/browser/mesh/index.mjs +2 -1
  21. package/dist/lib/browser/mesh/index.mjs.map +1 -1
  22. package/dist/lib/browser/meta.json +1 -1
  23. package/dist/lib/browser/testing/index.mjs +91 -83
  24. package/dist/lib/browser/testing/index.mjs.map +3 -3
  25. package/dist/lib/node-esm/chunk-373DPHEQ.mjs +10 -0
  26. package/dist/lib/node-esm/chunk-373DPHEQ.mjs.map +7 -0
  27. package/dist/lib/node-esm/{chunk-256IHY6F.mjs → chunk-AQJH2UNZ.mjs} +4 -4
  28. package/dist/lib/node-esm/chunk-AQJH2UNZ.mjs.map +7 -0
  29. package/dist/lib/node-esm/chunk-NETTSER2.mjs +17 -0
  30. package/dist/lib/node-esm/chunk-NETTSER2.mjs.map +7 -0
  31. package/dist/lib/node-esm/chunk-T3QTQHBD.mjs +180 -0
  32. package/dist/lib/node-esm/chunk-T3QTQHBD.mjs.map +7 -0
  33. package/dist/lib/node-esm/devtools/index.mjs +2 -1
  34. package/dist/lib/node-esm/devtools/index.mjs.map +1 -1
  35. package/dist/lib/node-esm/echo/index.mjs +46 -93
  36. package/dist/lib/node-esm/echo/index.mjs.map +4 -4
  37. package/dist/lib/node-esm/halo/index.mjs +8 -21
  38. package/dist/lib/node-esm/halo/index.mjs.map +2 -2
  39. package/dist/lib/node-esm/index.mjs +114 -12
  40. package/dist/lib/node-esm/index.mjs.map +4 -4
  41. package/dist/lib/node-esm/invitations/index.mjs +1 -1
  42. package/dist/lib/node-esm/mesh/index.mjs +2 -1
  43. package/dist/lib/node-esm/mesh/index.mjs.map +1 -1
  44. package/dist/lib/node-esm/meta.json +1 -1
  45. package/dist/lib/node-esm/testing/index.mjs +91 -83
  46. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  47. package/dist/types/src/client/ClientProvider.d.ts +0 -10
  48. package/dist/types/src/client/ClientProvider.d.ts.map +1 -1
  49. package/dist/types/src/echo/index.d.ts +2 -2
  50. package/dist/types/src/echo/index.d.ts.map +1 -1
  51. package/dist/types/src/echo/useMembers.d.ts +2 -2
  52. package/dist/types/src/echo/useMembers.d.ts.map +1 -1
  53. package/dist/types/src/echo/useQueue.d.ts +2 -2
  54. package/dist/types/src/echo/useQueue.d.ts.map +1 -1
  55. package/dist/types/src/echo/useSpaceInvitations.d.ts +3 -2
  56. package/dist/types/src/echo/useSpaceInvitations.d.ts.map +1 -1
  57. package/dist/types/src/echo/useSpaceProperties.d.ts +13 -0
  58. package/dist/types/src/echo/useSpaceProperties.d.ts.map +1 -0
  59. package/dist/types/src/echo/useSpaces.d.ts +17 -8
  60. package/dist/types/src/echo/useSpaces.d.ts.map +1 -1
  61. package/dist/types/src/echo/useSubscription.d.ts +1 -1
  62. package/dist/types/src/echo/useSubscription.d.ts.map +1 -1
  63. package/dist/types/src/halo/Passkey.stories.d.ts +1 -1
  64. package/dist/types/src/halo/Passkey.stories.d.ts.map +1 -1
  65. package/dist/types/src/invitations/useInvitationStatus.d.ts +1 -1
  66. package/dist/types/src/invitations/useInvitationStatus.d.ts.map +1 -1
  67. package/dist/types/src/testing/ClientRepeater.d.ts +6 -5
  68. package/dist/types/src/testing/ClientRepeater.d.ts.map +1 -1
  69. package/dist/types/src/testing/ClientRepeater.stories.d.ts +0 -1
  70. package/dist/types/src/testing/ClientRepeater.stories.d.ts.map +1 -1
  71. package/dist/types/src/testing/context.d.ts +4 -2
  72. package/dist/types/src/testing/context.d.ts.map +1 -1
  73. package/dist/types/src/testing/withClientProvider.d.ts +5 -9
  74. package/dist/types/src/testing/withClientProvider.d.ts.map +1 -1
  75. package/dist/types/src/testing/withClientProvider.stories.d.ts +1 -1
  76. package/dist/types/src/testing/withClientProvider.stories.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/moon.yml +4 -0
  79. package/package.json +35 -32
  80. package/src/client/ClientProvider.tsx +12 -26
  81. package/src/echo/index.ts +2 -2
  82. package/src/echo/useMembers.ts +5 -3
  83. package/src/echo/useQueue.ts +2 -2
  84. package/src/echo/useSpaceInvitations.ts +9 -4
  85. package/src/echo/useSpaceProperties.ts +28 -0
  86. package/src/echo/useSpaces.test.tsx +1 -1
  87. package/src/echo/useSpaces.ts +33 -12
  88. package/src/echo/useSubscription.ts +1 -1
  89. package/src/echo/useSyncState.ts +2 -2
  90. package/src/halo/Passkey.stories.tsx +11 -14
  91. package/src/invitations/useInvitationStatus.ts +9 -8
  92. package/src/playwright/playwright.config.ts +1 -1
  93. package/src/testing/ClientRepeater.stories.tsx +9 -6
  94. package/src/testing/ClientRepeater.tsx +12 -15
  95. package/src/testing/context.tsx +4 -3
  96. package/src/testing/withClientProvider.stories.tsx +12 -9
  97. package/src/testing/withClientProvider.tsx +75 -59
  98. package/tsconfig.json +3 -6
  99. package/vitest.config.ts +12 -16
  100. package/dist/lib/browser/chunk-BXOLJYAY.mjs.map +0 -7
  101. package/dist/lib/browser/chunk-TA2HGRAF.mjs +0 -309
  102. package/dist/lib/browser/chunk-TA2HGRAF.mjs.map +0 -7
  103. package/dist/lib/node-esm/chunk-256IHY6F.mjs.map +0 -7
  104. package/dist/lib/node-esm/chunk-ZJMNF7GC.mjs +0 -311
  105. package/dist/lib/node-esm/chunk-ZJMNF7GC.mjs.map +0 -7
  106. package/dist/types/src/echo/useQuery.d.ts +0 -11
  107. package/dist/types/src/echo/useQuery.d.ts.map +0 -1
  108. package/dist/types/src/echo/useQuery.test.d.ts +0 -2
  109. package/dist/types/src/echo/useQuery.test.d.ts.map +0 -1
  110. package/dist/types/src/echo/useSchema.d.ts +0 -8
  111. package/dist/types/src/echo/useSchema.d.ts.map +0 -1
  112. package/src/echo/useQuery.test.tsx +0 -129
  113. package/src/echo/useQuery.ts +0 -64
  114. package/src/echo/useSchema.ts +0 -54
package/moon.yml CHANGED
@@ -6,6 +6,7 @@ tags:
6
6
  - ts-test
7
7
  - typedoc
8
8
  - pack
9
+ - storybook
9
10
  tasks:
10
11
  compile:
11
12
  args:
@@ -17,3 +18,6 @@ tasks:
17
18
  - '--entryPoint=src/mesh/index.ts'
18
19
  - '--entryPoint=src/testing/index.ts'
19
20
  - '--entryPoint=src/worker.ts'
21
+ test:
22
+ inputs:
23
+ - .storybook/*
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@dxos/react-client",
3
- "version": "0.8.4-main.1da679c",
3
+ "version": "0.8.4-main.21d9917",
4
4
  "description": "React client API",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  "./devtools": {
@@ -86,43 +90,42 @@
86
90
  },
87
91
  "dependencies": {
88
92
  "react-error-boundary": "^4.0.13",
89
- "@dxos/client": "0.8.4-main.1da679c",
90
- "@dxos/async": "0.8.4-main.1da679c",
91
- "@dxos/context": "0.8.4-main.1da679c",
92
- "@dxos/config": "0.8.4-main.1da679c",
93
- "@dxos/debug": "0.8.4-main.1da679c",
94
- "@dxos/echo": "0.8.4-main.1da679c",
95
- "@dxos/echo-schema": "0.8.4-main.1da679c",
96
- "@dxos/invariant": "0.8.4-main.1da679c",
97
- "@dxos/echo-signals": "0.8.4-main.1da679c",
98
- "@dxos/node-std": "0.8.4-main.1da679c",
99
- "@dxos/log": "0.8.4-main.1da679c",
100
- "@dxos/protocols": "0.8.4-main.1da679c",
101
- "@dxos/react-hooks": "0.8.4-main.1da679c",
102
- "@dxos/codec-protobuf": "0.8.4-main.1da679c",
103
- "@dxos/util": "0.8.4-main.1da679c"
93
+ "@dxos/async": "0.8.4-main.21d9917",
94
+ "@dxos/codec-protobuf": "0.8.4-main.21d9917",
95
+ "@dxos/client": "0.8.4-main.21d9917",
96
+ "@dxos/config": "0.8.4-main.21d9917",
97
+ "@dxos/context": "0.8.4-main.21d9917",
98
+ "@dxos/echo-react": "0.8.4-main.21d9917",
99
+ "@dxos/invariant": "0.8.4-main.21d9917",
100
+ "@dxos/debug": "0.8.4-main.21d9917",
101
+ "@dxos/echo": "0.8.4-main.21d9917",
102
+ "@dxos/log": "0.8.4-main.21d9917",
103
+ "@dxos/node-std": "0.8.4-main.21d9917",
104
+ "@dxos/util": "0.8.4-main.21d9917",
105
+ "@dxos/protocols": "0.8.4-main.21d9917",
106
+ "@dxos/react-hooks": "0.8.4-main.21d9917"
104
107
  },
105
108
  "devDependencies": {
106
- "@types/react": "~18.2.0",
107
- "@types/react-dom": "~18.2.0",
108
- "react": "~18.2.0",
109
- "react-dom": "~18.2.0",
109
+ "@types/react": "~19.2.7",
110
+ "@types/react-dom": "~19.2.3",
111
+ "react": "~19.2.3",
112
+ "react-dom": "~19.2.3",
110
113
  "typedoc": "0.28.1",
111
- "@dxos/react-ui": "0.8.4-main.1da679c",
112
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.1da679c",
113
- "@dxos/react-ui-theme": "0.8.4-main.1da679c",
114
- "@dxos/storybook-utils": "0.8.4-main.1da679c",
115
- "@dxos/test-utils": "0.8.4-main.1da679c",
116
- "@dxos/random": "0.8.4-main.1da679c"
114
+ "@dxos/react-ui": "0.8.4-main.21d9917",
115
+ "@dxos/random": "0.8.4-main.21d9917",
116
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.21d9917",
117
+ "@dxos/test-utils": "0.8.4-main.21d9917",
118
+ "@dxos/storybook-utils": "0.8.4-main.21d9917",
119
+ "@dxos/ui-theme": "0.8.4-main.21d9917"
117
120
  },
118
121
  "peerDependencies": {
119
- "react": "~18.2.0",
120
- "react-dom": "~18.2.0"
122
+ "react": "~19.2.3",
123
+ "react-dom": "~19.2.3"
121
124
  },
122
125
  "optionalDependencies": {
123
- "@dxos/random": "0.8.4-main.1da679c",
124
- "@dxos/react-ui": "0.8.4-main.1da679c",
125
- "@dxos/react-ui-theme": "0.8.4-main.1da679c"
126
+ "@dxos/random": "0.8.4-main.21d9917",
127
+ "@dxos/react-ui": "0.8.4-main.21d9917",
128
+ "@dxos/ui-theme": "0.8.4-main.21d9917"
126
129
  },
127
130
  "publishConfig": {
128
131
  "access": "public"
@@ -13,7 +13,6 @@ import React, {
13
13
 
14
14
  import { Client, type ClientOptions, type ClientServicesProvider, SystemStatus } from '@dxos/client';
15
15
  import { type Config } from '@dxos/config';
16
- import { registerSignalsRuntime } from '@dxos/echo-signals/react';
17
16
  import { log } from '@dxos/log';
18
17
  import { useAsyncEffect, useControlledState } from '@dxos/react-hooks';
19
18
  import { type MaybePromise, type Provider, getAsyncProviderValue } from '@dxos/util';
@@ -56,12 +55,6 @@ export type ClientProviderProps = Omit<ClientOptions, 'config' | 'services'> &
56
55
  */
57
56
  fallback?: FunctionComponent<Partial<ClientContextProps>>;
58
57
 
59
- /**
60
- * Enable (by default) registration of Preact signals runtime for reactive ECHO objects.
61
- * @see https://www.npmjs.com/package/@preact/signals-react
62
- */
63
- signalsRuntime?: boolean;
64
-
65
58
  /**
66
59
  * Skip the DXOS banner.
67
60
  */
@@ -81,24 +74,17 @@ export const ClientProvider = forwardRef<Client | undefined, ClientProviderProps
81
74
  (
82
75
  {
83
76
  children,
84
- config: configParam,
85
- client: clientParam,
86
- services: servicesParam,
87
- status: statusParam,
77
+ config: configProp,
78
+ client: clientProp,
79
+ services: servicesProp,
80
+ status: statusProp,
88
81
  fallback: Fallback = () => null,
89
- signalsRuntime = true,
90
82
  noBanner,
91
83
  onInitialized,
92
84
  ...options
93
85
  },
94
86
  forwardedRef,
95
87
  ) => {
96
- useEffect(() => {
97
- // TODO(wittjosiah): Ideally this should be imported asynchronously because it is optional.
98
- // Unfortunately, async import seemed to break signals React instrumentation.
99
- signalsRuntime && registerSignalsRuntime();
100
- }, []);
101
-
102
88
  // The client is initialized asynchronously.
103
89
  // If an error occurs during initialization, it is caught and the state is set.
104
90
  // This allows the error to be thrown in the render method.
@@ -109,13 +95,13 @@ export const ClientProvider = forwardRef<Client | undefined, ClientProviderProps
109
95
  throw error;
110
96
  }
111
97
 
112
- const [client, setClient] = useState(clientParam instanceof Client ? clientParam : undefined);
98
+ const [client, setClient] = useState(clientProp instanceof Client ? clientProp : undefined);
113
99
 
114
100
  // Provide external access.
115
101
  useImperativeHandle(forwardedRef, () => client, [client]);
116
102
 
117
103
  // Client status subscription.
118
- const [status, setStatus] = useControlledState(statusParam);
104
+ const [status, setStatus] = useControlledState(statusProp);
119
105
  useEffect(() => {
120
106
  if (!client) {
121
107
  return;
@@ -148,15 +134,15 @@ export const ClientProvider = forwardRef<Client | undefined, ClientProviderProps
148
134
 
149
135
  let client: Client;
150
136
  try {
151
- if (clientParam) {
137
+ if (clientProp) {
152
138
  // Asynchronously request client.
153
- client = await getAsyncProviderValue(clientParam);
139
+ client = await getAsyncProviderValue(clientProp);
154
140
  await initialize(client);
155
141
  } else {
156
142
  // Asynchronously construct client (config may be undefined).
157
- const config = await getAsyncProviderValue(configParam);
143
+ const config = await getAsyncProviderValue(configProp);
158
144
  log('resolved config', { config });
159
- const services = await getAsyncProviderValue(servicesParam, config);
145
+ const services = await getAsyncProviderValue(servicesProp, config);
160
146
  log('created services', { services });
161
147
  client = new Client({ config, services, ...options });
162
148
  log('created client');
@@ -172,7 +158,7 @@ export const ClientProvider = forwardRef<Client | undefined, ClientProviderProps
172
158
  log('clean up');
173
159
  disposed = true;
174
160
  // Only destroy if the client is not provided by the parent.
175
- if (!clientParam) {
161
+ if (!clientProp) {
176
162
  void client
177
163
  ?.destroy()
178
164
  .then(() => {
@@ -181,7 +167,7 @@ export const ClientProvider = forwardRef<Client | undefined, ClientProviderProps
181
167
  .catch((err) => log.catch(err));
182
168
  }
183
169
  };
184
- }, [configParam, clientParam, servicesParam, noBanner]);
170
+ }, [configProp, clientProp, servicesProp, noBanner]);
185
171
 
186
172
  if (!client?.initialized || status !== SystemStatus.ACTIVE) {
187
173
  return <Fallback client={client} status={status} />;
package/src/echo/index.ts CHANGED
@@ -3,12 +3,12 @@
3
3
  //
4
4
 
5
5
  export * from '@dxos/client/echo';
6
+ export * from '@dxos/echo-react';
6
7
 
7
8
  export * from './useMembers';
8
- export * from './useQuery';
9
9
  export * from './useQueue';
10
- export * from './useSchema';
11
10
  export * from './useSpaces';
12
11
  export * from './useSpaceInvitations';
12
+ export * from './useSpaceProperties';
13
13
  export * from './useSubscription';
14
14
  export * from './useSyncState';
@@ -3,16 +3,18 @@
3
3
  //
4
4
 
5
5
  import { MulticastObservable } from '@dxos/async';
6
- import { type PublicKey } from '@dxos/client';
6
+ import { PublicKey, type PublicKeyLike } from '@dxos/client';
7
7
  import { HaloSpaceMember, type SpaceMember } from '@dxos/client/echo';
8
8
  import { useMulticastObservable } from '@dxos/react-hooks';
9
9
 
10
10
  import { useSpaces } from './useSpaces';
11
11
 
12
- export const useMembers = (spaceKey: PublicKey | undefined): SpaceMember[] => {
12
+ export const useMembers = (spaceKey: PublicKeyLike | undefined): SpaceMember[] => {
13
13
  // TODO(dmaretskyi): useSpace hook for spaces that are not ready.
14
14
  const spaces = useSpaces({ all: true });
15
- const space = spaceKey ? spaces.find((space) => space.key.equals(spaceKey)) : undefined;
15
+ const space = spaceKey
16
+ ? spaces.find((space) => (spaceKey instanceof PublicKey ? space.key.equals(spaceKey) : space.id === spaceKey))
17
+ : undefined;
16
18
 
17
19
  // EMPTY_OBSERVABLE needs to be a stable reference to avoid re-subscribing on every render.
18
20
  const members = useMulticastObservable(space?.members ?? MulticastObservable.empty()) ?? [];
@@ -6,7 +6,7 @@ import { useEffect, useMemo, useRef } from 'react';
6
6
 
7
7
  import { type Queue } from '@dxos/client/echo';
8
8
  import { raise } from '@dxos/debug';
9
- import { type Obj, type Relation } from '@dxos/echo';
9
+ import { type Entity } from '@dxos/echo';
10
10
  import { type DXN } from '@dxos/keys';
11
11
 
12
12
  import { useClient } from '../client';
@@ -24,7 +24,7 @@ export type UseQueueOptions = {
24
24
  // TODO(dmaretskyi): Move into client package.
25
25
  // TODO(dmaretskyi): Consider passing the space into the hook to support queue DXNs without space id.
26
26
  // TODO(ZaymonFC): If queue is unchanged returned object should be refferentially stable on poll.
27
- export const useQueue = <T extends Obj.Any | Relation.Any>(
27
+ export const useQueue = <T extends Entity.Unknown>(
28
28
  queueDxn?: DXN,
29
29
  options: UseQueueOptions = {},
30
30
  ): Queue<T> | undefined => {
@@ -6,19 +6,24 @@ import { useMemo } from 'react';
6
6
 
7
7
  import { MulticastObservable } from '@dxos/async';
8
8
  import { type PublicKey } from '@dxos/client';
9
+ import { type SpaceId } from '@dxos/client/echo';
9
10
  import { useMulticastObservable } from '@dxos/react-hooks';
10
11
 
11
12
  import { type CancellableInvitationObservable, useInvitationStatus } from '../invitations';
12
13
 
13
14
  import { useSpace } from './useSpaces';
14
15
 
15
- export const useSpaceInvitations = (spaceKey?: PublicKey): CancellableInvitationObservable[] => {
16
- const space = useSpace(spaceKey);
16
+ // TODO(wittjosiah): Currently unable to remove `PublicKey` from this api.
17
+ // When initially joining a space that is all that is returned.
18
+ export const useSpaceInvitations = (spaceId?: SpaceId | PublicKey): CancellableInvitationObservable[] => {
19
+ const space = useSpace(spaceId);
17
20
  return useMulticastObservable(space?.invitations ?? MulticastObservable.empty()) ?? [];
18
21
  };
19
22
 
20
- export const useSpaceInvitation = (spaceKey?: PublicKey, invitationId?: string) => {
21
- const invitations = useSpaceInvitations(spaceKey);
23
+ // TODO(wittjosiah): Currently unable to remove `PublicKey` from this api.
24
+ // When initially joining a space that is all that is returned.
25
+ export const useSpaceInvitation = (spaceId?: SpaceId | PublicKey, invitationId?: string) => {
26
+ const invitations = useSpaceInvitations(spaceId);
22
27
  const invitation = useMemo(
23
28
  () => invitations.find((invitation) => invitation.get().invitationId === invitationId),
24
29
  [invitations],
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { MulticastObservable } from '@dxos/async';
6
+ import { type SpaceProperties, SpaceState } from '@dxos/client/echo';
7
+ import { type Key, type Obj } from '@dxos/echo';
8
+ import { type ObjectUpdateCallback, useObject } from '@dxos/echo-react';
9
+ import { useMulticastObservable } from '@dxos/react-hooks';
10
+
11
+ import { useSpace } from './useSpaces';
12
+
13
+ /**
14
+ * Subscribe to a space's properties with reactive updates.
15
+ * Returns undefined if the space is undefined or not ready yet.
16
+ * Automatically waits for the space to be ready before returning properties.
17
+ *
18
+ * @param spaceId - The space ID to get properties from (can be undefined).
19
+ * @returns A tuple of [snapshot, updateCallback] or [undefined, no-op] if not ready.
20
+ */
21
+ export const useSpaceProperties = (
22
+ spaceId: Key.SpaceId | undefined,
23
+ ): [Obj.Snapshot<SpaceProperties> | undefined, ObjectUpdateCallback<SpaceProperties>] => {
24
+ const space = useSpace(spaceId);
25
+ const spaceState = useMulticastObservable(space?.state ?? MulticastObservable.empty());
26
+ const properties = spaceState === SpaceState.SPACE_READY ? space?.properties : undefined;
27
+ return useObject(properties);
28
+ };
@@ -47,7 +47,7 @@ describe('useSpace', () => {
47
47
  test('gets space by key', async () => {
48
48
  const { client, space } = await createClient({ createIdentity: true, createSpace: true });
49
49
  const wrapper = await createClientContextProvider(client);
50
- const { result } = renderHook(() => useSpace(space!.key), { wrapper });
50
+ const { result } = renderHook(() => useSpace(space!.id), { wrapper });
51
51
  expect(result.current).to.not.be.undefined;
52
52
  });
53
53
  });
@@ -4,37 +4,45 @@
4
4
 
5
5
  import { useState } from 'react';
6
6
 
7
- import { type PublicKeyLike } from '@dxos/client';
8
- import { type Space, SpaceState } from '@dxos/client/echo';
7
+ import { PublicKey } from '@dxos/client';
8
+ import { type Space, type SpaceId, SpaceState } from '@dxos/client/echo';
9
9
  import { useAsyncEffect, useMulticastObservable } from '@dxos/react-hooks';
10
10
 
11
11
  import { useClient } from '../client';
12
12
 
13
13
  /**
14
- * Get a specific Space using its key.
14
+ * Get a specific Space using its id.
15
15
  * The space is not guaranteed to be in the ready state.
16
- * Returns the default space if no key is provided.
16
+ * Returns the default space if no id is provided.
17
17
  * Requires a ClientProvider somewhere in the parent tree.
18
18
  *
19
- * @param spaceKeyLike the key of the space to look for
19
+ * @param spaceId the id of the space to look for
20
20
  */
21
- export const useSpace = (spaceKeyLike?: PublicKeyLike): Space | undefined => {
21
+ // TODO(wittjosiah): Currently unable to remove `PublicKey` from this api.
22
+ // When initially joining a space that is all that is returned.
23
+ export const useSpace = (spaceId?: SpaceId | PublicKey): Space | undefined => {
22
24
  const client = useClient();
23
25
  const spaces = useMulticastObservable<Space[]>(client.spaces);
24
26
  const [ready, setReady] = useState(false);
25
27
 
26
28
  useAsyncEffect(async () => {
27
29
  // Only wait for ready if looking for the default space.
28
- if (spaceKeyLike) {
30
+ if (spaceId) {
29
31
  return;
30
32
  }
31
33
 
32
34
  await client.spaces.waitUntilReady();
33
35
  setReady(true);
34
- }, [client, spaceKeyLike]);
36
+ }, [client, spaceId]);
35
37
 
36
- if (spaceKeyLike) {
37
- return spaces.find((space) => space.key.equals(spaceKeyLike) || space.id === spaceKeyLike);
38
+ if (spaceId) {
39
+ return spaces.find((space) => {
40
+ if (spaceId instanceof PublicKey) {
41
+ return space.key.equals(spaceId);
42
+ }
43
+
44
+ return space.id === spaceId;
45
+ });
38
46
  }
39
47
 
40
48
  if (ready && client.halo.identity.get()) {
@@ -42,7 +50,20 @@ export const useSpace = (spaceKeyLike?: PublicKeyLike): Space | undefined => {
42
50
  }
43
51
  };
44
52
 
45
- export type UseSpacesParams = {
53
+ /**
54
+ * Get a Space database by the id of the space.
55
+ * The space is not guaranteed to be in the ready state.
56
+ * Returns the default space if no id is provided.
57
+ * Requires a ClientProvider somewhere in the parent tree.
58
+ *
59
+ * @param spaceId the id of the space to look for
60
+ */
61
+ export const useDatabase = (spaceId?: SpaceId): Space['db'] | undefined => {
62
+ const space = useSpace(spaceId);
63
+ return space?.db;
64
+ };
65
+
66
+ export type UseSpacesProps = {
46
67
  /**
47
68
  * Return uninitialized spaces as well.
48
69
  */
@@ -55,7 +76,7 @@ export type UseSpacesParams = {
55
76
  * By default, only ready spaces are returned.
56
77
  * @returns an array of Spaces
57
78
  */
58
- export const useSpaces = ({ all = false }: UseSpacesParams = {}): Space[] => {
79
+ export const useSpaces = ({ all = false }: UseSpacesProps = {}): Space[] => {
59
80
  const client = useClient();
60
81
  const spaces = useMulticastObservable<Space[]>(client.spaces);
61
82
 
@@ -18,7 +18,7 @@ export const useSubscription = (cb: () => void, selection: Selection) => {
18
18
  const callbackRef = useRef(cb);
19
19
  callbackRef.current = cb;
20
20
 
21
- const subscriptionRef = useRef<SubscriptionHandle>();
21
+ const subscriptionRef = useRef<SubscriptionHandle>(null);
22
22
 
23
23
  useEffect(() => {
24
24
  subscriptionRef.current = createSubscription(() => {
@@ -30,7 +30,7 @@ export const useSyncState = (): SpaceSyncStateMap => {
30
30
  }
31
31
 
32
32
  ctx.onDispose(
33
- space.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
33
+ space.internal.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
34
34
  const syncState = peers.find((state) => isEdgePeerId(state.peerId, space.id));
35
35
  if (syncState) {
36
36
  setSpaceState((spaceState) => ({ ...spaceState, [space.id]: syncState }));
@@ -62,7 +62,7 @@ export const useSpaceSyncState = (space: Space): PeerSyncState | undefined => {
62
62
 
63
63
  useEffect(() => {
64
64
  const ctx = new Context();
65
- space.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
65
+ space.internal.db.subscribeToSyncState(ctx, ({ peers = [] }) => {
66
66
  const syncState = peers.find((state) => isEdgePeerId(state.peerId, space.id));
67
67
  if (syncState) {
68
68
  setSpaceState(syncState);
@@ -2,8 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useCallback } from 'react';
9
7
 
@@ -11,8 +9,8 @@ import { Config, PublicKey } from '@dxos/client';
11
9
  import { invariant } from '@dxos/invariant';
12
10
  import { log } from '@dxos/log';
13
11
  import { Button } from '@dxos/react-ui';
12
+ import { withTheme } from '@dxos/react-ui/testing';
14
13
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
15
- import { withTheme } from '@dxos/storybook-utils';
16
14
 
17
15
  import { useClient } from '../client';
18
16
  import { withClientProvider } from '../testing';
@@ -127,15 +125,6 @@ const Test = () => {
127
125
  );
128
126
  };
129
127
 
130
- const meta = {
131
- title: 'sdk/react-client/Passkeys',
132
- render: Test,
133
- } satisfies Meta<typeof Config>;
134
-
135
- export default meta;
136
-
137
- type Story = StoryObj<typeof meta>;
138
-
139
128
  const config = new Config({
140
129
  runtime: {
141
130
  client: {
@@ -156,6 +145,14 @@ const config = new Config({
156
145
  },
157
146
  });
158
147
 
159
- export const Default: Story = {
148
+ const meta = {
149
+ title: 'sdk/react-client/Passkeys',
150
+ render: Test,
160
151
  decorators: [withClientProvider({ config }), withTheme],
161
- };
152
+ } satisfies Meta;
153
+
154
+ export default meta;
155
+
156
+ type Story = StoryObj<typeof meta>;
157
+
158
+ export const Default: Story = {};
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { type Reducer, useCallback, useEffect, useMemo, useReducer } from 'react';
5
+ import { useCallback, useEffect, useMemo, useReducer } from 'react';
6
6
 
7
7
  import { type PublicKey } from '@dxos/client';
8
8
  import {
@@ -24,9 +24,10 @@ interface InvitationReducerState {
24
24
  status: Invitation.State; // TODO(burdon): Rename state.
25
25
  haltedAt?: Invitation.State;
26
26
  result: InvitationResult;
27
- error?: number;
27
+ error?: Error;
28
28
  id?: string;
29
29
  multiUse?: boolean;
30
+ shareable?: boolean;
30
31
  invitationCode?: string;
31
32
  authCode?: string;
32
33
  authMethod?: Invitation.AuthMethod;
@@ -72,7 +73,7 @@ export type InvitationStatus = {
72
73
  multiUse?: boolean;
73
74
  shareable?: boolean;
74
75
  result: InvitationResult;
75
- error?: number;
76
+ error?: Error;
76
77
  cancel(): void;
77
78
  // TODO(wittjosiah): Remove?
78
79
  connect(observable: CancellableInvitationObservable): void;
@@ -82,11 +83,11 @@ export type InvitationStatus = {
82
83
  // Without private key, the invitation code cannot be created.
83
84
  // These invitations are only available to be accepted but not shared.
84
85
  const isShareableInvitation = (invitation: Invitation) =>
85
- invitation.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY || invitation.guestKeypair?.privateKey;
86
+ invitation.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY || !!invitation.guestKeypair?.privateKey;
86
87
 
87
88
  export const useInvitationStatus = (observable?: CancellableInvitationObservable): InvitationStatus => {
88
- const [state, dispatch] = useReducer<Reducer<InvitationReducerState, InvitationAction>, null>(
89
- (prev, action) => {
89
+ const [state, dispatch] = useReducer(
90
+ (prev: InvitationReducerState, action: InvitationAction): InvitationReducerState => {
90
91
  log('useInvitationStatus', { action });
91
92
  const invitationProps =
92
93
  'invitation' in action
@@ -115,10 +116,10 @@ export const useInvitationStatus = (observable?: CancellableInvitationObservable
115
116
  action.status === Invitation.State.TIMEOUT) && {
116
117
  haltedAt: typeof prev.haltedAt === 'undefined' ? action.haltedAt : prev.haltedAt,
117
118
  }),
118
- } as InvitationReducerState;
119
+ };
119
120
  },
120
121
  null,
121
- (_arg: null) => {
122
+ (_arg: null): InvitationReducerState => {
122
123
  const invitation = observable?.get();
123
124
  return {
124
125
  status: Invitation.State.INIT,
@@ -10,7 +10,7 @@ export default defineConfig({
10
10
  ...e2ePreset(import.meta.dirname),
11
11
  // TODO(wittjosiah): Avoid hard-coding ports.
12
12
  webServer: {
13
- command: 'moon run storybook:serve-e2e -- --port=9000',
13
+ command: 'moon run storybook-react:serve-e2e -- --port=9000',
14
14
  port: 9000,
15
15
  reuseExistingServer: false,
16
16
  },
@@ -2,14 +2,13 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
8
+ import { Obj } from '@dxos/echo';
10
9
  import { Input } from '@dxos/react-ui';
10
+ import { withTheme } from '@dxos/react-ui/testing';
11
11
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
12
- import { withTheme } from '@dxos/storybook-utils';
13
12
 
14
13
  import { useClient } from '../client';
15
14
  import { useSpace } from '../echo';
@@ -34,9 +33,9 @@ const ClientStory = () => {
34
33
  return <JsonPanel value={client.toJSON()} />;
35
34
  };
36
35
 
37
- const ClientSpace = ({ spaceKey }: ClientRepeatedComponentProps) => {
36
+ const ClientSpace = ({ spaceId }: ClientRepeatedComponentProps) => {
38
37
  const client = useClient();
39
- const space = useSpace(spaceKey);
38
+ const space = useSpace(spaceId);
40
39
  if (!space?.isOpen) {
41
40
  return null;
42
41
  }
@@ -47,7 +46,11 @@ const ClientSpace = ({ spaceKey }: ClientRepeatedComponentProps) => {
47
46
  <Input.TextInput
48
47
  placeholder='Name'
49
48
  value={space.properties.name}
50
- onChange={(event) => (space.properties.name = event.target.value)}
49
+ onChange={(event) =>
50
+ Obj.change(space.properties, (p) => {
51
+ p.name = event.target.value;
52
+ })
53
+ }
51
54
  />
52
55
  </Input.Root>
53
56
  <JsonPanel value={client.toJSON()} />