@archlast/client 0.1.1 → 0.1.3
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 -21
- package/README.md +174 -960
- package/dist/admin/index.cjs.map +1 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/auth/index.cjs +53 -2
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +4170 -1
- package/dist/auth/index.d.ts +4170 -1
- package/dist/auth/index.js +49 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/client.cjs +28 -0
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +3 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +28 -0
- package/dist/client.js.map +1 -1
- package/dist/function-reference.cjs.map +1 -1
- package/dist/function-reference.js.map +1 -1
- package/dist/index.cjs +79 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +48 -2
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +0 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +3 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.js +0 -2
- package/dist/react.js.map +1 -1
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.js.map +1 -1
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -1,960 +1,174 @@
|
|
|
1
|
-
# @archlast/client
|
|
2
|
-
|
|
3
|
-
The Archlast client SDK provides
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Runtime Credential Updates
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
// Rotate API key
|
|
184
|
-
client.setApiKey("arch_new_api_key");
|
|
185
|
-
|
|
186
|
-
// Update session cookies (for SSR)
|
|
187
|
-
client.setBetterAuthCookies({
|
|
188
|
-
"better-auth.session": "new_session_token"
|
|
189
|
-
});
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## React Hooks
|
|
195
|
-
|
|
196
|
-
### useQuery
|
|
197
|
-
|
|
198
|
-
Subscribe to real-time query updates with automatic caching via TanStack Query.
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
import { useQuery } from "@archlast/client/react";
|
|
202
|
-
import { api } from "./_generated/api";
|
|
203
|
-
|
|
204
|
-
function TaskList() {
|
|
205
|
-
// Basic usage
|
|
206
|
-
const tasks = useQuery(api.tasks.list, {});
|
|
207
|
-
|
|
208
|
-
// With arguments
|
|
209
|
-
const task = useQuery(api.tasks.get, { id: "123" });
|
|
210
|
-
|
|
211
|
-
// With options
|
|
212
|
-
const { data, isLoading, error, refetch } = useQuery(
|
|
213
|
-
api.tasks.list,
|
|
214
|
-
{ completed: false },
|
|
215
|
-
{
|
|
216
|
-
// TanStack Query options
|
|
217
|
-
staleTime: 5000,
|
|
218
|
-
refetchOnWindowFocus: true,
|
|
219
|
-
}
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
if (isLoading) return <Spinner />;
|
|
223
|
-
if (error) return <Error message={error.message} />;
|
|
224
|
-
|
|
225
|
-
return <TaskGrid tasks={data} />;
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### useMutation
|
|
230
|
-
|
|
231
|
-
Execute mutations with automatic cache invalidation.
|
|
232
|
-
|
|
233
|
-
```ts
|
|
234
|
-
import { useMutation } from "@archlast/client/react";
|
|
235
|
-
|
|
236
|
-
function CreateTaskForm() {
|
|
237
|
-
const createTask = useMutation(api.tasks.create);
|
|
238
|
-
|
|
239
|
-
const handleSubmit = async (e: FormEvent) => {
|
|
240
|
-
e.preventDefault();
|
|
241
|
-
const formData = new FormData(e.target as HTMLFormElement);
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
const task = await createTask({
|
|
245
|
-
text: formData.get("text") as string,
|
|
246
|
-
priority: "medium"
|
|
247
|
-
});
|
|
248
|
-
console.log("Created:", task._id);
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.error("Failed:", error);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
return (
|
|
255
|
-
<form onSubmit={handleSubmit}>
|
|
256
|
-
<input name="text" required />
|
|
257
|
-
<button type="submit">Create</button>
|
|
258
|
-
</form>
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### usePagination
|
|
264
|
-
|
|
265
|
-
Handle cursor-based pagination with infinite scroll support.
|
|
266
|
-
|
|
267
|
-
```ts
|
|
268
|
-
import { usePagination } from "@archlast/client/react";
|
|
269
|
-
|
|
270
|
-
function InfiniteTaskList() {
|
|
271
|
-
const {
|
|
272
|
-
items, // Accumulated items across all pages
|
|
273
|
-
cursor, // Current cursor for next page
|
|
274
|
-
isDone, // Whether pagination is complete
|
|
275
|
-
hasMore, // Convenience flag: !isDone && cursor exists
|
|
276
|
-
isLoading, // Loading state
|
|
277
|
-
loadMore, // Function to load next page
|
|
278
|
-
refresh, // Function to reset and reload from first page
|
|
279
|
-
page, // Optional: current page number (if server provides)
|
|
280
|
-
pageSize, // Optional: page size (if server provides)
|
|
281
|
-
total, // Optional: total count (if server provides)
|
|
282
|
-
} = usePagination(
|
|
283
|
-
api.tasks.listPaginated,
|
|
284
|
-
{ completed: false }, // Base args (no cursor/limit)
|
|
285
|
-
{ limit: 20 } // Options
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
return (
|
|
289
|
-
<div>
|
|
290
|
-
<ul>
|
|
291
|
-
{items.map(task => (
|
|
292
|
-
<li key={task._id}>{task.text}</li>
|
|
293
|
-
))}
|
|
294
|
-
</ul>
|
|
295
|
-
|
|
296
|
-
{hasMore && (
|
|
297
|
-
<button
|
|
298
|
-
onClick={loadMore}
|
|
299
|
-
disabled={isLoading}
|
|
300
|
-
>
|
|
301
|
-
{isLoading ? "Loading..." : "Load More"}
|
|
302
|
-
</button>
|
|
303
|
-
)}
|
|
304
|
-
|
|
305
|
-
<button onClick={refresh}>Refresh</button>
|
|
306
|
-
</div>
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
**Server-side query must return:**
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
|
-
interface PaginatedResponse<T> {
|
|
315
|
-
items: T[];
|
|
316
|
-
continueCursor: string | null;
|
|
317
|
-
isDone: boolean;
|
|
318
|
-
page?: number; // Optional
|
|
319
|
-
pageSize?: number; // Optional
|
|
320
|
-
total?: number; // Optional
|
|
321
|
-
}
|
|
322
|
-
continueCursor: string | null;
|
|
323
|
-
isDone: boolean;
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### useUpload
|
|
328
|
-
|
|
329
|
-
Upload files with progress tracking.
|
|
330
|
-
|
|
331
|
-
```ts
|
|
332
|
-
import { useUpload } from "@archlast/client/react";
|
|
333
|
-
|
|
334
|
-
function FileUploader() {
|
|
335
|
-
const {
|
|
336
|
-
upload,
|
|
337
|
-
progress,
|
|
338
|
-
isUploading,
|
|
339
|
-
storageId,
|
|
340
|
-
error
|
|
341
|
-
} = useUpload();
|
|
342
|
-
|
|
343
|
-
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
|
344
|
-
const file = e.target.files?.[0];
|
|
345
|
-
if (!file) return;
|
|
346
|
-
|
|
347
|
-
const id = await upload(file, "images");
|
|
348
|
-
console.log("Uploaded with ID:", id);
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
return (
|
|
352
|
-
<div>
|
|
353
|
-
<input type="file" onChange={handleFileChange} disabled={isUploading} />
|
|
354
|
-
|
|
355
|
-
{isUploading && (
|
|
356
|
-
<div>
|
|
357
|
-
<progress value={progress} max={100} />
|
|
358
|
-
<span>{progress}%</span>
|
|
359
|
-
</div>
|
|
360
|
-
)}
|
|
361
|
-
|
|
362
|
-
{storageId && <p>Uploaded: {storageId}</p>}
|
|
363
|
-
{error && <p className="error">{error.message}</p>}
|
|
364
|
-
</div>
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### useAuth
|
|
370
|
-
|
|
371
|
-
Authentication state and actions via the `ArchlastAuthClient` wrapper.
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
import { useAuth } from "@archlast/client/react";
|
|
375
|
-
|
|
376
|
-
function AuthButton() {
|
|
377
|
-
const {
|
|
378
|
-
isAuthenticated,
|
|
379
|
-
user,
|
|
380
|
-
isLoading,
|
|
381
|
-
signIn,
|
|
382
|
-
signOut
|
|
383
|
-
} = useAuth();
|
|
384
|
-
|
|
385
|
-
if (isLoading) return <Spinner />;
|
|
386
|
-
|
|
387
|
-
if (isAuthenticated) {
|
|
388
|
-
return (
|
|
389
|
-
<div>
|
|
390
|
-
<span>Welcome, {user?.name}</span>
|
|
391
|
-
<button onClick={() => signOut()}>
|
|
392
|
-
Sign Out
|
|
393
|
-
</button>
|
|
394
|
-
</div>
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return (
|
|
399
|
-
<button onClick={() => signIn({ email: "user@example.com", password: "..." })}>
|
|
400
|
-
Sign In
|
|
401
|
-
</button>
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Using Better-Auth React Hooks
|
|
407
|
-
|
|
408
|
-
For full-featured auth in React, use Better-Auth's `useSession` hook:
|
|
409
|
-
|
|
410
|
-
```tsx
|
|
411
|
-
import { authClient } from "./lib/better-auth"; // Your Better-Auth client
|
|
412
|
-
|
|
413
|
-
function AuthButton() {
|
|
414
|
-
const { data: session, isPending } = authClient.useSession();
|
|
415
|
-
|
|
416
|
-
if (isPending) return <Spinner />;
|
|
417
|
-
|
|
418
|
-
if (session?.user) {
|
|
419
|
-
return (
|
|
420
|
-
<div>
|
|
421
|
-
<span>Welcome, {session.user.name}</span>
|
|
422
|
-
<button onClick={() => authClient.signOut()}>
|
|
423
|
-
Sign Out
|
|
424
|
-
</button>
|
|
425
|
-
</div>
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return (
|
|
430
|
-
<button onClick={() => authClient.signIn.email({ email, password })}>
|
|
431
|
-
Sign In
|
|
432
|
-
</button>
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
---
|
|
438
|
-
|
|
439
|
-
## Direct Client Usage
|
|
440
|
-
|
|
441
|
-
Use the client directly without React for Node.js, scripts, or non-React frameworks.
|
|
442
|
-
|
|
443
|
-
### Fetch (Query)
|
|
444
|
-
|
|
445
|
-
```ts
|
|
446
|
-
// Fetch a single query
|
|
447
|
-
const tasks = await client.fetch("tasks.list", {});
|
|
448
|
-
|
|
449
|
-
// With arguments
|
|
450
|
-
const task = await client.fetch("tasks.get", { id: "123" });
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
### Mutate
|
|
454
|
-
|
|
455
|
-
```ts
|
|
456
|
-
// Execute a mutation
|
|
457
|
-
const created = await client.mutate("tasks.create", {
|
|
458
|
-
text: "New task",
|
|
459
|
-
priority: "high"
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// Update
|
|
463
|
-
await client.mutate("tasks.update", {
|
|
464
|
-
id: "123",
|
|
465
|
-
completed: true
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
// Delete
|
|
469
|
-
await client.mutate("tasks.delete", { id: "123" });
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
### Subscribe
|
|
473
|
-
|
|
474
|
-
```ts
|
|
475
|
-
// Manual WebSocket subscription
|
|
476
|
-
const unsubscribe = client.subscribe("tasks.list", {}, (data) => {
|
|
477
|
-
console.log("Tasks updated:", data);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// Later: unsubscribe
|
|
481
|
-
unsubscribe();
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
---
|
|
485
|
-
|
|
486
|
-
## Authentication
|
|
487
|
-
|
|
488
|
-
Archlast uses [Better-Auth](https://www.better-auth.com/) for authentication. The server exposes Better-Auth endpoints at `/api/auth/*`.
|
|
489
|
-
|
|
490
|
-
### Using the Auth Client
|
|
491
|
-
|
|
492
|
-
The `ArchlastAuthClient` provides a wrapper around Better-Auth endpoints:
|
|
493
|
-
|
|
494
|
-
```ts
|
|
495
|
-
import { ArchlastAuthClient } from "@archlast/client/auth";
|
|
496
|
-
|
|
497
|
-
const auth = new ArchlastAuthClient({
|
|
498
|
-
baseUrl: "http://localhost:4000",
|
|
499
|
-
apiKey: "arch_your_api_key" // Optional: for programmatic access
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
// Sign up with email/password
|
|
503
|
-
// POST /api/auth/sign-up/email
|
|
504
|
-
await auth.signUp({
|
|
505
|
-
email: "user@example.com",
|
|
506
|
-
password: "secure_password",
|
|
507
|
-
name: "John Doe"
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// Sign in with email/password
|
|
511
|
-
// POST /api/auth/sign-in/email
|
|
512
|
-
await auth.signIn({
|
|
513
|
-
email: "user@example.com",
|
|
514
|
-
password: "secure_password"
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
// Sign in with username (if username plugin enabled)
|
|
518
|
-
await auth.signIn({
|
|
519
|
-
username: "johndoe",
|
|
520
|
-
password: "secure_password"
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
// Get current session
|
|
524
|
-
// GET /api/auth/get-session
|
|
525
|
-
const state = await auth.getState();
|
|
526
|
-
console.log(state.isAuthenticated, state.user);
|
|
527
|
-
|
|
528
|
-
// Sign out
|
|
529
|
-
await auth.signOut();
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### Using Better-Auth Client Directly
|
|
533
|
-
|
|
534
|
-
For full Better-Auth features (OAuth, organization, admin), use the Better-Auth client directly:
|
|
535
|
-
|
|
536
|
-
```ts
|
|
537
|
-
import { createAuthClient } from "better-auth/react";
|
|
538
|
-
import {
|
|
539
|
-
adminClient,
|
|
540
|
-
usernameClient,
|
|
541
|
-
organizationClient,
|
|
542
|
-
// Optional plugins (add as needed):
|
|
543
|
-
// anonymousClient, // For guest users
|
|
544
|
-
// apiKeyClient, // For API key management
|
|
545
|
-
} from "better-auth/client/plugins";
|
|
546
|
-
|
|
547
|
-
const authClient = createAuthClient({
|
|
548
|
-
baseURL: "http://localhost:4000/api/auth",
|
|
549
|
-
fetchOptions: { credentials: "include" },
|
|
550
|
-
plugins: [
|
|
551
|
-
adminClient(),
|
|
552
|
-
usernameClient(),
|
|
553
|
-
organizationClient(),
|
|
554
|
-
],
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// Email sign in (typical for user-facing apps)
|
|
558
|
-
await authClient.signIn.email({
|
|
559
|
-
email: "user@example.com",
|
|
560
|
-
password: "secure_password",
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
// Username sign in (used by dashboard)
|
|
564
|
-
await authClient.signIn.username({
|
|
565
|
-
username: "admin",
|
|
566
|
-
password: "secure_password",
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
// Session management
|
|
570
|
-
const { data: session } = authClient.useSession();
|
|
571
|
-
|
|
572
|
-
// Organization management
|
|
573
|
-
await authClient.organization.create({ name: "My Team" });
|
|
574
|
-
|
|
575
|
-
// Admin operations (requires admin role)
|
|
576
|
-
await authClient.admin.listUsers({ limit: 10 });
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
### Auth State in Components
|
|
580
|
-
|
|
581
|
-
```tsx
|
|
582
|
-
import { useAuth } from "@archlast/client/react";
|
|
583
|
-
|
|
584
|
-
function ProtectedRoute({ children }) {
|
|
585
|
-
const { isAuthenticated, isLoading } = useAuth();
|
|
586
|
-
|
|
587
|
-
if (isLoading) return <LoadingScreen />;
|
|
588
|
-
if (!isAuthenticated) return <Navigate to="/login" />;
|
|
589
|
-
|
|
590
|
-
return children;
|
|
591
|
-
}
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
---
|
|
595
|
-
|
|
596
|
-
## File Storage
|
|
597
|
-
|
|
598
|
-
### Storage Client
|
|
599
|
-
|
|
600
|
-
```ts
|
|
601
|
-
// Access via main client
|
|
602
|
-
const storage = client.storage;
|
|
603
|
-
|
|
604
|
-
// Or import directly
|
|
605
|
-
import { StorageClient } from "@archlast/client/storage";
|
|
606
|
-
const storage = new StorageClient(httpUrl, getAuthHeaders);
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
### Upload Files
|
|
610
|
-
|
|
611
|
-
```ts
|
|
612
|
-
const file = new File(["Hello, World!"], "hello.txt", { type: "text/plain" });
|
|
613
|
-
|
|
614
|
-
// Basic upload
|
|
615
|
-
const result = await client.storage.upload(file);
|
|
616
|
-
console.log("Storage ID:", result.id);
|
|
617
|
-
console.log("URL:", result.url);
|
|
618
|
-
|
|
619
|
-
// Upload with explicit content type
|
|
620
|
-
const blob = new Blob(["data"], { type: "application/octet-stream" });
|
|
621
|
-
const binaryResult = await client.storage.upload(blob, "application/octet-stream");
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### List Files
|
|
625
|
-
|
|
626
|
-
### List Files
|
|
627
|
-
|
|
628
|
-
```ts
|
|
629
|
-
// List files with pagination
|
|
630
|
-
const { items } = await client.storage.list(20, 0); // limit, offset
|
|
631
|
-
|
|
632
|
-
// Next page
|
|
633
|
-
const page2 = await client.storage.list(20, 20);
|
|
634
|
-
|
|
635
|
-
// Items structure
|
|
636
|
-
items.forEach(file => {
|
|
637
|
-
console.log(file.id); // Storage ID
|
|
638
|
-
console.log(file.fileName); // Original filename
|
|
639
|
-
console.log(file.contentType); // MIME type
|
|
640
|
-
console.log(file.size); // Size in bytes
|
|
641
|
-
console.log(file.url); // Public URL
|
|
642
|
-
});
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
### Get Presigned URLs
|
|
646
|
-
|
|
647
|
-
```ts
|
|
648
|
-
// Get temporary signed URL for download
|
|
649
|
-
const { url, expiresAt } = await client.storage.presign(storageId);
|
|
650
|
-
|
|
651
|
-
// Use in img tag
|
|
652
|
-
<img src={url} alt="Uploaded file" />
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### Get Metadata
|
|
656
|
-
|
|
657
|
-
```ts
|
|
658
|
-
const metadata = await client.storage.getMetadata(storageId);
|
|
659
|
-
console.log(metadata.filename); // "hello.txt"
|
|
660
|
-
console.log(metadata.contentType); // "text/plain"
|
|
661
|
-
console.log(metadata.size); // 13
|
|
662
|
-
console.log(metadata.createdAt); // Date
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
---
|
|
666
|
-
|
|
667
|
-
## Admin APIs
|
|
668
|
-
|
|
669
|
-
Access admin functionality (requires admin privileges).
|
|
670
|
-
|
|
671
|
-
### Admin Auth Client
|
|
672
|
-
|
|
673
|
-
The `AdminClient` currently provides authentication management:
|
|
674
|
-
|
|
675
|
-
```ts
|
|
676
|
-
import { AdminClient } from "@archlast/client/admin";
|
|
677
|
-
|
|
678
|
-
const admin = new AdminClient("http://localhost:4000", {
|
|
679
|
-
apiKey: "arch_your_api_key" // Optional: Better-Auth API key
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// Admin authentication
|
|
683
|
-
await admin.auth.signIn(email, password);
|
|
684
|
-
const { user } = await admin.auth.getProfile();
|
|
685
|
-
await admin.auth.signOut();
|
|
686
|
-
|
|
687
|
-
// Session management
|
|
688
|
-
const { sessions } = await admin.auth.getSessions();
|
|
689
|
-
await admin.auth.revokeSession(sessionId);
|
|
690
|
-
await admin.auth.signOutAll();
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
**Available Methods:**
|
|
694
|
-
|
|
695
|
-
- `admin.auth.signIn(email, password)` - Admin sign in
|
|
696
|
-
- `admin.auth.signOut()` - Sign out current session
|
|
697
|
-
- `admin.auth.getProfile()` - Get admin profile
|
|
698
|
-
- `admin.auth.getSessions()` - List all sessions
|
|
699
|
-
- `admin.auth.revokeSession(sessionId)` - Revoke specific session
|
|
700
|
-
- `admin.auth.signOutAll()` - Sign out from all sessions
|
|
701
|
-
|
|
702
|
-
**Note:** Additional admin clients (users, data, API keys) may be added in future releases.
|
|
703
|
-
|
|
704
|
-
---
|
|
705
|
-
|
|
706
|
-
## tRPC Integration
|
|
707
|
-
|
|
708
|
-
For type-safe RPC with full TypeScript inference.
|
|
709
|
-
|
|
710
|
-
### Setup
|
|
711
|
-
|
|
712
|
-
```ts
|
|
713
|
-
import { createArchlastTRPCClient } from "@archlast/client/trpc";
|
|
714
|
-
import type { AppRouter } from "./_generated/rpc";
|
|
715
|
-
|
|
716
|
-
const trpc = createArchlastTRPCClient<AppRouter>({
|
|
717
|
-
baseUrl: "http://localhost:4000",
|
|
718
|
-
apiKey: "arch_your_api_key"
|
|
719
|
-
});
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
### Usage
|
|
723
|
-
|
|
724
|
-
```ts
|
|
725
|
-
// Queries
|
|
726
|
-
const tasks = await trpc.tasks.list.query({});
|
|
727
|
-
const task = await trpc.tasks.get.query({ id: "123" });
|
|
728
|
-
|
|
729
|
-
// Mutations
|
|
730
|
-
const created = await trpc.tasks.create.mutate({ text: "New task" });
|
|
731
|
-
await trpc.tasks.update.mutate({ id: "123", completed: true });
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
### React Query Integration
|
|
735
|
-
|
|
736
|
-
```tsx
|
|
737
|
-
import { createTRPCReact } from "@trpc/react-query";
|
|
738
|
-
import type { AppRouter } from "./_generated/rpc";
|
|
739
|
-
|
|
740
|
-
export const trpc = createTRPCReact<AppRouter>();
|
|
741
|
-
|
|
742
|
-
// In component
|
|
743
|
-
function TaskList() {
|
|
744
|
-
const { data, isLoading } = trpc.tasks.list.useQuery({});
|
|
745
|
-
const createTask = trpc.tasks.create.useMutation();
|
|
746
|
-
|
|
747
|
-
// ...
|
|
748
|
-
}
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
---
|
|
752
|
-
|
|
753
|
-
## Subpath Exports
|
|
754
|
-
|
|
755
|
-
Import specific functionality to reduce bundle size:
|
|
756
|
-
|
|
757
|
-
| Import | Description |
|
|
758
|
-
|--------|-------------|
|
|
759
|
-
| `@archlast/client` | Core `ArchlastClient` class |
|
|
760
|
-
| `@archlast/client/react` | React hooks and provider |
|
|
761
|
-
| `@archlast/client/auth` | Authentication client |
|
|
762
|
-
| `@archlast/client/storage` | File storage client |
|
|
763
|
-
| `@archlast/client/admin` | Admin API client |
|
|
764
|
-
| `@archlast/client/trpc` | tRPC client factory |
|
|
765
|
-
|
|
766
|
-
```ts
|
|
767
|
-
// Minimal import for core functionality
|
|
768
|
-
import { ArchlastClient } from "@archlast/client";
|
|
769
|
-
|
|
770
|
-
// React-specific imports
|
|
771
|
-
import {
|
|
772
|
-
ArchlastProvider,
|
|
773
|
-
useQuery,
|
|
774
|
-
useMutation,
|
|
775
|
-
usePagination,
|
|
776
|
-
useUpload,
|
|
777
|
-
useAuth
|
|
778
|
-
} from "@archlast/client/react";
|
|
779
|
-
|
|
780
|
-
// Auth-only import
|
|
781
|
-
import { ArchlastAuthClient } from "@archlast/client/auth";
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
---
|
|
785
|
-
|
|
786
|
-
## TypeScript Support
|
|
787
|
-
|
|
788
|
-
The client is fully typed. Types are generated by the CLI based on your schema.
|
|
789
|
-
|
|
790
|
-
### Generated Types
|
|
791
|
-
|
|
792
|
-
```ts
|
|
793
|
-
// _generated/api.ts
|
|
794
|
-
export const api = {
|
|
795
|
-
tasks: {
|
|
796
|
-
list: { _path: "tasks.list", _returnType: {} as Task[] },
|
|
797
|
-
get: { _path: "tasks.get", _returnType: {} as Task | null },
|
|
798
|
-
create: { _path: "tasks.create", _returnType: {} as Task },
|
|
799
|
-
// ...
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
```
|
|
803
|
-
|
|
804
|
-
### Usage with Types
|
|
805
|
-
|
|
806
|
-
```ts
|
|
807
|
-
import { useQuery } from "@archlast/client/react";
|
|
808
|
-
import { api } from "./_generated/api";
|
|
809
|
-
|
|
810
|
-
// TypeScript knows `tasks` is Task[]
|
|
811
|
-
const tasks = useQuery(api.tasks.list, {});
|
|
812
|
-
|
|
813
|
-
// TypeScript knows `task` is Task | null
|
|
814
|
-
const task = useQuery(api.tasks.get, { id: "123" });
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
---
|
|
818
|
-
|
|
819
|
-
## Error Handling
|
|
820
|
-
|
|
821
|
-
### Query Errors
|
|
822
|
-
|
|
823
|
-
```tsx
|
|
824
|
-
function TaskList() {
|
|
825
|
-
const { data, error, isError } = useQuery(api.tasks.list, {});
|
|
826
|
-
|
|
827
|
-
if (isError) {
|
|
828
|
-
return (
|
|
829
|
-
<div className="error">
|
|
830
|
-
<h3>Failed to load tasks</h3>
|
|
831
|
-
<p>{error.message}</p>
|
|
832
|
-
<button onClick={() => refetch()}>Retry</button>
|
|
833
|
-
</div>
|
|
834
|
-
);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
return <TaskGrid tasks={data} />;
|
|
838
|
-
}
|
|
839
|
-
```
|
|
840
|
-
|
|
841
|
-
### Mutation Errors
|
|
842
|
-
|
|
843
|
-
```ts
|
|
844
|
-
const createTask = useMutation(api.tasks.create);
|
|
845
|
-
|
|
846
|
-
try {
|
|
847
|
-
await createTask({ text: "" }); // Invalid
|
|
848
|
-
} catch (error) {
|
|
849
|
-
if (error instanceof ValidationError) {
|
|
850
|
-
console.log("Validation failed:", error.issues);
|
|
851
|
-
} else if (error instanceof AuthenticationError) {
|
|
852
|
-
console.log("Not authenticated");
|
|
853
|
-
} else {
|
|
854
|
-
console.log("Unknown error:", error);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
### Global Error Boundary
|
|
860
|
-
|
|
861
|
-
```tsx
|
|
862
|
-
import { QueryErrorResetBoundary } from "@tanstack/react-query";
|
|
863
|
-
import { ErrorBoundary } from "react-error-boundary";
|
|
864
|
-
|
|
865
|
-
function App() {
|
|
866
|
-
return (
|
|
867
|
-
<QueryErrorResetBoundary>
|
|
868
|
-
{({ reset }) => (
|
|
869
|
-
<ErrorBoundary
|
|
870
|
-
onReset={reset}
|
|
871
|
-
fallbackRender={({ error, resetErrorBoundary }) => (
|
|
872
|
-
<div>
|
|
873
|
-
<p>Something went wrong: {error.message}</p>
|
|
874
|
-
<button onClick={resetErrorBoundary}>Try again</button>
|
|
875
|
-
</div>
|
|
876
|
-
)}
|
|
877
|
-
>
|
|
878
|
-
<ArchlastProvider client={client}>
|
|
879
|
-
<YourApp />
|
|
880
|
-
</ArchlastProvider>
|
|
881
|
-
</ErrorBoundary>
|
|
882
|
-
)}
|
|
883
|
-
</QueryErrorResetBoundary>
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
```
|
|
887
|
-
|
|
888
|
-
---
|
|
889
|
-
|
|
890
|
-
## Advanced Patterns
|
|
891
|
-
|
|
892
|
-
### Optimistic Updates
|
|
893
|
-
|
|
894
|
-
```ts
|
|
895
|
-
const queryClient = useQueryClient();
|
|
896
|
-
|
|
897
|
-
const updateTask = useMutation(api.tasks.update, {
|
|
898
|
-
onMutate: async (newData) => {
|
|
899
|
-
// Cancel outgoing refetches
|
|
900
|
-
await queryClient.cancelQueries(["tasks.list"]);
|
|
901
|
-
|
|
902
|
-
// Snapshot previous value
|
|
903
|
-
const previous = queryClient.getQueryData(["tasks.list"]);
|
|
904
|
-
|
|
905
|
-
// Optimistically update
|
|
906
|
-
queryClient.setQueryData(["tasks.list"], (old: Task[]) =>
|
|
907
|
-
old.map(t => t._id === newData.id ? { ...t, ...newData } : t)
|
|
908
|
-
);
|
|
909
|
-
|
|
910
|
-
return { previous };
|
|
911
|
-
},
|
|
912
|
-
onError: (err, newData, context) => {
|
|
913
|
-
// Rollback on error
|
|
914
|
-
queryClient.setQueryData(["tasks.list"], context.previous);
|
|
915
|
-
}
|
|
916
|
-
});
|
|
917
|
-
```
|
|
918
|
-
|
|
919
|
-
### SSR / Server Components
|
|
920
|
-
|
|
921
|
-
```ts
|
|
922
|
-
// For Next.js SSR
|
|
923
|
-
const client = new ArchlastClient(
|
|
924
|
-
wsUrl,
|
|
925
|
-
httpUrl,
|
|
926
|
-
"web",
|
|
927
|
-
undefined,
|
|
928
|
-
{
|
|
929
|
-
autoConnect: false, // Don't connect on server
|
|
930
|
-
betterAuthCookies: cookies() // Pass request cookies
|
|
931
|
-
}
|
|
932
|
-
);
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
### Multiple Clients
|
|
936
|
-
|
|
937
|
-
```tsx
|
|
938
|
-
const mainClient = new ArchlastClient(mainWsUrl, mainHttpUrl, "main");
|
|
939
|
-
const analyticsClient = new ArchlastClient(analyticsWsUrl, analyticsHttpUrl, "analytics");
|
|
940
|
-
|
|
941
|
-
function App() {
|
|
942
|
-
return (
|
|
943
|
-
<ArchlastProvider client={mainClient}>
|
|
944
|
-
<ArchlastProvider client={analyticsClient} contextKey="analytics">
|
|
945
|
-
<YourApp />
|
|
946
|
-
</ArchlastProvider>
|
|
947
|
-
</ArchlastProvider>
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
---
|
|
953
|
-
|
|
954
|
-
## Keywords
|
|
955
|
-
|
|
956
|
-
archlast, client, sdk, react, hooks, real-time, websocket, typescript, api
|
|
957
|
-
|
|
958
|
-
## License
|
|
959
|
-
|
|
960
|
-
MIT
|
|
1
|
+
# @archlast/client
|
|
2
|
+
|
|
3
|
+
The Archlast client SDK provides WebSocket-based queries and mutations, React
|
|
4
|
+
hooks with cache integration, authentication helpers, storage utilities, admin
|
|
5
|
+
APIs, and optional tRPC access.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @archlast/client
|
|
11
|
+
|
|
12
|
+
# Other package managers
|
|
13
|
+
pnpm add @archlast/client
|
|
14
|
+
yarn add @archlast/client
|
|
15
|
+
bun add @archlast/client
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Peer dependencies:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install react react-dom @tanstack/react-query
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { ArchlastClient } from "@archlast/client";
|
|
28
|
+
import { ArchlastProvider, useQuery, useMutation } from "@archlast/client/react";
|
|
29
|
+
import { api } from "./_generated/api";
|
|
30
|
+
|
|
31
|
+
const client = new ArchlastClient(
|
|
32
|
+
"ws://localhost:4000/ws",
|
|
33
|
+
"http://localhost:4000",
|
|
34
|
+
"web"
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
function App() {
|
|
38
|
+
return (
|
|
39
|
+
<ArchlastProvider client={client}>
|
|
40
|
+
<TodoList />
|
|
41
|
+
</ArchlastProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function TodoList() {
|
|
46
|
+
const tasks = useQuery(api.tasks.list, {});
|
|
47
|
+
const createTask = useMutation(api.tasks.create);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div>
|
|
51
|
+
<pre>{JSON.stringify(tasks, null, 2)}</pre>
|
|
52
|
+
<button onClick={() => createTask({ text: "New task" })}>
|
|
53
|
+
Add
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The `api` object is generated by the CLI in `_generated/api.ts`.
|
|
61
|
+
|
|
62
|
+
## Client configuration
|
|
63
|
+
|
|
64
|
+
Constructor signature:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
new ArchlastClient(wsUrl, httpUrl?, appId?, authUrl?, options?)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
- `autoConnect` (default true)
|
|
72
|
+
- `apiKey` Better Auth API key (`arch_` prefix)
|
|
73
|
+
- `isAdmin` subscribe to admin streams
|
|
74
|
+
- `betterAuthCookies` for session cookies
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const client = new ArchlastClient(
|
|
78
|
+
"ws://localhost:4000/ws",
|
|
79
|
+
"http://localhost:4000",
|
|
80
|
+
"web",
|
|
81
|
+
undefined,
|
|
82
|
+
{ apiKey: "arch_example" }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
client.setApiKey("arch_new_key");
|
|
86
|
+
client.setBetterAuthCookies({ "better-auth.session": "..." });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## React hooks
|
|
90
|
+
|
|
91
|
+
Available from `@archlast/client/react`:
|
|
92
|
+
|
|
93
|
+
- `useQuery` / `useMutation` for live queries and mutations
|
|
94
|
+
- `usePagination` for cursor based queries
|
|
95
|
+
- `useAuth` for Better Auth session state
|
|
96
|
+
- `useUpload`, `useDownload` for storage
|
|
97
|
+
- `useStorageList`, `useStorageMetadata`, `useStorageDeleteFile`
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const items = useQuery(api.items.list, { limit: 10 });
|
|
103
|
+
const createItem = useMutation(api.items.create);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Direct client usage
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const data = await client.fetch("tasks.get", { id: "123" });
|
|
110
|
+
const updated = await client.mutate("tasks.update", { id: "123", text: "Hi" });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Authentication
|
|
114
|
+
|
|
115
|
+
Auth uses Better Auth endpoints under `/api/auth/*`.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { useAuth } from "@archlast/client/react";
|
|
119
|
+
|
|
120
|
+
const { signIn, signOut, isAuthenticated } = useAuth();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Or use the auth client directly:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { ArchlastAuthClient } from "@archlast/client/auth";
|
|
127
|
+
|
|
128
|
+
const auth = new ArchlastAuthClient({ baseUrl: "http://localhost:4000" });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Storage
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const file = new File(["hello"], "hello.txt", { type: "text/plain" });
|
|
135
|
+
const uploaded = await client.storage.upload(file);
|
|
136
|
+
const list = await client.storage.list();
|
|
137
|
+
const { url } = await client.storage.presign(uploaded.id);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Admin APIs
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const profile = await client.admin.auth.getProfile();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## tRPC integration
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { createArchlastTRPCClient } from "@archlast/client/trpc";
|
|
150
|
+
import type { AppRouter } from "./_generated/rpc";
|
|
151
|
+
|
|
152
|
+
const trpc = createArchlastTRPCClient<AppRouter>({
|
|
153
|
+
baseUrl: "http://localhost:4000",
|
|
154
|
+
apiKey: "arch_example"
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Subpath exports
|
|
159
|
+
|
|
160
|
+
- `@archlast/client/react`
|
|
161
|
+
- `@archlast/client/trpc`
|
|
162
|
+
- `@archlast/client/auth`
|
|
163
|
+
- `@archlast/client/storage`
|
|
164
|
+
- `@archlast/client/admin`
|
|
165
|
+
|
|
166
|
+
## Troubleshooting
|
|
167
|
+
|
|
168
|
+
- WebSocket errors: use `ws://<host>/ws` and confirm CORS settings.
|
|
169
|
+
- Auth errors: ensure cookies or API key are configured.
|
|
170
|
+
- Storage uploads: use `File` or `Blob` for browser uploads.
|
|
171
|
+
|
|
172
|
+
## Publishing (maintainers)
|
|
173
|
+
|
|
174
|
+
See `docs/npm-publishing.md` for release and publish steps.
|