@palbase/web 1.0.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/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/analytics-facade-DkOwkEpi.d.ts +454 -0
- package/dist/analytics-facade-t6UrFdn7.d.cts +454 -0
- package/dist/chunk-JVT65V4E.js +3384 -0
- package/dist/chunk-JVT65V4E.js.map +1 -0
- package/dist/chunk-VJXFABBW.js +94 -0
- package/dist/chunk-VJXFABBW.js.map +1 -0
- package/dist/errors-fDoNdTrJ.d.cts +35 -0
- package/dist/errors-fDoNdTrJ.d.ts +35 -0
- package/dist/index.cjs +2394 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.cjs +3403 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +49 -0
- package/dist/internal.d.ts +49 -0
- package/dist/internal.js +19 -0
- package/dist/internal.js.map +1 -0
- package/dist/next/client.cjs +3131 -0
- package/dist/next/client.cjs.map +1 -0
- package/dist/next/client.d.cts +19 -0
- package/dist/next/client.d.ts +19 -0
- package/dist/next/client.js +75 -0
- package/dist/next/client.js.map +1 -0
- package/dist/next/index.cjs +3680 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +238 -0
- package/dist/next/index.d.ts +238 -0
- package/dist/next/index.js +301 -0
- package/dist/next/index.js.map +1 -0
- package/dist/pb-BmgkAe97.d.ts +54 -0
- package/dist/pb-Cudze7Kb.d.cts +54 -0
- package/dist/react/index.cjs +649 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +86 -0
- package/dist/react/index.d.ts +86 -0
- package/dist/react/index.js +156 -0
- package/dist/react/index.js.map +1 -0
- package/dist/storage-BPaeSG8K.d.cts +21 -0
- package/dist/storage-BPaeSG8K.d.ts +21 -0
- package/package.json +123 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Palbase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# @palbase/web
|
|
2
|
+
|
|
3
|
+
The Palbase client SDK for web. One import, one entry point: **`pb`**.
|
|
4
|
+
|
|
5
|
+
`@palbase/web` is typed against **your own backend**. You run `palbase web link` once, it
|
|
6
|
+
generates a `palbe.gen.ts` file from your deployed endpoints, and from then on
|
|
7
|
+
`pb.todos.create(...)`, `pb.rooms.list(...)` and friends are fully typed — request
|
|
8
|
+
shapes, response shapes and per-endpoint errors, all inferred from the backend you
|
|
9
|
+
shipped. Auth, feature flags, realtime and analytics come built in.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { pb } from '@palbase/web';
|
|
13
|
+
|
|
14
|
+
await pb.auth.signIn({ email, password });
|
|
15
|
+
const todo = await pb.todos.create({ title: 'Ship it' }); // typed by your backend
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm i @palbase/web
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then link your project and generate the typed client:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx palbase web link
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`palbase web link` writes `palbe.gen.ts` (the typed client config + namespaces),
|
|
33
|
+
wires `import './palbe.gen';` into your app entry, and adds a `predev`/`prebuild`
|
|
34
|
+
hook that keeps the types in sync. **Commit `palbe.gen.ts`** — it's the typed
|
|
35
|
+
client your app imports, not a build artifact.
|
|
36
|
+
|
|
37
|
+
The generated file calls `__configure(...)` with your project's URL and anon API
|
|
38
|
+
key at module load, so importing it once at startup is all the setup there is.
|
|
39
|
+
If you forget to import it, every `pb` call throws a guided `BackendError`
|
|
40
|
+
(`kind: 'notConfigured'`).
|
|
41
|
+
|
|
42
|
+
> Regenerate any time your backend changes: `npx palbase types` (or just run
|
|
43
|
+
> `dev`/`build` — the hook does it for you).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Calling your backend
|
|
48
|
+
|
|
49
|
+
Codegen turns each backend controller into a typed namespace on `pb`. A
|
|
50
|
+
controller method `todos.create` becomes `pb.todos.create(input)`; a top-level
|
|
51
|
+
endpoint `getHello` becomes `pb.getHello()`. Path params come first, the request
|
|
52
|
+
body/query second:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { pb } from '@palbase/web';
|
|
56
|
+
|
|
57
|
+
// POST /todos/create — body typed, response typed
|
|
58
|
+
const todo = await pb.todos.create({ title: 'Buy milk', done: false });
|
|
59
|
+
|
|
60
|
+
// GET /todos/{id} — path param, then options
|
|
61
|
+
const one = await pb.todos.get('todo_123');
|
|
62
|
+
|
|
63
|
+
// GET /todos — typed query object
|
|
64
|
+
const list = await pb.todos.list({ limit: 20, done: false });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Escape hatches
|
|
68
|
+
|
|
69
|
+
When you need to call an endpoint that isn't in the generated types yet (or do a
|
|
70
|
+
raw multipart upload), use `pb.call` and `pb.upload`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// Untyped POST — you supply the response type
|
|
74
|
+
const result = await pb.call<{ ok: boolean }>('todos/archive', { id: 'todo_123' });
|
|
75
|
+
|
|
76
|
+
// Multipart upload with progress
|
|
77
|
+
const uploaded = await pb.upload<{ url: string }>('files/avatar', {
|
|
78
|
+
file: blob,
|
|
79
|
+
filename: 'avatar.png',
|
|
80
|
+
onProgress: ({ sent, total }) => console.log(sent / total),
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Auth
|
|
87
|
+
|
|
88
|
+
`pb.auth` is the full authentication surface — email/password, OTP, magic links,
|
|
89
|
+
OAuth, password reset, email verification:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
await pb.auth.signUp({ email: 'a@b.com', password: 'secret123' });
|
|
93
|
+
const { user, session } = await pb.auth.signIn({ email: 'a@b.com', password: 'secret123' });
|
|
94
|
+
await pb.auth.signOut();
|
|
95
|
+
|
|
96
|
+
// React to session changes
|
|
97
|
+
const unsub = pb.auth.onAuthStateChange((state) => {
|
|
98
|
+
console.log(state.status); // 'signedIn' | 'signedOut'
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
pb.auth.isSignedIn; // boolean (token presence)
|
|
102
|
+
pb.auth.currentUser; // AuthUser | null
|
|
103
|
+
await pb.auth.refreshUser(); // re-fetch the profile (e.g. emailVerified flip)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Phone OTP, magic links and OAuth:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
await pb.auth.signInWithOTP({ phone: '+1555…' });
|
|
110
|
+
await pb.auth.verifyOTP({ phone: '+1555…', token: '123456' });
|
|
111
|
+
|
|
112
|
+
await pb.auth.signInWithMagicLink('a@b.com');
|
|
113
|
+
|
|
114
|
+
// Redirects the browser to the provider; completes via @palbase/web/next (see below)
|
|
115
|
+
await pb.auth.signInWithOAuth({ provider: 'google', redirectTo: '/auth/callback' });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Feature flags
|
|
121
|
+
|
|
122
|
+
`pb.flags` polls and caches your project's feature flags (auth-aware — flags
|
|
123
|
+
re-evaluate when the user signs in/out):
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
if (pb.flags.isEnabled('new-checkout')) { /* … */ }
|
|
127
|
+
|
|
128
|
+
pb.flags.getString('theme', 'light');
|
|
129
|
+
pb.flags.getInt('max-items', 10);
|
|
130
|
+
|
|
131
|
+
await pb.flags.getVariant('pricing-experiment'); // multivariate → variant name | null
|
|
132
|
+
|
|
133
|
+
const off = pb.flags.onChange(() => console.log('flags changed', pb.flags.all()));
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Realtime
|
|
139
|
+
|
|
140
|
+
`pb.realtime` multiplexes every subscription over one auto-reconnecting
|
|
141
|
+
WebSocket. Channels are client-only (browser):
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const room = pb.realtime.channel('room:42');
|
|
145
|
+
|
|
146
|
+
const sub = room.on('message', (payload) => {
|
|
147
|
+
console.log('got', payload);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
room.send('message', { text: 'hello' }); // broadcast to other subscribers
|
|
151
|
+
|
|
152
|
+
sub.cancel(); // last cancel on a channel leaves it
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Connection status is observable (`pb.realtime.status.state` /
|
|
156
|
+
`pb.realtime.status.onChange(...)`).
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Analytics
|
|
161
|
+
|
|
162
|
+
`pb.analytics` buffers events client-side and flushes in batches. It manages an
|
|
163
|
+
anonymous distinct id and stitches it to the user on sign-in automatically:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
pb.analytics.capture('checkout_started', { plan: 'pro' });
|
|
167
|
+
pb.analytics.screen('Dashboard');
|
|
168
|
+
pb.analytics.identify('user_123', { email: 'a@b.com' });
|
|
169
|
+
await pb.analytics.flush(); // force-send buffered events
|
|
170
|
+
pb.analytics.setOptOut(true); // GDPR opt-out (drops pending + future)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Errors
|
|
176
|
+
|
|
177
|
+
Every failing call throws a `BackendError`. Inspect `kind` (a coarse category)
|
|
178
|
+
and `code` (the server's machine code):
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
import { pb, BackendError, isBackendError } from '@palbase/web';
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
await pb.todos.create({ title: '' });
|
|
185
|
+
} catch (e) {
|
|
186
|
+
if (isBackendError(e)) {
|
|
187
|
+
e.kind; // 'validation' | 'unauthorized' | 'rateLimited' | 'notConfigured'
|
|
188
|
+
// | 'network' | 'server' | 'decode'
|
|
189
|
+
e.code; // server machine code, e.g. 'invalid_input'
|
|
190
|
+
e.status; // HTTP status
|
|
191
|
+
e.fields; // field-level validation errors (when kind === 'validation')
|
|
192
|
+
e.retryAfter; // seconds (when kind === 'rateLimited')
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Generated code also gives you **typed per-endpoint error classes** (e.g.
|
|
198
|
+
`RoomsCreateRoomLockedError`) with typed `.data`, so you can `instanceof`-match the
|
|
199
|
+
specific failures a given endpoint documents.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Next.js — `@palbase/web/next`
|
|
204
|
+
|
|
205
|
+
The Next.js App Router adapter shares one session cookie between the browser and
|
|
206
|
+
the server so Server Components, Route Handlers and middleware all see the same
|
|
207
|
+
auth state.
|
|
208
|
+
|
|
209
|
+
**1. Configure the browser client** (a client provider/component):
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
'use client';
|
|
213
|
+
import { setupPalbeNext } from '@palbase/web/next/client';
|
|
214
|
+
import { useEffect } from 'react';
|
|
215
|
+
|
|
216
|
+
export function PalbeProvider({ children }: { children: React.ReactNode }) {
|
|
217
|
+
useEffect(() => { setupPalbeNext(); }, []);
|
|
218
|
+
return <>{children}</>;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`setupPalbeNext()` swaps in `cookieSessionStorage` so the session is written to a
|
|
223
|
+
cookie the server can read.
|
|
224
|
+
|
|
225
|
+
**2. Refresh the session in middleware** (required for session-bearing RSC apps):
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
// middleware.ts
|
|
229
|
+
import './palbe.gen';
|
|
230
|
+
import { palbeMiddleware } from '@palbase/web/next';
|
|
231
|
+
import type { NextRequest } from 'next/server';
|
|
232
|
+
|
|
233
|
+
export function middleware(request: NextRequest) {
|
|
234
|
+
return palbeMiddleware(request);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**3. Read data in Server Components** with `pbServer()` — a per-request,
|
|
241
|
+
session-isolated client:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
import { pbServer } from '@palbase/web/next';
|
|
245
|
+
|
|
246
|
+
export default async function Page() {
|
|
247
|
+
const pb = await pbServer();
|
|
248
|
+
const todos = await pb.todos.list({ limit: 20 });
|
|
249
|
+
return <TodoList items={todos} />;
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**4. Complete OAuth** with a callback route handler:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// app/auth/callback/route.ts
|
|
257
|
+
import { handleAuthCallback } from '@palbase/web/next';
|
|
258
|
+
export const GET = handleAuthCallback({ defaultNext: '/' });
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## React — `@palbase/web/react`
|
|
264
|
+
|
|
265
|
+
Thin, concurrent-safe hooks over the observable `pb.*` facades. `react` is an
|
|
266
|
+
**optional** peer dependency — importing `@palbase/web` never pulls React; only
|
|
267
|
+
`@palbase/web/react` does.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
'use client';
|
|
271
|
+
import { useUser, useSession, useFlag, useFlags, useChannel } from '@palbase/web/react';
|
|
272
|
+
|
|
273
|
+
function Profile() {
|
|
274
|
+
const user = useUser(); // AuthUser | null, re-renders on auth change
|
|
275
|
+
const { signedIn } = useSession(); // { signedIn, user }
|
|
276
|
+
const dark = useFlag('dark-mode', false);
|
|
277
|
+
const flags = useFlags(); // whole flag set
|
|
278
|
+
|
|
279
|
+
// Subscribe to a realtime event for the component's lifetime
|
|
280
|
+
const { status } = useChannel('room:42', 'message', (payload) => {
|
|
281
|
+
console.log(payload);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return signedIn ? <span>{user?.email}</span> : <SignInButton />;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|