@pylonsync/react 0.3.149 → 0.3.152
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 +154 -0
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @pylonsync/react
|
|
2
|
+
|
|
3
|
+
React hooks for [Pylon](https://pylonsync.com) — live queries, optimistic mutations, reactive server functions, search, presence, all backed by a local sync replica that re-renders on every change.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bun add @pylonsync/react
|
|
7
|
+
# or: npm i @pylonsync/react
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
// app entry — once, anywhere before your first hook fires
|
|
14
|
+
import { init } from "@pylonsync/react";
|
|
15
|
+
|
|
16
|
+
init({ baseUrl: "http://localhost:4321" });
|
|
17
|
+
|
|
18
|
+
// any component
|
|
19
|
+
import { db } from "@pylonsync/react";
|
|
20
|
+
|
|
21
|
+
type Todo = { id: string; title: string; done: boolean };
|
|
22
|
+
|
|
23
|
+
export function TodoList() {
|
|
24
|
+
const { data: todos, loading } = db.useQuery<Todo>("Todo", {
|
|
25
|
+
where: { done: false },
|
|
26
|
+
orderBy: { createdAt: "desc" },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const create = db.useMutation<{ title: string }>("createTodo");
|
|
30
|
+
|
|
31
|
+
if (loading) return <Spinner />;
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<ul>{todos.map((t) => <li key={t.id}>{t.title}</li>)}</ul>
|
|
35
|
+
<button onClick={() => create.mutate({ title: "Buy milk" })}>+ Add</button>
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`db.useQuery` subscribes to the local sync replica. Inserts (yours or anyone else's), updates, deletes — every component reading this entity re-renders automatically. No queryClient, no manual invalidation, no polling.
|
|
42
|
+
|
|
43
|
+
## The `db` namespace
|
|
44
|
+
|
|
45
|
+
`db.*` is the ergonomic API for apps that use a single global sync engine (set up via `init()` once at startup). It owns engine lifecycle, optimistic update bookkeeping, and storage namespacing.
|
|
46
|
+
|
|
47
|
+
| Method | What it does |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `db.useQuery<T>(entity, opts?)` | Live list of rows |
|
|
50
|
+
| `db.useQueryOne<T>(entity, id)` | Live single row |
|
|
51
|
+
| `db.useInfiniteQuery<T>(entity, opts)` | Cursor-paginated list with `loadMore()` |
|
|
52
|
+
| `db.useReactiveQuery<T>(fnName, args?)` | Convex-style auto-rerunning server query |
|
|
53
|
+
| `db.useAggregate<R>(entity, spec)` | Live count / sum / avg / groupBy |
|
|
54
|
+
| `db.useSearch<T>(entity, spec)` | Full-text + faceted live search |
|
|
55
|
+
| `db.useMutation<I, O>(fnName, opts?)` | Server function call w/ optional optimistic update |
|
|
56
|
+
| `db.useEntity(entity)` | Optimistic CRUD bound to one entity |
|
|
57
|
+
| `db.insert/update/delete` | Imperative writes (returns id) |
|
|
58
|
+
| `db.fn<T>(name, args?)` | Imperative server function call |
|
|
59
|
+
| `db.streamFn(name, args?)` | Async iterable of SSE chunks |
|
|
60
|
+
| `db.uploadFile(blob, opts?)` | Upload to `/api/files/upload` |
|
|
61
|
+
| `db.setPresence(data)` | Publish presence for the current user |
|
|
62
|
+
| `db.publishTopic(topic, data)` | Fire-and-forget pubsub |
|
|
63
|
+
| `db.sync` | The underlying `SyncEngine` for escape hatches |
|
|
64
|
+
|
|
65
|
+
## Auth
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { useSession } from "@pylonsync/react";
|
|
69
|
+
|
|
70
|
+
function NavBar() {
|
|
71
|
+
const { auth, signOut } = useSession(db.sync);
|
|
72
|
+
if (!auth) return <a href="/login">Sign in</a>;
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<span>{auth.user.displayName}</span>
|
|
76
|
+
<button onClick={signOut}>Sign out</button>
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`useSession` returns a live `auth` object (re-renders when the user signs in, signs out, switches org, or has their session revoked from another device). It also exposes `selectOrg`, `clearOrg`, and `refresh` for multi-tenant flows.
|
|
83
|
+
|
|
84
|
+
## Optimistic mutations
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
const send = db.useMutation<
|
|
88
|
+
{ channelId: string; body: string },
|
|
89
|
+
{ messageId: string }
|
|
90
|
+
>("sendMessage", {
|
|
91
|
+
optimistic: (args, ctx) => ({
|
|
92
|
+
entity: "Message",
|
|
93
|
+
data: {
|
|
94
|
+
id: ctx.id, // ghost id; server's reply merges in-place
|
|
95
|
+
channelId: args.channelId,
|
|
96
|
+
body: args.body,
|
|
97
|
+
authorId: me.id,
|
|
98
|
+
createdAt: ctx.now,
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await send.mutate({ channelId, body: "Hi!" });
|
|
104
|
+
// Message appears in `db.useQuery("Message", ...)` before the server responds.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The framework paints the ghost into the local store, threads `ctx.id` to the server (where your handler should re-use it), and reconciles on server broadcast. See the [optimistic updates concept doc](https://pylonsync.com/docs/concepts/optimistic-updates) for the full pattern.
|
|
108
|
+
|
|
109
|
+
## Live presence + rooms
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { useRoom } from "@pylonsync/react";
|
|
113
|
+
|
|
114
|
+
function Editor({ documentId }: { documentId: string }) {
|
|
115
|
+
const room = useRoom(db.sync, `doc:${documentId}`);
|
|
116
|
+
return (
|
|
117
|
+
<div>
|
|
118
|
+
{room.peers.map((p) => (
|
|
119
|
+
<Cursor key={p.id} x={p.presence.x} y={p.presence.y} />
|
|
120
|
+
))}
|
|
121
|
+
<textarea
|
|
122
|
+
onChange={(e) =>
|
|
123
|
+
room.setPresence({ cursor: e.target.selectionStart })
|
|
124
|
+
}
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
init({
|
|
135
|
+
baseUrl: "http://localhost:4321",
|
|
136
|
+
// Optional — namespaces localStorage keys so multiple Pylon apps
|
|
137
|
+
// on the same origin don't fight over `pylon:token`.
|
|
138
|
+
appName: "my-app",
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
In browser contexts, omitting `baseUrl` falls back to `window.location.origin` — the right answer for Next.js + Vercel deploys that use a `next.config.js` rewrite to proxy `/api/*` to the backend. In SSR it falls back to `http://localhost:4321` (the `pylon dev` default).
|
|
143
|
+
|
|
144
|
+
## With Next.js
|
|
145
|
+
|
|
146
|
+
Use [`@pylonsync/next`](https://npmjs.com/package/@pylonsync/next) for App Router server helpers, middleware, and cookie-aware auth. The full deploy story including Vercel env vars and CORS is at [pylonsync.com/docs/operations/vercel](https://pylonsync.com/docs/operations/vercel).
|
|
147
|
+
|
|
148
|
+
## With React Native
|
|
149
|
+
|
|
150
|
+
Use [`@pylonsync/react-native`](https://npmjs.com/package/@pylonsync/react-native), which ships the same `db.*` API on top of an AsyncStorage-backed sync replica.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.152",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "src/index.ts",
|
|
9
9
|
"types": "src/index.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"check": "tsc -p tsconfig.json --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@pylonsync/sdk": "0.3.
|
|
16
|
-
"@pylonsync/sync": "0.3.
|
|
15
|
+
"@pylonsync/sdk": "0.3.152",
|
|
16
|
+
"@pylonsync/sync": "0.3.152"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"react": ">=19.0.0"
|