@dxos/react-client 0.8.4-main.e098934 → 0.8.4-main.e8ec1fe

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 (57) hide show
  1. package/.storybook/main.mts +11 -0
  2. package/.storybook/preview.mts +8 -0
  3. package/dist/lib/browser/{chunk-BXOLJYAY.mjs → chunk-LO3TU5CK.mjs} +4 -4
  4. package/dist/lib/browser/chunk-LO3TU5CK.mjs.map +7 -0
  5. package/dist/lib/browser/echo/index.mjs +8 -5
  6. package/dist/lib/browser/echo/index.mjs.map +3 -3
  7. package/dist/lib/browser/halo/index.mjs +6 -20
  8. package/dist/lib/browser/halo/index.mjs.map +2 -2
  9. package/dist/lib/browser/invitations/index.mjs +1 -1
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/browser/testing/index.mjs +19 -16
  12. package/dist/lib/browser/testing/index.mjs.map +3 -3
  13. package/dist/lib/node-esm/{chunk-256IHY6F.mjs → chunk-AQJH2UNZ.mjs} +4 -4
  14. package/dist/lib/node-esm/chunk-AQJH2UNZ.mjs.map +7 -0
  15. package/dist/lib/node-esm/echo/index.mjs +8 -5
  16. package/dist/lib/node-esm/echo/index.mjs.map +3 -3
  17. package/dist/lib/node-esm/halo/index.mjs +6 -20
  18. package/dist/lib/node-esm/halo/index.mjs.map +2 -2
  19. package/dist/lib/node-esm/invitations/index.mjs +1 -1
  20. package/dist/lib/node-esm/meta.json +1 -1
  21. package/dist/lib/node-esm/testing/index.mjs +19 -16
  22. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  23. package/dist/types/src/echo/useQuery.d.ts +3 -3
  24. package/dist/types/src/echo/useQuery.d.ts.map +1 -1
  25. package/dist/types/src/echo/useSchema.d.ts +1 -1
  26. package/dist/types/src/echo/useSchema.d.ts.map +1 -1
  27. package/dist/types/src/echo/useSubscription.d.ts +1 -1
  28. package/dist/types/src/echo/useSubscription.d.ts.map +1 -1
  29. package/dist/types/src/halo/Passkey.stories.d.ts +1 -1
  30. package/dist/types/src/halo/Passkey.stories.d.ts.map +1 -1
  31. package/dist/types/src/invitations/useInvitationStatus.d.ts +1 -1
  32. package/dist/types/src/invitations/useInvitationStatus.d.ts.map +1 -1
  33. package/dist/types/src/testing/ClientRepeater.d.ts +2 -2
  34. package/dist/types/src/testing/ClientRepeater.d.ts.map +1 -1
  35. package/dist/types/src/testing/ClientRepeater.stories.d.ts +0 -1
  36. package/dist/types/src/testing/ClientRepeater.stories.d.ts.map +1 -1
  37. package/dist/types/src/testing/withClientProvider.d.ts +5 -6
  38. package/dist/types/src/testing/withClientProvider.d.ts.map +1 -1
  39. package/dist/types/src/testing/withClientProvider.stories.d.ts +1 -1
  40. package/dist/types/src/testing/withClientProvider.stories.d.ts.map +1 -1
  41. package/dist/types/tsconfig.tsbuildinfo +1 -1
  42. package/moon.yml +4 -0
  43. package/package.json +30 -31
  44. package/src/echo/useQuery.test.tsx +7 -7
  45. package/src/echo/useQuery.ts +12 -12
  46. package/src/echo/useSchema.ts +2 -2
  47. package/src/echo/useSubscription.ts +1 -1
  48. package/src/halo/Passkey.stories.tsx +11 -14
  49. package/src/invitations/useInvitationStatus.ts +9 -8
  50. package/src/testing/ClientRepeater.stories.tsx +1 -3
  51. package/src/testing/ClientRepeater.tsx +4 -4
  52. package/src/testing/withClientProvider.stories.tsx +12 -9
  53. package/src/testing/withClientProvider.tsx +17 -15
  54. package/tsconfig.json +0 -3
  55. package/vitest.config.ts +12 -16
  56. package/dist/lib/browser/chunk-BXOLJYAY.mjs.map +0 -7
  57. package/dist/lib/node-esm/chunk-256IHY6F.mjs.map +0 -7
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-client",
3
- "version": "0.8.4-main.e098934",
3
+ "version": "0.8.4-main.e8ec1fe",
4
4
  "description": "React client API",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -86,43 +86,42 @@
