@polyphron/viper-react 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/auth.d.ts +3 -3
- package/dist/auth.js +3 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/realtime.d.ts +12 -1
- package/dist/realtime.js +50 -1
- package/dist/records.d.ts +15 -1
- package/dist/records.js +22 -3
- package/dist/upload.d.ts +24 -0
- package/dist/upload.js +44 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -90,12 +90,11 @@ to refresh the email shown by `useUser()`.
|
|
|
90
90
|
| `useAuth(collection)` | `{ user, auth }` for an auth collection |
|
|
91
91
|
| `useSession()` / `useUser()` | The session / the logged-in user, re-rendering on change |
|
|
92
92
|
| `useSessionOf(client, read)` | The same for any client with `onSessionChange` (the admin client, say); `read` returns what to show, like `() => admin.user` |
|
|
93
|
-
| `useAuthMethods({ collection?, enabled? })` | How to sign in here (methods, sign-up,
|
|
93
|
+
| `useAuthMethods({ collection?, enabled? })` | How to sign in here (methods, sign-up, project branding), or `null` while it loads |
|
|
94
94
|
| `useSessions()` | The user's active sessions: `{ sessions, error, loading, refresh, revoke, logoutEverywhere }` |
|
|
95
95
|
| `useInvites()` | Invites the user sent: `{ invites, error, loading, refresh, invite, cancel }` |
|
|
96
96
|
| `useTenants(name)` | The user's memberships of tenant collection `name`; never the last user's list |
|
|
97
97
|
| `useRun(name, started, { every? })` | Follows a background workflow's run until it ends: `{ data, error, pending }` |
|
|
98
|
-
| `useAppIdentity()` / `useAppManagement()` | The user's app role and ceilings; opt-in role and member edits. Both give `{ identity, error, loading, refresh }`, management adds `roles`, `members` and the writes. A 403 on the management calls keeps `identity` |
|
|
99
98
|
| `useRealtime(topics, onEvent)` | Watch changes yourself; returns the connection status |
|
|
100
99
|
| `useRealtimeStatus()` | `"closed"`, `"connecting"` or `"live"` |
|
|
101
100
|
|
|
@@ -113,7 +112,7 @@ flight, so lists don't flash.
|
|
|
113
112
|
- With `live: true`, the list is also re-fetched once after the socket comes
|
|
114
113
|
back from a drop, since events may have been missed while it was down.
|
|
115
114
|
|
|
116
|
-
`useSessions
|
|
115
|
+
`useSessions` and `useInvites` both give `loading` (true
|
|
117
116
|
until the first answer for this user), `error` (an `Error` or `null`) and
|
|
118
117
|
`refresh()`, which resolves once the new answer is in. They key their state on
|
|
119
118
|
the signed-in user, so after a user switch you never see the last user's data.
|
package/dist/auth.d.ts
CHANGED
|
@@ -50,15 +50,15 @@ export declare function useAuth<T extends RecordModel = RecordModel>(collection?
|
|
|
50
50
|
auth: AuthService<T>;
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* How to sign in here: the ways on offer, sign-up, and the
|
|
54
|
-
*
|
|
53
|
+
* How to sign in here: the ways on offer, sign-up, and the project's
|
|
54
|
+
* branding. Null while
|
|
55
55
|
* it loads, and when the load fails: the sign-in calls report their own
|
|
56
56
|
* errors. Name an auth `collection` when the project has several, and pass
|
|
57
57
|
* `enabled: false` to hold the request back. For a sign-in page.
|
|
58
58
|
*
|
|
59
59
|
* ```tsx
|
|
60
60
|
* const methods = useAuthMethods()
|
|
61
|
-
* return <h1>{methods?.
|
|
61
|
+
* return <h1>{methods?.branding?.name}</h1>
|
|
62
62
|
* ```
|
|
63
63
|
*/
|
|
64
64
|
export declare function useAuthMethods(options?: {
|
package/dist/auth.js
CHANGED
|
@@ -81,15 +81,15 @@ export function useAuth(collection) {
|
|
|
81
81
|
return { user, auth };
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
|
-
* How to sign in here: the ways on offer, sign-up, and the
|
|
85
|
-
*
|
|
84
|
+
* How to sign in here: the ways on offer, sign-up, and the project's
|
|
85
|
+
* branding. Null while
|
|
86
86
|
* it loads, and when the load fails: the sign-in calls report their own
|
|
87
87
|
* errors. Name an auth `collection` when the project has several, and pass
|
|
88
88
|
* `enabled: false` to hold the request back. For a sign-in page.
|
|
89
89
|
*
|
|
90
90
|
* ```tsx
|
|
91
91
|
* const methods = useAuthMethods()
|
|
92
|
-
* return <h1>{methods?.
|
|
92
|
+
* return <h1>{methods?.branding?.name}</h1>
|
|
93
93
|
* ```
|
|
94
94
|
*/
|
|
95
95
|
export function useAuthMethods(options = {}) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { ViperProvider, useViper, type ViperProviderProps } from "./provider.js";
|
|
2
2
|
export { useAuth, useAuthMethods, useSession, useSessionOf, useInvites, useSessions, useTenants, useUser, } from "./auth.js";
|
|
3
|
-
export { useRealtime, useRealtimeStatus } from "./realtime.js";
|
|
3
|
+
export { usePresence, useRealtime, useRealtimeStatus } from "./realtime.js";
|
|
4
4
|
export { useLoaded, type Query } from "./loaded.js";
|
|
5
|
-
export { useRecord, useRecords, type RecordsOptions } from "./records.js";
|
|
5
|
+
export { useAggregate, useRecord, useRecords, type AggregateHookOptions, type RecordsOptions, } from "./records.js";
|
|
6
|
+
export { useUpload, type UploadState } from "./upload.js";
|
|
6
7
|
export { useRun, type RunState } from "./runs.js";
|
|
7
|
-
export {
|
|
8
|
-
export { type AppIdentity, type AppMember, type AppRole, type AppLevel, Viper, ViperError, fieldError, memoryStorage, type ListOptions, type ListResult, type RealtimeEvent, type RealtimeStatus, type RecordInput, type RecordModel, type Session, type UserSession, type StartedRun, type ViperOptions, type WorkflowRun, } from "@polyphron/viper-client";
|
|
8
|
+
export { Viper, ViperError, fieldError, memoryStorage, type AggregateOptions, type AggregateResult, type AggregateRow, type ListOptions, type ListResult, type PresenceMember, type RealtimeEvent, type RealtimeStatus, type Room, type RoomListener, type RecordInput, type RecordModel, type Session, type UserSession, type StartedRun, type ViperOptions, type WorkflowRun, } from "@polyphron/viper-client";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { ViperProvider, useViper } from "./provider.js";
|
|
2
2
|
export { useAuth, useAuthMethods, useSession, useSessionOf, useInvites, useSessions, useTenants, useUser, } from "./auth.js";
|
|
3
|
-
export { useRealtime, useRealtimeStatus } from "./realtime.js";
|
|
3
|
+
export { usePresence, useRealtime, useRealtimeStatus } from "./realtime.js";
|
|
4
4
|
export { useLoaded } from "./loaded.js";
|
|
5
|
-
export { useRecord, useRecords } from "./records.js";
|
|
5
|
+
export { useAggregate, useRecord, useRecords, } from "./records.js";
|
|
6
|
+
export { useUpload } from "./upload.js";
|
|
6
7
|
export { useRun } from "./runs.js";
|
|
7
|
-
export { useAppIdentity, useAppManagement } from "./identity.js";
|
|
8
8
|
// Re-exported so an app only needs @polyphron/viper-react in its imports.
|
|
9
9
|
export { Viper, ViperError, fieldError, memoryStorage, } from "@polyphron/viper-client";
|
package/dist/realtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RealtimeEvent, RealtimeStatus, RecordModel } from "@polyphron/viper-client";
|
|
1
|
+
import type { PresenceMember, RealtimeEvent, RealtimeStatus, RecordModel } from "@polyphron/viper-client";
|
|
2
2
|
/**
|
|
3
3
|
* Watches topics and calls `onEvent` for each change. A topic is a collection
|
|
4
4
|
* name for every record in it (`posts`), or `collection/id` for one record.
|
|
@@ -8,3 +8,14 @@ import type { RealtimeEvent, RealtimeStatus, RecordModel } from "@polyphron/vipe
|
|
|
8
8
|
export declare function useRealtime<T extends RecordModel = RecordModel>(topics: string | string[], onEvent: (event: RealtimeEvent<T>) => void): RealtimeStatus;
|
|
9
9
|
/** The realtime connection status, on its own. */
|
|
10
10
|
export declare function useRealtimeStatus(): RealtimeStatus;
|
|
11
|
+
/**
|
|
12
|
+
* Joins a room (`posts` or `posts/abc123`) while mounted and returns who is
|
|
13
|
+
* in it. `state` is what others see about you; a new value is sent when its
|
|
14
|
+
* JSON changes. `onBroadcast` hears what the others send with `broadcast`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function usePresence<S = unknown, D = unknown>(room: string, state?: S, onBroadcast?: (data: D, from: PresenceMember<S>) => void): {
|
|
17
|
+
members: PresenceMember<S>[];
|
|
18
|
+
me: string;
|
|
19
|
+
error: string;
|
|
20
|
+
broadcast: (data: D) => void;
|
|
21
|
+
};
|
package/dist/realtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
1
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
2
2
|
import { useViper } from "./provider.js";
|
|
3
3
|
/**
|
|
4
4
|
* Watches topics and calls `onEvent` for each change. A topic is a collection
|
|
@@ -23,3 +23,52 @@ export function useRealtimeStatus() {
|
|
|
23
23
|
const viper = useViper();
|
|
24
24
|
return useSyncExternalStore((listener) => viper.realtime.onStatus(listener), () => viper.realtime.status, () => "closed");
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Joins a room (`posts` or `posts/abc123`) while mounted and returns who is
|
|
28
|
+
* in it. `state` is what others see about you; a new value is sent when its
|
|
29
|
+
* JSON changes. `onBroadcast` hears what the others send with `broadcast`.
|
|
30
|
+
*/
|
|
31
|
+
export function usePresence(room, state, onBroadcast) {
|
|
32
|
+
const viper = useViper();
|
|
33
|
+
const [members, setMembers] = useState([]);
|
|
34
|
+
const [error, setError] = useState("");
|
|
35
|
+
const [joined, setJoined] = useState(null);
|
|
36
|
+
const onBroadcastRef = useRef(onBroadcast);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
onBroadcastRef.current = onBroadcast;
|
|
39
|
+
});
|
|
40
|
+
// The state as JSON, so a new object with the same values sends nothing.
|
|
41
|
+
const stateKey = JSON.stringify(state ?? null);
|
|
42
|
+
const stateRef = useRef(state);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
stateRef.current = state;
|
|
45
|
+
});
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!room)
|
|
48
|
+
return;
|
|
49
|
+
const r = viper.realtime.join(room, {
|
|
50
|
+
onPresence: (m) => {
|
|
51
|
+
setMembers(m);
|
|
52
|
+
setError("");
|
|
53
|
+
},
|
|
54
|
+
onBroadcast: (data, from) => onBroadcastRef.current?.(data, from),
|
|
55
|
+
onError: setError,
|
|
56
|
+
}, stateRef.current);
|
|
57
|
+
setJoined(r);
|
|
58
|
+
return () => {
|
|
59
|
+
r.leave();
|
|
60
|
+
setJoined(null);
|
|
61
|
+
setMembers([]);
|
|
62
|
+
};
|
|
63
|
+
}, [viper, room]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (joined && stateRef.current !== undefined)
|
|
66
|
+
joined.track(stateRef.current);
|
|
67
|
+
}, [joined, stateKey]);
|
|
68
|
+
return {
|
|
69
|
+
members,
|
|
70
|
+
me: joined?.me ?? "",
|
|
71
|
+
error,
|
|
72
|
+
broadcast: (data) => joined?.broadcast(data),
|
|
73
|
+
};
|
|
74
|
+
}
|
package/dist/records.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ListOptions, ListResult, RecordModel } from "@polyphron/viper-client";
|
|
1
|
+
import type { AggregateOptions, AggregateResult, ListOptions, ListResult, RecordModel } from "@polyphron/viper-client";
|
|
2
2
|
import { type Query } from "./loaded.js";
|
|
3
3
|
export type RecordsOptions = ListOptions & {
|
|
4
4
|
/** Re-fetch when anything in the collection changes. Off by default. */
|
|
@@ -12,8 +12,22 @@ export type RecordsOptions = ListOptions & {
|
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
14
|
export declare function useRecords<T extends RecordModel = RecordModel>(collection: string, options?: RecordsOptions): Query<ListResult<T>>;
|
|
15
|
+
export type AggregateHookOptions = AggregateOptions & {
|
|
16
|
+
/** Re-fetch when anything in the collection changes. Off by default. */
|
|
17
|
+
live?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Counts and sums of a collection, grouped by fields or time buckets,
|
|
21
|
+
* re-fetched when the options change.
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const { data } = useAggregate("orders", { group: "created:day", agg: "count,sum:amount" })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function useAggregate(collection: string, options?: AggregateHookOptions): Query<AggregateResult>;
|
|
15
28
|
/** One record, re-fetched when the id changes. */
|
|
16
29
|
export declare function useRecord<T extends RecordModel = RecordModel>(collection: string, id: string | undefined, options?: {
|
|
17
30
|
expand?: string;
|
|
31
|
+
fields?: string;
|
|
18
32
|
live?: boolean;
|
|
19
33
|
}): Query<T>;
|
package/dist/records.js
CHANGED
|
@@ -20,15 +20,34 @@ export function useRecords(collection, options = {}) {
|
|
|
20
20
|
useLiveReload(live, collection, query.reload, viper);
|
|
21
21
|
return query;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Counts and sums of a collection, grouped by fields or time buckets,
|
|
25
|
+
* re-fetched when the options change.
|
|
26
|
+
*
|
|
27
|
+
* ```tsx
|
|
28
|
+
* const { data } = useAggregate("orders", { group: "created:day", agg: "count,sum:amount" })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function useAggregate(collection, options = {}) {
|
|
32
|
+
const viper = useViper();
|
|
33
|
+
const { live = false, ...aggregate } = options;
|
|
34
|
+
// Options are a fresh object every render; compare them by value.
|
|
35
|
+
const key = JSON.stringify(aggregate);
|
|
36
|
+
const query = useLoaded(`${collection}:aggregate`, useCallback((signal) => viper
|
|
37
|
+
.collection(collection)
|
|
38
|
+
.aggregate({ ...JSON.parse(key), signal }), [viper, collection, key]));
|
|
39
|
+
useLiveReload(live, collection, query.reload, viper);
|
|
40
|
+
return query;
|
|
41
|
+
}
|
|
23
42
|
/** One record, re-fetched when the id changes. */
|
|
24
43
|
export function useRecord(collection, id, options = {}) {
|
|
25
44
|
const viper = useViper();
|
|
26
|
-
const { live = false, expand } = options;
|
|
45
|
+
const { live = false, expand, fields } = options;
|
|
27
46
|
const query = useLoaded(
|
|
28
47
|
// A different record must never show the last one's data.
|
|
29
48
|
`${collection}/${id ?? ""}`, useCallback((signal) => id
|
|
30
|
-
? viper.collection(collection).view(id, { expand, signal })
|
|
31
|
-
: Promise.resolve(null), [viper, collection, id, expand]));
|
|
49
|
+
? viper.collection(collection).view(id, { expand, fields, signal })
|
|
50
|
+
: Promise.resolve(null), [viper, collection, id, expand, fields]));
|
|
32
51
|
useLiveReload(live && Boolean(id), `${collection}/${id}`, query.reload, viper);
|
|
33
52
|
return query;
|
|
34
53
|
}
|
package/dist/upload.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RecordModel, UploadLargeOptions, ViperError } from "@polyphron/viper-client";
|
|
2
|
+
/** Where a big upload is: what `useUpload` returns next to `upload`. */
|
|
3
|
+
export type UploadState<T extends RecordModel> = {
|
|
4
|
+
/** Sends the file; resolves with the record, rejects on failure. */
|
|
5
|
+
upload: (id: string, field: string, file: Blob, options?: Omit<UploadLargeOptions, "onProgress" | "signal">) => Promise<T>;
|
|
6
|
+
/** Stops the upload in flight. */
|
|
7
|
+
cancel: () => void;
|
|
8
|
+
/** 0 to 1; stays at 1 once done. */
|
|
9
|
+
progress: number;
|
|
10
|
+
uploading: boolean;
|
|
11
|
+
error: ViperError | Error | null;
|
|
12
|
+
/** The record the last finished upload returned. */
|
|
13
|
+
data: T | null;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A big file into a file field, in parts or straight to the bucket (the
|
|
17
|
+
* project's big-uploads switch must be on), with progress.
|
|
18
|
+
*
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const { upload, progress } = useUpload("videos")
|
|
21
|
+
* await upload(record.id, "file", input.files[0])
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function useUpload<T extends RecordModel = RecordModel>(collection: string): UploadState<T>;
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
import { useViper } from "./provider.js";
|
|
3
|
+
/**
|
|
4
|
+
* A big file into a file field, in parts or straight to the bucket (the
|
|
5
|
+
* project's big-uploads switch must be on), with progress.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* const { upload, progress } = useUpload("videos")
|
|
9
|
+
* await upload(record.id, "file", input.files[0])
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function useUpload(collection) {
|
|
13
|
+
const viper = useViper();
|
|
14
|
+
const [progress, setProgress] = useState(0);
|
|
15
|
+
const [uploading, setUploading] = useState(false);
|
|
16
|
+
const [error, setError] = useState(null);
|
|
17
|
+
const [data, setData] = useState(null);
|
|
18
|
+
const abort = useRef(null);
|
|
19
|
+
const upload = useCallback(async (id, field, file, options = {}) => {
|
|
20
|
+
abort.current = new AbortController();
|
|
21
|
+
setUploading(true);
|
|
22
|
+
setProgress(0);
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
const record = await viper.collection(collection).uploadLarge(id, field, file, {
|
|
26
|
+
...options,
|
|
27
|
+
signal: abort.current.signal,
|
|
28
|
+
onProgress: (sent, total) => setProgress(total ? sent / total : 1),
|
|
29
|
+
});
|
|
30
|
+
setProgress(1);
|
|
31
|
+
setData(record);
|
|
32
|
+
return record;
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
setError(e);
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
setUploading(false);
|
|
40
|
+
}
|
|
41
|
+
}, [viper, collection]);
|
|
42
|
+
const cancel = useCallback(() => abort.current?.abort(), []);
|
|
43
|
+
return { upload, cancel, progress, uploading, error, data };
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polyphron/viper-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "React provider and hooks for the Viper app API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"typecheck": "tsc --noEmit"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@polyphron/viper-client": "^0.
|
|
33
|
+
"@polyphron/viper-client": "^0.5.0",
|
|
34
34
|
"@tanstack/db": "0.9.2",
|
|
35
35
|
"@tanstack/react-db": "0.4.1",
|
|
36
36
|
"react": "^18.3.0 || ^19.0.0"
|