@dxos/react-client 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d
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/.storybook/main.mts +11 -0
- package/.storybook/preview.mts +8 -0
- package/dist/lib/browser/{chunk-BXOLJYAY.mjs → chunk-LO3TU5CK.mjs} +4 -4
- package/dist/lib/browser/chunk-LO3TU5CK.mjs.map +7 -0
- package/dist/lib/browser/echo/index.mjs +8 -5
- package/dist/lib/browser/echo/index.mjs.map +3 -3
- package/dist/lib/browser/halo/index.mjs +6 -20
- package/dist/lib/browser/halo/index.mjs.map +2 -2
- package/dist/lib/browser/invitations/index.mjs +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +21 -18
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/{chunk-256IHY6F.mjs → chunk-AQJH2UNZ.mjs} +4 -4
- package/dist/lib/node-esm/chunk-AQJH2UNZ.mjs.map +7 -0
- package/dist/lib/node-esm/echo/index.mjs +8 -5
- package/dist/lib/node-esm/echo/index.mjs.map +3 -3
- package/dist/lib/node-esm/halo/index.mjs +6 -20
- package/dist/lib/node-esm/halo/index.mjs.map +2 -2
- package/dist/lib/node-esm/invitations/index.mjs +1 -1
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +21 -18
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/echo/useQuery.d.ts +3 -3
- package/dist/types/src/echo/useQuery.d.ts.map +1 -1
- package/dist/types/src/echo/useQueue.d.ts +2 -2
- package/dist/types/src/echo/useQueue.d.ts.map +1 -1
- package/dist/types/src/echo/useSchema.d.ts +1 -1
- package/dist/types/src/echo/useSchema.d.ts.map +1 -1
- package/dist/types/src/echo/useSubscription.d.ts +1 -1
- package/dist/types/src/echo/useSubscription.d.ts.map +1 -1
- package/dist/types/src/halo/Passkey.stories.d.ts +1 -1
- package/dist/types/src/halo/Passkey.stories.d.ts.map +1 -1
- package/dist/types/src/invitations/useInvitationStatus.d.ts +1 -1
- package/dist/types/src/invitations/useInvitationStatus.d.ts.map +1 -1
- package/dist/types/src/testing/ClientRepeater.d.ts +2 -2
- package/dist/types/src/testing/ClientRepeater.d.ts.map +1 -1
- package/dist/types/src/testing/ClientRepeater.stories.d.ts +0 -1
- package/dist/types/src/testing/ClientRepeater.stories.d.ts.map +1 -1
- package/dist/types/src/testing/withClientProvider.d.ts +5 -6
- package/dist/types/src/testing/withClientProvider.d.ts.map +1 -1
- package/dist/types/src/testing/withClientProvider.stories.d.ts +1 -1
- package/dist/types/src/testing/withClientProvider.stories.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/moon.yml +4 -0
- package/package.json +30 -31
- package/src/echo/useQuery.test.tsx +9 -9
- package/src/echo/useQuery.ts +23 -14
- package/src/echo/useQueue.ts +2 -2
- package/src/echo/useSchema.ts +2 -2
- package/src/echo/useSubscription.ts +1 -1
- package/src/halo/Passkey.stories.tsx +11 -14
- package/src/invitations/useInvitationStatus.ts +9 -8
- package/src/testing/ClientRepeater.stories.tsx +1 -3
- package/src/testing/ClientRepeater.tsx +4 -4
- package/src/testing/withClientProvider.stories.tsx +12 -9
- package/src/testing/withClientProvider.tsx +19 -17
- package/tsconfig.json +0 -3
- package/vitest.config.ts +12 -16
- package/dist/lib/browser/chunk-BXOLJYAY.mjs.map +0 -7
- 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.
|
|
3
|
+
"version": "0.8.4-main.66e292d",
|
|
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/async": "0.8.4-main.
|
|
90
|
-
"@dxos/
|
|
91
|
-
"@dxos/
|
|
92
|
-
"@dxos/
|
|
93
|
-
"@dxos/
|
|
94
|
-
"@dxos/echo
|
|
95
|
-
"@dxos/
|
|
96
|
-
"@dxos/
|
|
97
|
-
"@dxos/
|
|
98
|
-
"@dxos/
|
|
99
|
-
"@dxos/
|
|
100
|
-
"@dxos/
|
|
101
|
-
"@dxos/
|
|
102
|
-
"@dxos/
|
|
103
|
-
"@dxos/util": "0.8.4-main.5ea62a8"
|
|
89
|
+
"@dxos/async": "0.8.4-main.66e292d",
|
|
90
|
+
"@dxos/client": "0.8.4-main.66e292d",
|
|
91
|
+
"@dxos/codec-protobuf": "0.8.4-main.66e292d",
|
|
92
|
+
"@dxos/config": "0.8.4-main.66e292d",
|
|
93
|
+
"@dxos/debug": "0.8.4-main.66e292d",
|
|
94
|
+
"@dxos/echo": "0.8.4-main.66e292d",
|
|
95
|
+
"@dxos/context": "0.8.4-main.66e292d",
|
|
96
|
+
"@dxos/echo-signals": "0.8.4-main.66e292d",
|
|
97
|
+
"@dxos/invariant": "0.8.4-main.66e292d",
|
|
98
|
+
"@dxos/log": "0.8.4-main.66e292d",
|
|
99
|
+
"@dxos/node-std": "0.8.4-main.66e292d",
|
|
100
|
+
"@dxos/react-hooks": "0.8.4-main.66e292d",
|
|
101
|
+
"@dxos/util": "0.8.4-main.66e292d",
|
|
102
|
+
"@dxos/protocols": "0.8.4-main.66e292d"
|
|
104
103
|
},
|
|
105
104
|
"devDependencies": {
|
|
106
|
-
"@types/react": "~
|
|
107
|
-
"@types/react-dom": "~
|
|
108
|
-
"react": "~
|
|
109
|
-
"react-dom": "~
|
|
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.
|
|
112
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
113
|
-
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.
|
|
114
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
115
|
-
"@dxos/
|
|
116
|
-
"@dxos/
|
|
110
|
+
"@dxos/random": "0.8.4-main.66e292d",
|
|
111
|
+
"@dxos/react-ui": "0.8.4-main.66e292d",
|
|
112
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.66e292d",
|
|
113
|
+
"@dxos/react-ui-theme": "0.8.4-main.66e292d",
|
|
114
|
+
"@dxos/test-utils": "0.8.4-main.66e292d",
|
|
115
|
+
"@dxos/storybook-utils": "0.8.4-main.66e292d"
|
|
117
116
|
},
|
|
118
117
|
"peerDependencies": {
|
|
119
|
-
"react": "
|
|
120
|
-
"react-dom": "
|
|
118
|
+
"react": "^19.0.0",
|
|
119
|
+
"react-dom": "^19.0.0"
|
|
121
120
|
},
|
|
122
121
|
"optionalDependencies": {
|
|
123
|
-
"@dxos/random": "0.8.4-main.
|
|
124
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
125
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
122
|
+
"@dxos/random": "0.8.4-main.66e292d",
|
|
123
|
+
"@dxos/react-ui": "0.8.4-main.66e292d",
|
|
124
|
+
"@dxos/react-ui-theme": "0.8.4-main.66e292d"
|
|
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 {
|
|
8
|
+
import { 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 =
|
|
23
|
-
const bob =
|
|
24
|
-
const 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);
|
|
@@ -34,7 +35,7 @@ describe('useQuery', () => {
|
|
|
34
35
|
// Setup useQuery hook that captures every render.
|
|
35
36
|
renderHook(
|
|
36
37
|
() => {
|
|
37
|
-
const objects = useQuery(space, Filter.type(Expando));
|
|
38
|
+
const objects = useQuery(space, Filter.type(Type.Expando));
|
|
38
39
|
|
|
39
40
|
// Capture the names in this render.
|
|
40
41
|
const namesInThisRender = objects.map((obj) => obj.name);
|
|
@@ -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) =>
|
|
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();
|
|
@@ -93,7 +94,7 @@ describe('useQuery', () => {
|
|
|
93
94
|
// Setup useQuery hook that captures every render.
|
|
94
95
|
renderHook(
|
|
95
96
|
() => {
|
|
96
|
-
const queryObjects = useQuery(space, Filter.type(Expando));
|
|
97
|
+
const queryObjects = useQuery(space, Filter.type(Type.Expando));
|
|
97
98
|
|
|
98
99
|
// Capture the values in this render.
|
|
99
100
|
const valuesInThisRender = queryObjects.map((obj) => obj.value);
|
|
@@ -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
|
});
|
package/src/echo/useQuery.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { useMemo, useSyncExternalStore } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type
|
|
7
|
+
import { type Database, type Echo, type Entity, Filter, Query, type Space, isSpace } from '@dxos/client/echo';
|
|
8
|
+
import { type AnyProperties } from '@dxos/echo/internal';
|
|
8
9
|
|
|
9
10
|
const EMPTY_ARRAY: never[] = [];
|
|
10
11
|
|
|
@@ -12,30 +13,38 @@ const noop = () => {};
|
|
|
12
13
|
|
|
13
14
|
// TODO(dmaretskyi): Queries are fully serializable, so we can remove `deps` argument.
|
|
14
15
|
interface UseQueryFn {
|
|
15
|
-
<Q extends Query.Any
|
|
16
|
+
<Q extends Query.Any, O extends Entity.Entity<Query.Type<Q>> = Entity.Entity<Query.Type<Q>>>(
|
|
17
|
+
spaceOrEcho: Space | Echo | Database.Queryable | undefined,
|
|
18
|
+
query: Q,
|
|
19
|
+
deps?: any[],
|
|
20
|
+
): O[];
|
|
16
21
|
|
|
17
|
-
<F extends Filter.Any
|
|
22
|
+
<F extends Filter.Any, O extends Entity.Entity<Filter.Type<F>> = Entity.Entity<Filter.Type<F>>>(
|
|
23
|
+
spaceOrEcho: Space | Echo | Database.Queryable | undefined,
|
|
24
|
+
filter: F,
|
|
25
|
+
deps?: any[],
|
|
26
|
+
): O[];
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
/**
|
|
21
30
|
* Create subscription.
|
|
22
31
|
*/
|
|
32
|
+
// TODO(burdon): Sort?
|
|
23
33
|
export const useQuery: UseQueryFn = (
|
|
24
|
-
|
|
34
|
+
// TODO(burdon): CRITICAL: Remove Space and just requre Queryable.
|
|
35
|
+
resource: Space | Echo | Database.Queryable | undefined,
|
|
25
36
|
queryOrFilter: Query.Any | Filter.Any,
|
|
26
37
|
deps?: any[],
|
|
27
|
-
):
|
|
38
|
+
): Entity.Entity<AnyProperties>[] => {
|
|
28
39
|
const query = Filter.is(queryOrFilter) ? Query.select(queryOrFilter) : queryOrFilter;
|
|
29
40
|
|
|
30
41
|
const { getObjects, subscribe } = useMemo(() => {
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
: isSpace(spaceOrEcho)
|
|
36
|
-
? spaceOrEcho.db.query(query)
|
|
37
|
-
: spaceOrEcho.query(query);
|
|
42
|
+
let queryResult = undefined;
|
|
43
|
+
if (resource) {
|
|
44
|
+
queryResult = isSpace(resource) ? resource.db.query(query) : resource.query(query);
|
|
45
|
+
}
|
|
38
46
|
|
|
47
|
+
let subscribed = false;
|
|
39
48
|
return {
|
|
40
49
|
getObjects: () => (subscribed && queryResult ? queryResult.objects : EMPTY_ARRAY),
|
|
41
50
|
subscribe: (cb: () => void) => {
|
|
@@ -47,10 +56,10 @@ export const useQuery: UseQueryFn = (
|
|
|
47
56
|
};
|
|
48
57
|
},
|
|
49
58
|
};
|
|
50
|
-
}, [
|
|
59
|
+
}, [resource, JSON.stringify(query.ast), ...(deps ?? [])]);
|
|
51
60
|
|
|
52
61
|
// https://beta.reactjs.org/reference/react/useSyncExternalStore
|
|
53
62
|
// NOTE: This hook will resubscribe whenever the callback passed to the first argument changes; make sure it is stable.
|
|
54
|
-
const objects = useSyncExternalStore<
|
|
63
|
+
const objects = useSyncExternalStore<Entity.Entity<AnyProperties>[] | undefined>(subscribe, getObjects);
|
|
55
64
|
return objects ?? [];
|
|
56
65
|
};
|
package/src/echo/useQueue.ts
CHANGED
|
@@ -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
|
|
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
|
|
27
|
+
export const useQueue = <T extends Entity.Unknown>(
|
|
28
28
|
queueDxn?: DXN,
|
|
29
29
|
options: UseQueueOptions = {},
|
|
30
30
|
): Queue<T> | undefined => {
|
package/src/echo/useSchema.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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?:
|
|
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?:
|
|
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
|
|
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
|
-
}
|
|
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
|
|
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' | '
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
57
|
-
log.info('
|
|
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)
|
|
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:
|
|
71
|
+
withLayout({ classNames: 'grid grid-cols-3' }),
|
|
72
|
+
withMultiClientProvider({ ...clientProps, numClients: 3 }),
|
|
70
73
|
],
|
|
71
74
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Decorator, type StoryContext } from '@storybook/react';
|
|
6
6
|
import React, { type PropsWithChildren, createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
7
|
-
import { type FallbackProps, ErrorBoundary as
|
|
7
|
+
import { type FallbackProps, ErrorBoundary as NaturalErrorBoundary } from 'react-error-boundary';
|
|
8
8
|
|
|
9
9
|
import { Trigger } from '@dxos/async';
|
|
10
10
|
import { type Client } from '@dxos/client';
|
|
@@ -21,9 +21,9 @@ type InitializeProps = {
|
|
|
21
21
|
createIdentity?: boolean;
|
|
22
22
|
createSpace?: boolean;
|
|
23
23
|
onInitialized?: (client: Client) => MaybePromise<void>;
|
|
24
|
-
|
|
24
|
+
onCreateIdentity?: (props: { client: Client }, context: StoryContext | any) => MaybePromise<void>;
|
|
25
25
|
// NOTE: context must be untyped until ClientRepeater is removed.
|
|
26
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
+
onCreateSpace,
|
|
142
|
+
onCreateIdentity,
|
|
141
143
|
onInitialized,
|
|
142
144
|
},
|
|
143
145
|
context,
|
|
@@ -189,7 +191,7 @@ const ErrorBoundary = ({ children }: PropsWithChildren) => {
|
|
|
189
191
|
return <ErrorFallback error={error} resetErrorBoundary={() => setError(undefined)} />;
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
return <
|
|
194
|
+
return <NaturalErrorBoundary FallbackComponent={ErrorFallback}>{children}</NaturalErrorBoundary>;
|
|
193
195
|
};
|
|
194
196
|
|
|
195
197
|
const ErrorFallback = ({ error }: FallbackProps) => {
|