86
86
  },
87
87
  "dependencies": {
88
88
  "react-error-boundary": "^4.0.13",
89
- "@dxos/client": "0.8.4-main.e098934",
90
- "@dxos/async": "0.8.4-main.e098934",
91
- "@dxos/codec-protobuf": "0.8.4-main.e098934",
92
- "@dxos/debug": "0.8.4-main.e098934",
93
- "@dxos/echo": "0.8.4-main.e098934",
94
- "@dxos/context": "0.8.4-main.e098934",
95
- "@dxos/echo-schema": "0.8.4-main.e098934",
96
- "@dxos/echo-signals": "0.8.4-main.e098934",
97
- "@dxos/log": "0.8.4-main.e098934",
98
- "@dxos/node-std": "0.8.4-main.e098934",
99
- "@dxos/config": "0.8.4-main.e098934",
100
- "@dxos/protocols": "0.8.4-main.e098934",
101
- "@dxos/react-hooks": "0.8.4-main.e098934",
102
- "@dxos/util": "0.8.4-main.e098934",
103
- "@dxos/invariant": "0.8.4-main.e098934"
89
+ "@dxos/async": "0.8.4-main.e8ec1fe",
90
+ "@dxos/client": "0.8.4-main.e8ec1fe",
91
+ "@dxos/config": "0.8.4-main.e8ec1fe",
92
+ "@dxos/context": "0.8.4-main.e8ec1fe",
93
+ "@dxos/codec-protobuf": "0.8.4-main.e8ec1fe",
94
+ "@dxos/debug": "0.8.4-main.e8ec1fe",
95
+ "@dxos/echo": "0.8.4-main.e8ec1fe",
96
+ "@dxos/echo-signals": "0.8.4-main.e8ec1fe",
97
+ "@dxos/log": "0.8.4-main.e8ec1fe",
98
+ "@dxos/invariant": "0.8.4-main.e8ec1fe",
99
+ "@dxos/node-std": "0.8.4-main.e8ec1fe",
100
+ "@dxos/protocols": "0.8.4-main.e8ec1fe",
101
+ "@dxos/react-hooks": "0.8.4-main.e8ec1fe",
102
+ "@dxos/util": "0.8.4-main.e8ec1fe"
104
103
  },
105
104
  "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",
105
+ "@types/react": "~19.2.2",
106
+ "@types/react-dom": "~19.2.2",
107
+ "react": "~19.2.0",
108
+ "react-dom": "~19.2.0",
110
109
  "typedoc": "0.28.1",
111
- "@dxos/random": "0.8.4-main.e098934",
112
- "@dxos/react-ui": "0.8.4-main.e098934",
113
- "@dxos/react-ui-theme": "0.8.4-main.e098934",
114
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.e098934",
115
- "@dxos/test-utils": "0.8.4-main.e098934",
116
- "@dxos/storybook-utils": "0.8.4-main.e098934"
110
+ "@dxos/react-ui": "0.8.4-main.e8ec1fe",
111
+ "@dxos/random": "0.8.4-main.e8ec1fe",
112
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.e8ec1fe",
113
+ "@dxos/react-ui-theme": "0.8.4-main.e8ec1fe",
114
+ "@dxos/test-utils": "0.8.4-main.e8ec1fe",
115
+ "@dxos/storybook-utils": "0.8.4-main.e8ec1fe"
117
116
  },
118
117
  "peerDependencies": {
119
- "react": "~18.2.0",
120
- "react-dom": "~18.2.0"
118
+ "react": "^19.0.0",
119
+ "react-dom": "^19.0.0"
121
120
  },
122
121
  "optionalDependencies": {
123
- "@dxos/random": "0.8.4-main.e098934",
124
- "@dxos/react-ui-theme": "0.8.4-main.e098934",
125
- "@dxos/react-ui": "0.8.4-main.e098934"
122
+ "@dxos/react-ui": "0.8.4-main.e8ec1fe",
123
+ "@dxos/react-ui-theme": "0.8.4-main.e8ec1fe",
124
+ "@dxos/random": "0.8.4-main.e8ec1fe"
126
125
  },
