@noya-app/noya-api-client-react 0.1.35 → 0.1.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-api-client-react",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -10,10 +10,11 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@legendapp/state": "^2.1.1",
13
- "@noya-app/noya-api": "0.1.35",
14
- "@noya-app/noya-utils": "0.1.5",
15
- "@noya-app/observable": "0.1.10",
16
- "@noya-app/observable-react": "0.1.20"
13
+ "@noya-app/noya-api": "0.1.37",
14
+ "@noya-app/noya-utils": "0.1.7",
15
+ "@noya-app/observable": "0.1.12",
16
+ "@noya-app/observable-store": "0.1.1",
17
+ "@noya-app/observable-react": "0.1.22"
17
18
  },
18
19
  "peerDependencies": {
19
20
  "react": "*",
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "@noya-app/noya-api";
2
2
  export * from "./react/context";
3
3
  export * from "./react/hooks";
4
+ export * from "./react/tableHooks";
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { NoyaAPI } from "@noya-app/noya-api";
2
4
  import { createContext, useContext, useMemo } from "react";
3
5
 
@@ -31,11 +33,12 @@ export function useOptionalNoyaClient(): NoyaAPIContextValue | undefined {
31
33
  export function useNoyaClientOrFallback(): NoyaAPIContextValue {
32
34
  const value = useContext(NoyaAPIContext);
33
35
 
34
- const memoryClient = useMemo(() => {
35
- return new NoyaAPI.Client({
36
- networkClient: new NoyaAPI.MemoryClient(),
37
- });
38
- }, []);
39
-
40
- return value ?? memoryClient;
36
+ return useMemo(() => {
37
+ return (
38
+ value ??
39
+ new NoyaAPI.Client({
40
+ networkClient: new NoyaAPI.MemoryClient(),
41
+ })
42
+ );
43
+ }, [value]);
41
44
  }
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { useSelector } from "@legendapp/state/react";
2
4
  import { GENERATED_PAGE_NAME_COUNT, NoyaAPI } from "@noya-app/noya-api";
3
5
  import { range } from "@noya-app/noya-utils";
@@ -9,52 +11,6 @@ import {
9
11
  useOptionalNoyaClient,
10
12
  } from "./context";
11
13
 
12
- export function useNoyaFiles(): {
13
- files: NoyaAPI.File[];
14
- loading: boolean;
15
- reload: () => Promise<void>;
16
- } {
17
- const client = useNoyaClientOrFallback();
18
- const files = useSelector(client.files$);
19
- const session = useSelector(useNoyaClient().session$);
20
-
21
- useEffect(() => {
22
- if (!session) return;
23
-
24
- if (
25
- files.loading === "notStarted" ||
26
- files.loading === "populatedOnServer"
27
- ) {
28
- client.files.refetch();
29
- }
30
- }, [client, files.loading, session]);
31
-
32
- const reload = useCallback(() => {
33
- return client.files.refetch();
34
- }, [client]);
35
-
36
- return {
37
- files: files.files,
38
- loading: !(
39
- files.loading === "loadedInClient" ||
40
- files.loading === "populatedOnServer"
41
- ),
42
- reload,
43
- };
44
- }
45
-
46
- export function useNoyaFile(fileId?: string) {
47
- const client = useNoyaClientOrFallback();
48
- const files = useSelector(client.files$);
49
- return files.files.find((file) => file.id === fileId);
50
- }
51
-
52
- export function useNoyaIsOwnFile(fileId?: string) {
53
- const file = useNoyaFile(fileId);
54
- const session = useNoyaSession();
55
- return file?.userId === session?.user.id;
56
- }
57
-
58
14
  export function useNoyaTools(initial?: NoyaAPI.Tool[]) {
59
15
  const didInitialize = useRef(false);
60
16
  const client = useNoyaClientOrFallback();
@@ -127,6 +83,13 @@ export function useNoyaAssets(fileId?: string) {
127
83
  return assets ?? empty;
128
84
  }
129
85
 
86
+ export function useNoyaAssetsByFileVersionId(fileVersionId: string) {
87
+ const client = useNoyaClientOrFallback();
88
+ const empty = useMemo(() => ({ assets: [], loading: true }), []);
89
+ const assets = useSelector(client.assetsByFileVersionId$[fileVersionId]);
90
+ return assets ?? empty;
91
+ }
92
+
130
93
  export function useNoyaSecrets(fileId?: string) {
131
94
  const client = useNoyaClientOrFallback();
132
95
  const empty = useMemo(() => ({ secrets: [], loading: true }), []);
@@ -355,6 +318,15 @@ export function useNoyaWorkspaces() {
355
318
  return workspaces;
356
319
  }
357
320
 
321
+ export function useNoyaMultiplayerRoomToken(fileId: string) {
322
+ const client = useNoyaClientOrFallback();
323
+ const multiplayerRoomToken = useObservable(
324
+ client.multiplayerRoomTokens$,
325
+ useMemo(() => (tokens) => tokens[fileId]?.payload ?? undefined, [fileId])
326
+ );
327
+ return multiplayerRoomToken as { access: "read" | "write" } | undefined;
328
+ }
329
+
358
330
  export function useNoyaWorkspaceInvitations(workspaceId: string) {
359
331
  const client = useNoyaClientOrFallback();
360
332
  const workspaceInvitations = useSelector(
@@ -380,15 +352,15 @@ export function useNoyaWorkspaceInvitations(workspaceId: string) {
380
352
 
381
353
  const emptyObservedFileVersions: {
382
354
  fileVersions: NoyaAPI.FileVersion[];
383
- loading: NoyaAPI.DataLoadingState;
355
+ loading: NoyaAPI.FetchStatus;
384
356
  } = {
385
357
  fileVersions: [],
386
- loading: "notStarted",
358
+ loading: "idle",
387
359
  };
388
360
 
389
361
  export function useNoyaOwnedFileVersions(fileId: string): {
390
362
  fileVersions: NoyaAPI.FileVersion[];
391
- loading: NoyaAPI.DataLoadingState;
363
+ loading: NoyaAPI.FetchStatus;
392
364
  reload: () => Promise<void>;
393
365
  } {
394
366
  const client = useNoyaClientOrFallback();
@@ -396,7 +368,7 @@ export function useNoyaOwnedFileVersions(fileId: string): {
396
368
  const fileVersions = useObservable(client.ownedFileVersions$, path);
397
369
 
398
370
  useEffect(() => {
399
- if (!fileVersions || fileVersions.loading === "notStarted") {
371
+ if (!fileVersions || fileVersions.loading === "idle") {
400
372
  client.fileVersions.refetch({ fileId, type: "owned" });
401
373
  }
402
374
  }, [client, fileId, fileVersions, fileVersions?.loading]);
@@ -420,13 +392,13 @@ export function useNoyaTemplates() {
420
392
  const { templates, loading } = useSelector(client.templates$);
421
393
 
422
394
  useEffect(() => {
423
- if (loading === "notStarted" || loading === "populatedOnServer") {
395
+ if (loading === "idle") {
424
396
  client.templates.read();
425
397
  }
426
398
  }, [client, loading]);
427
399
 
428
400
  return {
429
401
  templates,
430
- loading: !(loading === "loadedInClient" || loading === "populatedOnServer"),
402
+ loading: loading === "fetching",
431
403
  };
432
404
  }
@@ -0,0 +1,217 @@
1
+ "use client";
2
+
3
+ import { NoyaAPI } from "@noya-app/noya-api";
4
+ import { Static, useObservable } from "@noya-app/noya-multiplayer-react";
5
+ import { ActivityEvent, Resource } from "@noya-app/noya-schemas";
6
+ import { SelectConfig, SelectedShape } from "@noya-app/observable";
7
+ import {
8
+ EntitySchema,
9
+ RefetchFunction,
10
+ RefetchableObservable,
11
+ Table,
12
+ TableFetchOptions,
13
+ } from "@noya-app/observable-store";
14
+ import { useEffect, useMemo } from "react";
15
+ import { useNoyaClientOrFallback } from "./context";
16
+
17
+ type GetRowType<TTable extends Table<EntitySchema>> = Static<TTable["schema"]>;
18
+ type GetSelectConfig<TTable extends Table<EntitySchema>> = SelectConfig<
19
+ GetRowType<TTable>[]
20
+ >;
21
+
22
+ type UseTableOptions = Omit<TableFetchOptions, "autoStart">;
23
+
24
+ export function useTable<
25
+ TTable extends Table<any>,
26
+ Config extends GetSelectConfig<TTable>,
27
+ >(
28
+ table: TTable | null | undefined,
29
+ {
30
+ where,
31
+ select,
32
+ }: {
33
+ where?: Partial<Record<string, any>> & { id?: string | null };
34
+ select: Config;
35
+ },
36
+ options: UseTableOptions = {}
37
+ ): {
38
+ status: NoyaAPI.FetchStatus;
39
+ data: SelectedShape<GetRowType<TTable>, Config>[];
40
+ refetch: () => void;
41
+ } {
42
+ const stableSelect = useMemo(() => {
43
+ return JSON.stringify(select);
44
+ }, [select]);
45
+
46
+ const stableWhere = useMemo(() => {
47
+ let cloneWithNoUndefined: Partial<typeof where> = {};
48
+ for (const [key, value] of Object.entries(where ?? {})) {
49
+ if (value !== undefined) {
50
+ cloneWithNoUndefined[key] = value;
51
+ }
52
+ }
53
+ return JSON.stringify(cloneWithNoUndefined);
54
+ }, [where]);
55
+
56
+ const observable = useMemo((): RefetchableObservable<{
57
+ status: NoyaAPI.FetchStatus;
58
+ data: SelectedShape<GetRowType<TTable>, Config>[];
59
+ }> => {
60
+ const parsedSelect = JSON.parse(stableSelect) as typeof select;
61
+ const parsedWhere = JSON.parse(stableWhere) as typeof where;
62
+
63
+ if (!table) {
64
+ return new RefetchableObservable<{
65
+ status: NoyaAPI.FetchStatus;
66
+ data: SelectedShape<GetRowType<TTable>, Config>[];
67
+ }>({
68
+ status: "idle",
69
+ data: [],
70
+ });
71
+ }
72
+
73
+ if (parsedWhere?.id === null) {
74
+ return new RefetchableObservable<{
75
+ status: NoyaAPI.FetchStatus;
76
+ data: SelectedShape<GetRowType<TTable>, Config>[];
77
+ }>({
78
+ status: "success",
79
+ data: [],
80
+ });
81
+ }
82
+
83
+ return table.query(
84
+ {
85
+ $where: parsedWhere,
86
+ $select: parsedSelect,
87
+ },
88
+ {
89
+ policy: options.policy,
90
+ debug: options.debug,
91
+ autoStart: false,
92
+ }
93
+ ) as RefetchableObservable<{
94
+ status: NoyaAPI.FetchStatus;
95
+ data: any[];
96
+ }>;
97
+ }, [stableSelect, stableWhere, table, options.policy, options.debug]);
98
+
99
+ useEffect(() => {
100
+ observable.start?.();
101
+ }, [observable]);
102
+
103
+ const { status, data } = useObservable(observable);
104
+
105
+ return useMemo(() => {
106
+ return {
107
+ status,
108
+ data,
109
+ refetch: observable.refetch,
110
+ };
111
+ }, [status, data, observable]);
112
+ }
113
+
114
+ export function useFileList<Config extends SelectConfig<NoyaAPI.FileListItem>>(
115
+ select: Config,
116
+ options: UseTableOptions = {}
117
+ ): {
118
+ status: NoyaAPI.FetchStatus;
119
+ data: SelectedShape<NoyaAPI.FileListItem, Config>[];
120
+ refetch: RefetchFunction;
121
+ } {
122
+ const client = useNoyaClientOrFallback();
123
+
124
+ return useTable(client.fileList$, { select }, options);
125
+ }
126
+
127
+ export function useFileListItem<
128
+ Config extends SelectConfig<NoyaAPI.FileListItem>,
129
+ >(
130
+ id: string | undefined,
131
+ select: Config,
132
+ options: UseTableOptions = {}
133
+ ): {
134
+ status: NoyaAPI.FetchStatus;
135
+ data: SelectedShape<NoyaAPI.FileListItem, Config> | undefined;
136
+ refetch: RefetchFunction;
137
+ } {
138
+ const client = useNoyaClientOrFallback();
139
+
140
+ const { status, data, refetch } = useTable(
141
+ client.fileList$,
142
+ { where: { id: id ?? null }, select },
143
+ options
144
+ );
145
+
146
+ return useMemo(() => {
147
+ return {
148
+ data: data.at(0),
149
+ status,
150
+ refetch,
151
+ };
152
+ }, [data, status, refetch]);
153
+ }
154
+
155
+ export function useResourcesList<Config extends SelectConfig<Resource>>(
156
+ fileId: string,
157
+ select: Config,
158
+ options: UseTableOptions = {}
159
+ ): {
160
+ status: NoyaAPI.FetchStatus;
161
+ data: SelectedShape<Resource, Config>[];
162
+ refetch: RefetchFunction;
163
+ } {
164
+ const client = useNoyaClientOrFallback();
165
+
166
+ return useTable(
167
+ client.resources$,
168
+ { select, where: { accessibleByFileId: fileId } },
169
+ options
170
+ );
171
+ }
172
+
173
+ export function useUser<Config extends SelectConfig<NoyaAPI.PublicUser>>(
174
+ id: string | null | undefined,
175
+ select: Config,
176
+ options: UseTableOptions = {}
177
+ ): {
178
+ status: NoyaAPI.FetchStatus;
179
+ data: SelectedShape<NoyaAPI.PublicUser, Config> | undefined;
180
+ refetch: RefetchFunction;
181
+ } {
182
+ const client = useNoyaClientOrFallback();
183
+
184
+ const { status, data, refetch } = useTable(
185
+ client.users$,
186
+ { select, where: { id: id ?? null } },
187
+ options
188
+ );
189
+
190
+ return useMemo(() => {
191
+ return {
192
+ status,
193
+ data: data.at(0),
194
+ refetch,
195
+ };
196
+ }, [status, data, refetch]);
197
+ }
198
+
199
+ export function useActivityEventsList<
200
+ Config extends SelectConfig<ActivityEvent>,
201
+ >(
202
+ fileId: string,
203
+ select: Config,
204
+ options: UseTableOptions = {}
205
+ ): {
206
+ status: NoyaAPI.FetchStatus;
207
+ data: SelectedShape<ActivityEvent, Config>[];
208
+ refetch: RefetchFunction;
209
+ } {
210
+ const client = useNoyaClientOrFallback();
211
+
212
+ return useTable(
213
+ client.activityEvents$,
214
+ { select, where: { fileId } },
215
+ options
216
+ );
217
+ }