@pylonsync/react 0.2.4
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 +24 -0
- package/src/db.ts +218 -0
- package/src/hooks.ts +922 -0
- package/src/index.ts +610 -0
- package/src/typed.ts +149 -0
- package/src/useRoom.ts +231 -0
- package/src/useSession.ts +71 -0
- package/src/useShard.ts +299 -0
- package/tsconfig.json +7 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pylonsync/react",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.2.4",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/index.ts",
|
|
9
|
+
"types": "src/index.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json --noEmit",
|
|
12
|
+
"check": "tsc -p tsconfig.json --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@pylonsync/sdk": "file:../sdk",
|
|
16
|
+
"@pylonsync/sync": "file:../sync"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": ">=18.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^19.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/db.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { SyncEngine, createSyncEngine, type Row, type SyncEngineConfig } from "@pylonsync/sync";
|
|
2
|
+
import {
|
|
3
|
+
useQuery as useQueryHook,
|
|
4
|
+
useQueryOne as useQueryOneHook,
|
|
5
|
+
useMutation as useMutationHook,
|
|
6
|
+
useInfiniteQuery as useInfiniteQueryHook,
|
|
7
|
+
useAggregate as useAggregateHook,
|
|
8
|
+
useSearch as useSearchHook,
|
|
9
|
+
useEntityMutation,
|
|
10
|
+
type QueryOptions,
|
|
11
|
+
type UseQueryReturn,
|
|
12
|
+
type UseQueryOneReturn,
|
|
13
|
+
type UseMutationReturn,
|
|
14
|
+
type UseInfiniteQueryReturn,
|
|
15
|
+
type AggregateSpec,
|
|
16
|
+
type UseAggregateReturn,
|
|
17
|
+
type SearchSpec,
|
|
18
|
+
type UseSearchReturn,
|
|
19
|
+
} from "./hooks";
|
|
20
|
+
import {
|
|
21
|
+
callFn,
|
|
22
|
+
configureClient,
|
|
23
|
+
streamFn,
|
|
24
|
+
uploadFile,
|
|
25
|
+
uploadFileMultipart,
|
|
26
|
+
type UploadedFile,
|
|
27
|
+
} from "./index";
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// db — one-liner API
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
let _sync: SyncEngine | null = null;
|
|
34
|
+
let _started = false;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Initialize the pylon client. Call once at app startup.
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { init } from "@pylonsync/react";
|
|
41
|
+
* init({ baseUrl: "http://localhost:4321" });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function init(config?: Partial<SyncEngineConfig> & { baseUrl?: string }) {
|
|
45
|
+
_sync = createSyncEngine(config?.baseUrl ?? "http://localhost:4321", config);
|
|
46
|
+
_started = false;
|
|
47
|
+
// Keep the React-side helpers in sync — a single init() should fully
|
|
48
|
+
// namespace this app's storage without a separate configureClient call.
|
|
49
|
+
configureClient({
|
|
50
|
+
baseUrl: config?.baseUrl,
|
|
51
|
+
appName: config?.appName,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getSync(): SyncEngine {
|
|
56
|
+
if (!_sync) {
|
|
57
|
+
_sync = createSyncEngine("http://localhost:4321");
|
|
58
|
+
}
|
|
59
|
+
if (!_started) {
|
|
60
|
+
_started = true;
|
|
61
|
+
_sync.start();
|
|
62
|
+
}
|
|
63
|
+
return _sync;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Live query with loading/error state.
|
|
68
|
+
*
|
|
69
|
+
* ```tsx
|
|
70
|
+
* const { data, loading, error } = db.useQuery<Todo>("Todo", {
|
|
71
|
+
* where: { done: false },
|
|
72
|
+
* orderBy: { createdAt: "desc" },
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export const db = {
|
|
77
|
+
/** Live query for entity rows with loading/error state. */
|
|
78
|
+
useQuery<T = Row>(entity: string, options?: QueryOptions): UseQueryReturn<T> {
|
|
79
|
+
return useQueryHook<T>(getSync(), entity, options);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/** Live query for a single row by ID. */
|
|
83
|
+
useQueryOne<T = Row>(entity: string, id: string): UseQueryOneReturn<T> {
|
|
84
|
+
return useQueryOneHook<T>(getSync(), entity, id);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Server-side function call with mutation state (loading, data, error).
|
|
89
|
+
*
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const placeBid = db.useMutation<{lotId: string}, {accepted: boolean}>("placeBid");
|
|
92
|
+
* await placeBid.mutate({ lotId: "x", amount: 150 });
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
useMutation<TArgs = Record<string, unknown>, TResult = unknown>(
|
|
96
|
+
fnName: string
|
|
97
|
+
): UseMutationReturn<TArgs, TResult> {
|
|
98
|
+
return useMutationHook<TArgs, TResult>(fnName);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/** Paginated live query with loadMore(). */
|
|
102
|
+
useInfiniteQuery<T = Row>(
|
|
103
|
+
entity: string,
|
|
104
|
+
options: { pageSize?: number } = {}
|
|
105
|
+
): UseInfiniteQueryReturn<T> {
|
|
106
|
+
return useInfiniteQueryHook<T>(getSync(), entity, options);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Live aggregate query (count / sum / avg / groupBy). Automatically
|
|
111
|
+
* re-runs when the entity's rows change in the sync replica — dashboard
|
|
112
|
+
* charts stay up to date without polling.
|
|
113
|
+
*/
|
|
114
|
+
useAggregate<Row = Record<string, unknown>>(
|
|
115
|
+
entity: string,
|
|
116
|
+
spec: AggregateSpec
|
|
117
|
+
): UseAggregateReturn<Row> {
|
|
118
|
+
return useAggregateHook<Row>(getSync(), entity, spec);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Live faceted full-text search. Returns ranked hits + per-facet
|
|
123
|
+
* counts + total; re-runs when the entity's rows change so facet
|
|
124
|
+
* counts and result lists stay in lockstep with writes.
|
|
125
|
+
*
|
|
126
|
+
* ```tsx
|
|
127
|
+
* const { hits, facetCounts, total } = db.useSearch<Product>("Product", {
|
|
128
|
+
* query: "red sneakers",
|
|
129
|
+
* filters: { category: "shoes" },
|
|
130
|
+
* facets: ["brand", "color"],
|
|
131
|
+
* sort: ["price", "desc"],
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
useSearch<T = Row>(entity: string, spec: SearchSpec): UseSearchReturn<T> {
|
|
136
|
+
return useSearchHook<T>(getSync(), entity, spec);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/** Entity-level optimistic CRUD (not server-side functions). */
|
|
140
|
+
useEntity(entity: string) {
|
|
141
|
+
return useEntityMutation(getSync(), entity);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/** Get the sync engine instance. */
|
|
145
|
+
get sync() {
|
|
146
|
+
return getSync();
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/** Insert a row (optimistic). */
|
|
150
|
+
insert(entity: string, data: Row) {
|
|
151
|
+
return getSync().insert(entity, data);
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/** Update a row (optimistic). */
|
|
155
|
+
update(entity: string, id: string, data: Partial<Row>) {
|
|
156
|
+
return getSync().update(entity, id, data);
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/** Delete a row (optimistic). */
|
|
160
|
+
delete(entity: string, id: string) {
|
|
161
|
+
return getSync().delete(entity, id);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/** Set presence data. */
|
|
165
|
+
setPresence(data: Record<string, unknown>) {
|
|
166
|
+
(getSync() as unknown as { setPresence: (d: Record<string, unknown>) => void }).setPresence(
|
|
167
|
+
data
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/** Publish to a topic. */
|
|
172
|
+
publishTopic(topic: string, data: unknown) {
|
|
173
|
+
(getSync() as unknown as { publishTopic: (t: string, d: unknown) => void }).publishTopic(
|
|
174
|
+
topic,
|
|
175
|
+
data
|
|
176
|
+
);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Call a server-side function (query, mutation, or action).
|
|
181
|
+
*
|
|
182
|
+
* ```ts
|
|
183
|
+
* const result = await db.fn("placeBid", { lotId: "x", amount: 150 });
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
fn<T = unknown>(name: string, args?: Record<string, unknown>): Promise<T> {
|
|
187
|
+
return callFn<T>(name, args);
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Stream output from a server-side function as SSE chunks.
|
|
192
|
+
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* for await (const chunk of db.streamFn("chat", { message: "hi" })) {
|
|
195
|
+
* console.log(chunk);
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
streamFn(name: string, args?: Record<string, unknown>) {
|
|
200
|
+
return streamFn(name, args);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/** Upload a file to /api/files/upload. */
|
|
204
|
+
uploadFile(
|
|
205
|
+
input: File | Blob | ArrayBuffer | Uint8Array,
|
|
206
|
+
options?: { filename?: string; contentType?: string }
|
|
207
|
+
): Promise<UploadedFile> {
|
|
208
|
+
return uploadFile(input, options);
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/** Upload via multipart/form-data with extra fields. */
|
|
212
|
+
uploadFileMultipart(
|
|
213
|
+
file: File | Blob,
|
|
214
|
+
fields?: Record<string, string>
|
|
215
|
+
): Promise<UploadedFile> {
|
|
216
|
+
return uploadFileMultipart(file, fields);
|
|
217
|
+
},
|
|
218
|
+
};
|