127
126
  "publishConfig": {
128
127
  "access": "public"
@@ -5,7 +5,8 @@
5
5
  import { renderHook } from '@testing-library/react';
6
6
  import { describe, expect, test } from 'vitest';
7
7
 
8
- import { Expando, Filter, live } from '@dxos/client/echo';
8
+ import { Expando, Filter } from '@dxos/client/echo';
9
+ import { Obj, Type } from '@dxos/echo';
9
10
 
10
11
  import { createClient, createClientContextProvider } from '../testing/util';
11
12
 
@@ -19,9 +20,9 @@ describe('useQuery', () => {
19
20
  const wrapper = await createClientContextProvider(client);
20
21
 
21
22
  // Create 3 test objects: Alice, Bob, Charlie.
22
- const alice = live(Expando, { name: 'Alice' });
23
- const bob = live(Expando, { name: 'Bob' });
24
- const charlie = live(Expando, { name: 'Charlie' });
23
+ const alice = Obj.make(Type.Expando, { name: 'Alice' });
24
+ const bob = Obj.make(Type.Expando, { name: 'Bob' });
25
+ const charlie = Obj.make(Type.Expando, { name: 'Charlie' });
25
26
 
26
27
  space!.db.add(alice);
27
28
  space!.db.add(bob);
@@ -65,7 +66,7 @@ describe('useQuery', () => {
65
66
  * Actual: 4 renders
66
67
  * 1. [] (empty)
67
68
  * 2. ['Alice', 'Bob', 'Charlie'] (all loaded)
68
- * 3. ['Alice', 'Charlie', 'Bob'] (FLASH BUG - Bob moves to end!)
69
+ * 3. ['Alice', 'Charlie', 'Bob'] (FLASH BUG - Bob moves to end!)x
69
70
  * 4. ['Alice', 'Charlie'] (Bob finally removed)
70
71
  */
71
72
 
@@ -82,7 +83,7 @@ describe('useQuery', () => {
82
83
  const wrapper = await createClientContextProvider(client);
83
84
 
84
85
  // Create 10 test objects: 1, 2, 3, ..., 10.
85
- const objects = Array.from({ length: 10 }, (_, i) => live(Expando, { value: i + 1 }));
86
+ const objects = Array.from({ length: 10 }, (_, i) => Obj.make(Type.Expando, { value: i + 1 }));
86
87
 
87
88
  objects.forEach((obj) => space!.db.add(obj));
88
89
  await space!.db.flush();
@@ -122,7 +123,6 @@ describe('useQuery', () => {
122
123
  new Set([]), // Initial loading state.
123
124
  new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // All objects loaded.
124
125
  // TODO(dmaretskyi): Fix this with query ordering.
125
- new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // Getting another render for some reason.
126
126
  new Set([]), // All items deleted.
127
127
  ]);
128
128
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { useMemo, useSyncExternalStore } from 'react';
6
6
 
7
- import { type Echo, Filter, type Live, Query, type Queue, type Space, isSpace } from '@dxos/client/echo';
7
+ import { type Echo, Filter, type Live, Query, type Queryable, type Space, isSpace } from '@dxos/client/echo';
8
8
 
9
9
  const EMPTY_ARRAY: never[] = [];
10
10
 
@@ -13,13 +13,13 @@ const noop = () => {};
13
13
  // TODO(dmaretskyi): Queries are fully serializable, so we can remove `deps` argument.
14
14
  interface UseQueryFn {
15
15
  <Q extends Query.Any>(
16
- spaceOrEcho: Space | Echo | Queue<any> | undefined,
16
+ spaceOrEcho: Space | Echo | Queryable | undefined,
17
17
  query: Q,
18
18
  deps?: any[],
19
19
  ): Live<Query.Type<Q>>[];
20
20
 
21
21
  <F extends Filter.Any>(
22
- spaceOrEcho: Space | Echo | Queue<any> | undefined,
22
+ spaceOrEcho: Space | Echo | Queryable | undefined,
23
23
  filter: F,
24
24
  deps?: any[],
25
25
  ): Live<Filter.Type<F>>[];
@@ -28,22 +28,22 @@ interface UseQueryFn {
28
28
  /**
29
29
  * Create subscription.
30
30
  */
31
+ // TODO(burdon): Sort?
31
32
  export const useQuery: UseQueryFn = (
32
- spaceOrEcho: Space | Echo | Queue<any> | undefined,
33
+ // TODO(burdon): CRITICAL: Remove Space and just requre Queryable.
34
+ resource: Space | Echo | Queryable | undefined,
33
35
  queryOrFilter: Query.Any | Filter.Any,
34
36
  deps?: any[],
35
37
  ): Live<unknown>[] => {
36
38
  const query = Filter.is(queryOrFilter) ? Query.select(queryOrFilter) : queryOrFilter;
37
39
 
38
40
  const { getObjects, subscribe } = useMemo(() => {
39
- let subscribed = false;
40
- const queryResult =
41
- spaceOrEcho === undefined
42
- ? undefined
43
- : isSpace(spaceOrEcho)
44
- ? spaceOrEcho.db.query(query)
45
- : spaceOrEcho.query(query);
41
+ let queryResult = undefined;
42
+ if (resource) {
43
+ queryResult = isSpace(resource) ? resource.db.query(query) : resource.query(query);
44
+ }
46
45
 
46
+ let subscribed = false;
47
47
  return {
48
48
  getObjects: () => (subscribed && queryResult ? queryResult.objects : EMPTY_ARRAY),
49
49
  subscribe: (cb: () => void) => {
@@ -55,7 +55,7 @@ export const useQuery: UseQueryFn = (
55
55
  };
56
56
  },
57
57
  };
58
- }, [spaceOrEcho, JSON.stringify(query.ast), ...(deps ?? [])]);
58
+ }, [resource, JSON.stringify(query.ast), ...(deps ?? [])]);
59
59
 
60
60
  // https://beta.reactjs.org/reference/react/useSyncExternalStore
61
61
  // NOTE: This hook will resubscribe whenever the callback passed to the first argument changes; make sure it is stable.
@@ -2,12 +2,12 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect';
5
+ import type * as Schema from 'effect/Schema';
6
6
  import { useMemo, useSyncExternalStore } from 'react';
7
7
 
8
8
  import { type Client } from '@dxos/client';
9
9
  import { type Space } from '@dxos/client/echo';
10
- import { type BaseSchema } from '@dxos/echo-schema';
10
+ import { type BaseSchema } from '@dxos/echo/internal';
11
11
 
12
12
  /**
13
13
  * Subscribe to and retrieve schema changes from a space's schema registry.
@@ -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(() => {
@@ -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,
@@ -2,14 +2,12 @@
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
 
10
8
  import { Input } from '@dxos/react-ui';
9
+ import { withTheme } from '@dxos/react-ui/testing';
11
10
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
12
- import { withTheme } from '@dxos/storybook-utils';
13
11
 
14
12
  import { useClient } from '../client';
15
13
  import { useSpace } from '../echo';
@@ -6,7 +6,7 @@ import React, { type FC, useEffect, useRef, useState } from 'react';
6
6
 
7
7
  import { Client, type PublicKey } from '@dxos/client';
8
8
  import { TestBuilder, performInvitation } from '@dxos/client/testing';
9
- import { type TypedObject } from '@dxos/echo-schema';
9
+ import { type TypedObject } from '@dxos/echo/internal';
10
10
  import { registerSignalsRuntime } from '@dxos/echo-signals/react';
11
11
  import { faker } from '@dxos/random';
12
12
  import { useAsyncEffect } from '@dxos/react-hooks';
@@ -28,7 +28,7 @@ export type ClientRepeaterProps<P extends ClientRepeatedComponentProps> = {
28
28
  clients?: Client[];
29
29
  types?: TypedObject[];
30
30
  args?: Omit<P, 'id' | 'count'>;
31
- } & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | 'onSpaceCreated'>;
31
+ } & Pick<WithClientProviderProps, 'createIdentity' | 'createSpace' | 'onCreateSpace'>;
32
32
 
33
33
  /**
34
34
  * Utility component for Storybook stories which sets up clients for n peers.
@@ -48,7 +48,7 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
48
48
  types,
49
49
  createIdentity,
50
50
  createSpace,
51
- onSpaceCreated,
51
+ onCreateSpace,
52
52
  } = props;
53
53
  useEffect(() => {
54
54
  registerSignalsRuntime();
@@ -72,7 +72,7 @@ export const ClientRepeater = <P extends ClientRepeatedComponentProps>(props: Cl
72
72
  const client = clients[0];
73
73
  const space = await client.spaces.create({ name: faker.commerce.productName() });
74
74
  setSpaceKey(space.key);
75
- await onSpaceCreated?.({ client, space }, {});
75
+ await onCreateSpace?.({ client, space }, {});
76
76
  await Promise.all(clients.slice(1).flatMap((client) => performInvitation({ host: space, guest: client.spaces })));
77
77
  }
78
78
 
@@ -2,14 +2,13 @@
2
2
  // Copyright 2024 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
 
10
8
  import { log } from '@dxos/log';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
10
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
12
- import { render, withLayout, withTheme } from '@dxos/storybook-utils';
11
+ import { render } from '@dxos/storybook-utils';
13
12
 
14
13
  import { useClient } from '../client';
15
14
  import { type Space, useSpaces } from '../echo';
@@ -29,7 +28,7 @@ const DefaultStory = () => {
29
28
  }
30
29
 
31
30
  return (
32
- <div className='flex flex-col min-w-[28rem] divide-y divide-separator border border-separator rounded'>
31
+ <div className='flex flex-col divide-y divide-separator border border-separator'>
33
32
  <SyntaxHighlighter language='json'>{JSON.stringify(client.toJSON(), null, 2)}</SyntaxHighlighter>
34
33
  {spaces.map((space) => (
35
34
  <SpaceInfo key={space.id} space={space} />
@@ -41,6 +40,7 @@ const DefaultStory = () => {
41
40
  const meta = {
42
41
  title: 'sdk/react-client/withClientProvider',
43
42
  render: render(DefaultStory),
43
+ decorators: [withTheme],
44
44
  } satisfies Meta<typeof DefaultStory>;
45
45
 
46
46
  export default meta;
@@ -53,19 +53,22 @@ const clientProps: WithClientProviderProps = {
53
53
  onInitialized: async (client) => {
54
54
  log.info('onInitialized', { client });
55
55
  },
56
- onSpaceCreated: async ({ client, space }) => {
57
- log.info('onSpaceCreated', { client, space });
56
+ onCreateSpace: async ({ client, space }) => {
57
+ log.info('onCreateSpace', { client, space });
58
58
  },
59
59
  };
60
60
 
61
61
  export const Default: Story = {
62
- decorators: [withClientProvider(clientProps), withTheme, withLayout({ classNames: ['flex gap-2'] })],
62
+ decorators: [withClientProvider(clientProps)],
63
+ parameters: {
64
+ layout: 'centered',
65
+ },
63
66
  };
64
67
 
65
68
  export const Multiple: Story = {
66
69
  decorators: [
67
- withMultiClientProvider({ ...clientProps, numClients: 3 }),
68
70
  withTheme,
69
- withLayout({ classNames: ['flex gap-2'] }),
71
+ withLayout({ classNames: 'grid grid-cols-3' }),
72
+ withMultiClientProvider({ ...clientProps, numClients: 3 }),
70
73
  ],
71
74
  };
@@ -21,9 +21,9 @@ type InitializeProps = {
21
21
  createIdentity?: boolean;
22
22
  createSpace?: boolean;
23
23
  onInitialized?: (client: Client) => MaybePromise<void>;
24
- onIdentityCreated?: (props: { client: Client }) => MaybePromise<void>;
24
+ onCreateIdentity?: (props: { client: Client }, context: StoryContext | any) => MaybePromise<void>;
25
25
  // NOTE: context must be untyped until ClientRepeater is removed.
26
- onSpaceCreated?: (props: { client: Client; space: Space }, context: StoryContext | any) => MaybePromise<void>;
26
+ onCreateSpace?: (props: { client: Client; space: Space }, context: StoryContext | any) => MaybePromise<void>;
27
27
  };
28
28
 
29
29
  /**
@@ -31,7 +31,7 @@ type InitializeProps = {
31
31
  */
32
32
  const initializeClient = async (
33
33
  client: Client,
34
- { createIdentity, createSpace, onSpaceCreated, onIdentityCreated, onInitialized }: InitializeProps,
34
+ { createIdentity, createSpace, onCreateIdentity, onCreateSpace, onInitialized }: InitializeProps,
35
35
  context: StoryContext,
36
36
  ): Promise<ClientStory> => {
37
37
  await onInitialized?.(client);
@@ -39,14 +39,17 @@ const initializeClient = async (
39
39
  if (createIdentity || createSpace) {
40
40
  if (!client.halo.identity.get()) {
41
41
  await client.halo.createIdentity();
42
- await onIdentityCreated?.({ client });
42
+ await client.spaces.waitUntilReady();
43
+ await client.spaces.default.waitUntilReady();
44
+ await onCreateIdentity?.({ client }, context);
43
45
  }
44
46
  }
45
47
 
46
48
  let space: Space | undefined;
47
49
  if (createSpace) {
48
50
  space = await client.spaces.create({ name: 'Test Space' });
49
- await onSpaceCreated?.({ client, space }, context);
51
+ await space.waitUntilReady(); // Is this required?
52
+ await onCreateSpace?.({ client, space }, context);
50
53
  }
51
54
 
52
55
  return { space };
@@ -60,8 +63,8 @@ export type WithClientProviderProps = InitializeProps & Omit<ClientProviderProps
60
63
  export const withClientProvider = ({
61
64
  createIdentity,
62
65
  createSpace,
63
- onSpaceCreated,
64
- onIdentityCreated,
66
+ onCreateSpace,
67
+ onCreateIdentity,
65
68
  onInitialized,
66
69
  ...props
67
70
  }: WithClientProviderProps = {}): Decorator => {
@@ -73,8 +76,8 @@ export const withClientProvider = ({
73
76
  {
74
77
  createIdentity,
75
78
  createSpace,
76
- onSpaceCreated,
77
- onIdentityCreated,
79
+ onCreateSpace,
80
+ onCreateIdentity,
78
81
  onInitialized,
79
82
  },
80
83
  context,
@@ -108,20 +111,19 @@ export const useMultiClient = () => useContext(MultiClientContext);
108
111
  /**
109
112
  * Decorator that creates a scaffold for multiple clients.
110
113
  * Orchestrates invitations between a randomly selected host and the remaining clients.
111
- * NOTE: Should come before withLayout.
112
114
  */
113
115
  export const withMultiClientProvider = ({
114
116
  numClients = 2,
115
117
  createIdentity,
116
118
  createSpace,
117
- onSpaceCreated,
118
- onIdentityCreated,
119
+ onCreateSpace,
120
+ onCreateIdentity,
119
121
  onInitialized,
120
122
  ...props
121
123
  }: WithMultiClientProviderProps): Decorator => {
122
124
  return (Story, context) => {
123
125
  const builder = useRef(new TestBuilder());
124
- const hostRef = useRef<Client>();
126
+ const hostRef = useRef<Client>(null);
125
127
  const spaceReady = useRef(new Trigger<Space | undefined>());
126
128
 
127
129
  // Handle invitations.
@@ -136,8 +138,8 @@ export const withMultiClientProvider = ({
136
138
  {
137
139
  createIdentity,
138
140
  createSpace,
139
- onSpaceCreated,
140
- onIdentityCreated,
141
+ onCreateSpace,
142
+ onCreateIdentity,
141
143
  onInitialized,
142
144
  },
143
145
  context,
package/tsconfig.json CHANGED
@@ -44,9 +44,6 @@
44
44
  {
45
45
  "path": "../../core/echo/echo"
46
46
  },
47
- {
48
- "path": "../../core/echo/echo-schema"
49
- },
50
47
  {
51
48
  "path": "../../core/echo/echo-signals"
52
49
  },
package/vitest.config.ts CHANGED
@@ -2,21 +2,17 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { defineConfig, mergeConfig } from 'vitest/config';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
6
7
 
7
- import { baseConfig } from '../../../vitest.base.config';
8
+ import { createConfig } from '../../../vitest.base.config';
8
9
 
9
- // TODO(wittjosiah): Factor out to shared config as an option.
10
- const specifiedEnv = (process.env.VITEST_ENV ?? 'node').toLowerCase();
11
- const environment = specifiedEnv === 'node' ? 'jsdom' : undefined;
12
-
13
- export default mergeConfig(
14
- baseConfig({ cwd: __dirname }),
15
- defineConfig({
16
- test: {
17
- environment,
18
- // TODO(dmaretskyi): Enabled because client tests were flaky. Remove when that's not the case.
19
- retry: 2,
20
- },
21
- }),
22
- );
10
+ export default createConfig({
11
+ dirname: typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)),
12
+ node: {
13
+ environment: 'jsdom',
14
+ // TODO(dmaretskyi): Enabled because client tests were flaky. Remove when that's not the case.
15
+ retry: 2,
16
+ },
17
+ storybook: true,
18
+ });
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/invitations/index.ts", "../../../src/invitations/useInvitationStatus.ts"],
4
- "sourcesContent": ["//\n// Copyright 2020 DXOS.org\n//\n\n// NOTE: Export * fails here.\nexport {\n AuthenticatingInvitationObservable,\n CancellableInvitationObservable,\n InvitationEncoder,\n Invitation,\n type Invitations,\n InvitationsProxy,\n} from '@dxos/client/invitations';\n\nexport * from './useInvitationStatus';\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { type Reducer, useCallback, useEffect, useMemo, useReducer } from 'react';\n\nimport { type PublicKey } from '@dxos/client';\nimport {\n type AuthenticatingInvitationObservable,\n type CancellableInvitationObservable,\n Invitation,\n InvitationEncoder,\n} from '@dxos/client/invitations';\nimport { log } from '@dxos/log';\n\nexport type InvitationResult = {\n spaceKey: PublicKey | null;\n identityKey: PublicKey | null;\n swarmKey: PublicKey | null;\n target: string | null;\n};\n\ninterface InvitationReducerState {\n status: Invitation.State; // TODO(burdon): Rename state.\n haltedAt?: Invitation.State;\n result: InvitationResult;\n error?: number;\n id?: string;\n multiUse?: boolean;\n invitationCode?: string;\n authCode?: string;\n authMethod?: Invitation.AuthMethod;\n type?: Invitation.Type;\n}\n\ntype InvitationPayload = { invitation: Invitation };\n\nexport type InvitationAction =\n | ({\n status:\n | Invitation.State.INIT\n | Invitation.State.CONNECTED\n | Invitation.State.READY_FOR_AUTHENTICATION\n | Invitation.State.AUTHENTICATING;\n } & InvitationPayload)\n | ({\n status: Invitation.State.CONNECTING;\n observable: CancellableInvitationObservable;\n } & InvitationPayload)\n | ({\n status: Invitation.State.SUCCESS;\n result: InvitationResult;\n } & InvitationPayload)\n | ({\n status: Invitation.State.CANCELLED | Invitation.State.TIMEOUT;\n haltedAt: Invitation.State;\n } & InvitationPayload)\n | {\n status: Invitation.State.ERROR;\n error?: Error;\n haltedAt: Invitation.State;\n };\n\nexport type InvitationStatus = {\n id?: string;\n invitationCode?: string;\n authCode?: string;\n authMethod?: Invitation.AuthMethod;\n type?: Invitation.Type;\n status: Invitation.State;\n haltedAt?: Invitation.State;\n multiUse?: boolean;\n shareable?: boolean;\n result: InvitationResult;\n error?: number;\n cancel(): void;\n // TODO(wittjosiah): Remove?\n connect(observable: CancellableInvitationObservable): void;\n authenticate(authCode: string): Promise<void>;\n};\n\n// Without private key, the invitation code cannot be created.\n// These invitations are only available to be accepted but not shared.\nconst isShareableInvitation = (invitation: Invitation) =>\n invitation.authMethod !== Invitation.AuthMethod.KNOWN_PUBLIC_KEY || invitation.guestKeypair?.privateKey;\n\nexport const useInvitationStatus = (observable?: CancellableInvitationObservable): InvitationStatus => {\n const [state, dispatch] = useReducer<Reducer<InvitationReducerState, InvitationAction>, null>(\n (prev, action) => {\n log('useInvitationStatus', { action });\n const invitationProps =\n 'invitation' in action\n ? {\n id: action.invitation.invitationId,\n multiUse: action.invitation.multiUse,\n invitationCode: InvitationEncoder.encode(action.invitation),\n authCode: action.invitation.authCode,\n authMethod: action.invitation.authMethod,\n shareable: isShareableInvitation(action.invitation),\n type: action.invitation.type,\n }\n : {};\n return {\n ...prev,\n ...invitationProps,\n // TODO(burdon): State.\n status: action.status,\n // `invitationObservable`, `secret`, and `result` is persisted between the status-actions that set them.\n result: action.status === Invitation.State.SUCCESS ? action.result : prev.result,\n // `error` gets set each time we enter the error state\n ...(action.status === Invitation.State.ERROR && { error: action.error }),\n // `haltedAt` gets set on only the first error/cancelled/timeout action and reset on any others.\n ...((action.status === Invitation.State.ERROR ||\n action.status === Invitation.State.CANCELLED ||\n action.status === Invitation.State.TIMEOUT) && {\n haltedAt: typeof prev.haltedAt === 'undefined' ? action.haltedAt : prev.haltedAt,\n }),\n } as InvitationReducerState;\n },\n null,\n (_arg: null) => {\n const invitation = observable?.get();\n return {\n status: Invitation.State.INIT,\n result: { spaceKey: null, identityKey: null, swarmKey: null, target: null },\n id: invitation?.invitationId,\n multiUse: invitation?.multiUse,\n invitationCode: invitation ? InvitationEncoder.encode(invitation) : undefined,\n authCode: invitation?.authCode,\n authMethod: invitation?.authMethod,\n shareable: invitation ? isShareableInvitation(invitation) : false,\n type: invitation?.type,\n };\n },\n );\n\n // Handle unmount.\n\n useEffect(() => {\n const update = (invitation: Invitation) => {\n switch (invitation.state) {\n case Invitation.State.INIT:\n case Invitation.State.CONNECTED:\n case Invitation.State.READY_FOR_AUTHENTICATION:\n case Invitation.State.AUTHENTICATING: {\n dispatch({\n invitation,\n status: invitation.state,\n });\n break;\n }\n\n case Invitation.State.SUCCESS: {\n dispatch({\n invitation,\n status: invitation.state,\n result: {\n spaceKey: invitation.spaceKey || null,\n identityKey: invitation.identityKey || null,\n swarmKey: invitation.swarmKey || null,\n target: invitation.target || null,\n },\n });\n break;\n }\n\n case Invitation.State.CANCELLED:\n case Invitation.State.TIMEOUT: {\n dispatch({ invitation, status: invitation.state, haltedAt: state.status });\n break;\n }\n }\n };\n\n const subscription = observable?.subscribe(update, (err: Error) => {\n dispatch({ status: Invitation.State.ERROR, error: err, haltedAt: state.status });\n });\n\n const currentState = observable?.get();\n if (currentState) {\n update(currentState);\n }\n\n return () => subscription?.unsubscribe();\n }, [observable, state.status]);\n\n // Return memoized callbacks & values.\n\n const connect = useCallback((observable: CancellableInvitationObservable) => {\n dispatch({ invitation: observable.get(), status: Invitation.State.CONNECTING, observable });\n }, []);\n\n const authenticate = useCallback(\n (authCode: string) => {\n log('authenticating...', { authCode });\n return (observable as AuthenticatingInvitationObservable).authenticate(authCode);\n },\n [observable],\n );\n\n const cancel = useCallback(async () => observable?.cancel(), [observable]);\n\n return useMemo(() => {\n return {\n ...state,\n cancel,\n connect,\n authenticate,\n };\n }, [state, cancel, connect, authenticate]);\n};\n"],
5
- "mappings": "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",
6
- "names": ["AuthenticatingInvitationObservable", "CancellableInvitationObservable", "InvitationEncoder", "Invitation", "InvitationsProxy", "useCallback", "useEffect", "useMemo", "useReducer", "Invitation", "InvitationEncoder", "log", "isShareableInvitation", "invitation", "authMethod", "AuthMethod", "KNOWN_PUBLIC_KEY", "guestKeypair", "privateKey", "useInvitationStatus", "observable", "state", "dispatch", "prev", "action", "invitationProps", "id", "invitationId", "multiUse", "invitationCode", "encode", "authCode", "shareable", "type", "status", "result", "State", "SUCCESS", "ERROR", "error", "CANCELLED", "TIMEOUT", "haltedAt", "_arg", "get", "INIT", "spaceKey", "identityKey", "swarmKey", "target", "undefined", "update", "CONNECTED", "READY_FOR_AUTHENTICATION", "AUTHENTICATING", "subscription", "subscribe", "err", "currentState", "unsubscribe", "connect", "CONNECTING", "authenticate", "cancel"]
7
- }