@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 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
+ };