@archlast/client 0.1.0 → 0.1.2
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 +68 -720
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,579 +1,149 @@
|
|
|
1
1
|
# @archlast/client
|
|
2
2
|
|
|
3
|
-
The Archlast client SDK provides
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- [Installation](#installation)
|
|
8
|
-
- [Quick Start](#quick-start)
|
|
9
|
-
- [Client Configuration](#client-configuration)
|
|
10
|
-
- [React Hooks](#react-hooks)
|
|
11
|
-
- [useQuery](#usequery)
|
|
12
|
-
- [useMutation](#usemutation)
|
|
13
|
-
- [usePagination](#usepagination)
|
|
14
|
-
- [useUpload](#useupload)
|
|
15
|
-
- [useAuth](#useauth)
|
|
16
|
-
- [Direct Client Usage](#direct-client-usage)
|
|
17
|
-
- [Authentication](#authentication)
|
|
18
|
-
- [File Storage](#file-storage)
|
|
19
|
-
- [Admin APIs](#admin-apis)
|
|
20
|
-
- [tRPC Integration](#trpc-integration)
|
|
21
|
-
- [Subpath Exports](#subpath-exports)
|
|
22
|
-
- [TypeScript Support](#typescript-support)
|
|
23
|
-
- [Error Handling](#error-handling)
|
|
24
|
-
- [Advanced Patterns](#advanced-patterns)
|
|
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.
|
|
25
6
|
|
|
26
7
|
## Installation
|
|
27
8
|
|
|
28
9
|
```bash
|
|
29
10
|
npm install @archlast/client
|
|
30
11
|
|
|
31
|
-
#
|
|
12
|
+
# Other package managers
|
|
32
13
|
pnpm add @archlast/client
|
|
33
14
|
yarn add @archlast/client
|
|
34
15
|
bun add @archlast/client
|
|
35
16
|
```
|
|
36
17
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Install required peer dependencies in your application:
|
|
18
|
+
Peer dependencies:
|
|
40
19
|
|
|
41
20
|
```bash
|
|
42
21
|
npm install react react-dom @tanstack/react-query
|
|
43
22
|
```
|
|
44
23
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
For tRPC support:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm install @trpc/client @trpc/react-query
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Quick Start
|
|
54
|
-
|
|
55
|
-
### 1. Create the Client
|
|
24
|
+
## Quick start
|
|
56
25
|
|
|
57
26
|
```ts
|
|
58
27
|
import { ArchlastClient } from "@archlast/client";
|
|
28
|
+
import { ArchlastProvider, useQuery, useMutation } from "@archlast/client/react";
|
|
29
|
+
import { api } from "./_generated/api";
|
|
59
30
|
|
|
60
31
|
const client = new ArchlastClient(
|
|
61
|
-
"ws://localhost:4000/ws",
|
|
62
|
-
"http://localhost:4000",
|
|
63
|
-
"web"
|
|
32
|
+
"ws://localhost:4000/ws",
|
|
33
|
+
"http://localhost:4000",
|
|
34
|
+
"web"
|
|
64
35
|
);
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 2. Set Up the Provider
|
|
68
|
-
|
|
69
|
-
```tsx
|
|
70
|
-
import { ArchlastProvider } from "@archlast/client/react";
|
|
71
36
|
|
|
72
37
|
function App() {
|
|
73
38
|
return (
|
|
74
39
|
<ArchlastProvider client={client}>
|
|
75
|
-
<
|
|
40
|
+
<TodoList />
|
|
76
41
|
</ArchlastProvider>
|
|
77
42
|
);
|
|
78
43
|
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### 3. Use Hooks in Components
|
|
82
|
-
|
|
83
|
-
```tsx
|
|
84
|
-
import { useQuery, useMutation } from "@archlast/client/react";
|
|
85
|
-
import { api } from "./_generated/api";
|
|
86
44
|
|
|
87
45
|
function TodoList() {
|
|
88
|
-
// Subscribe to live updates
|
|
89
46
|
const tasks = useQuery(api.tasks.list, {});
|
|
90
|
-
|
|
91
|
-
// Mutation with automatic cache invalidation
|
|
92
47
|
const createTask = useMutation(api.tasks.create);
|
|
93
48
|
|
|
94
|
-
const handleAdd = async () => {
|
|
95
|
-
await createTask({ text: "New task" });
|
|
96
|
-
// Cache automatically invalidated, UI updates reactively
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
if (!tasks) return <div>Loading...</div>;
|
|
100
|
-
|
|
101
49
|
return (
|
|
102
50
|
<div>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</ul>
|
|
108
|
-
<button onClick={handleAdd}>Add Task</button>
|
|
51
|
+
<pre>{JSON.stringify(tasks, null, 2)}</pre>
|
|
52
|
+
<button onClick={() => createTask({ text: "New task" })}>
|
|
53
|
+
Add
|
|
54
|
+
</button>
|
|
109
55
|
</div>
|
|
110
56
|
);
|
|
111
57
|
}
|
|
112
58
|
```
|
|
113
59
|
|
|
114
|
-
|
|
60
|
+
The `api` object is generated by the CLI in `_generated/api.ts`.
|
|
115
61
|
|
|
116
|
-
|
|
62
|
+
## Client configuration
|
|
117
63
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
### Constructor Signature
|
|
64
|
+
Constructor signature:
|
|
121
65
|
|
|
122
66
|
```ts
|
|
123
|
-
new ArchlastClient(wsUrl, httpUrl
|
|
67
|
+
new ArchlastClient(wsUrl, httpUrl?, appId?, authUrl?, options?)
|
|
124
68
|
```
|
|
125
69
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| `httpUrl` | `string` | HTTP base URL (e.g., `http://localhost:4000`) |
|
|
132
|
-
| `appId` | `string` | Application identifier (e.g., `"web"`, `"mobile"`) |
|
|
133
|
-
| `authUrl` | `string?` | Optional custom auth URL (defaults to `httpUrl`) |
|
|
134
|
-
| `options` | `object?` | Additional configuration options |
|
|
135
|
-
|
|
136
|
-
### Options
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
interface ClientOptions {
|
|
140
|
-
// Auto-connect WebSocket on instantiation (default: true)
|
|
141
|
-
autoConnect?: boolean;
|
|
142
|
-
|
|
143
|
-
// API key for authentication (arch_ prefix)
|
|
144
|
-
apiKey?: string;
|
|
145
|
-
|
|
146
|
-
// Enable admin WebSocket subscriptions
|
|
147
|
-
isAdmin?: boolean;
|
|
148
|
-
|
|
149
|
-
// Better Auth session cookies for SSR
|
|
150
|
-
betterAuthCookies?: Record<string, string>;
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Full Example
|
|
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
|
|
155
75
|
|
|
156
76
|
```ts
|
|
157
77
|
const client = new ArchlastClient(
|
|
158
|
-
"
|
|
159
|
-
"
|
|
78
|
+
"ws://localhost:4000/ws",
|
|
79
|
+
"http://localhost:4000",
|
|
160
80
|
"web",
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
autoConnect: true,
|
|
164
|
-
apiKey: "arch_your_api_key",
|
|
165
|
-
isAdmin: false,
|
|
166
|
-
betterAuthCookies: {
|
|
167
|
-
"better-auth.session": "session_token_here"
|
|
168
|
-
}
|
|
169
|
-
}
|
|
81
|
+
undefined,
|
|
82
|
+
{ apiKey: "arch_example" }
|
|
170
83
|
);
|
|
171
|
-
```
|
|
172
84
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
// Rotate API key
|
|
177
|
-
client.setApiKey("arch_new_api_key");
|
|
178
|
-
|
|
179
|
-
// Update session cookies (for SSR)
|
|
180
|
-
client.setBetterAuthCookies({
|
|
181
|
-
"better-auth.session": "new_session_token"
|
|
182
|
-
});
|
|
85
|
+
client.setApiKey("arch_new_key");
|
|
86
|
+
client.setBetterAuthCookies({ "better-auth.session": "..." });
|
|
183
87
|
```
|
|
184
88
|
|
|
185
|
-
|
|
89
|
+
## React hooks
|
|
186
90
|
|
|
187
|
-
|
|
91
|
+
Available from `@archlast/client/react`:
|
|
188
92
|
|
|
189
|
-
|
|
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`
|
|
190
98
|
|
|
191
|
-
|
|
99
|
+
Example:
|
|
192
100
|
|
|
193
101
|
```ts
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
function TaskList() {
|
|
198
|
-
// Basic usage
|
|
199
|
-
const tasks = useQuery(api.tasks.list, {});
|
|
200
|
-
|
|
201
|
-
// With arguments
|
|
202
|
-
const task = useQuery(api.tasks.get, { id: "123" });
|
|
203
|
-
|
|
204
|
-
// With options
|
|
205
|
-
const { data, isLoading, error, refetch } = useQuery(
|
|
206
|
-
api.tasks.list,
|
|
207
|
-
{ completed: false },
|
|
208
|
-
{
|
|
209
|
-
// TanStack Query options
|
|
210
|
-
staleTime: 5000,
|
|
211
|
-
refetchOnWindowFocus: true,
|
|
212
|
-
}
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
if (isLoading) return <Spinner />;
|
|
216
|
-
if (error) return <Error message={error.message} />;
|
|
217
|
-
|
|
218
|
-
return <TaskGrid tasks={data} />;
|
|
219
|
-
}
|
|
102
|
+
const items = useQuery(api.items.list, { limit: 10 });
|
|
103
|
+
const createItem = useMutation(api.items.create);
|
|
220
104
|
```
|
|
221
105
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Execute mutations with automatic cache invalidation.
|
|
106
|
+
## Direct client usage
|
|
225
107
|
|
|
226
108
|
```ts
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
function CreateTaskForm() {
|
|
230
|
-
const createTask = useMutation(api.tasks.create);
|
|
231
|
-
|
|
232
|
-
const handleSubmit = async (e: FormEvent) => {
|
|
233
|
-
e.preventDefault();
|
|
234
|
-
const formData = new FormData(e.target as HTMLFormElement);
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
const task = await createTask({
|
|
238
|
-
text: formData.get("text") as string,
|
|
239
|
-
priority: "medium"
|
|
240
|
-
});
|
|
241
|
-
console.log("Created:", task._id);
|
|
242
|
-
} catch (error) {
|
|
243
|
-
console.error("Failed:", error);
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<form onSubmit={handleSubmit}>
|
|
249
|
-
<input name="text" required />
|
|
250
|
-
<button type="submit">Create</button>
|
|
251
|
-
</form>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
109
|
+
const data = await client.fetch("tasks.get", { id: "123" });
|
|
110
|
+
const updated = await client.mutate("tasks.update", { id: "123", text: "Hi" });
|
|
254
111
|
```
|
|
255
112
|
|
|
256
|
-
### usePagination
|
|
257
|
-
|
|
258
|
-
Handle cursor-based pagination with infinite scroll support.
|
|
259
|
-
|
|
260
|
-
```ts
|
|
261
|
-
import { usePagination } from "@archlast/client/react";
|
|
262
|
-
|
|
263
|
-
function InfiniteTaskList() {
|
|
264
|
-
const {
|
|
265
|
-
items,
|
|
266
|
-
isLoading,
|
|
267
|
-
hasMore,
|
|
268
|
-
loadMore,
|
|
269
|
-
error
|
|
270
|
-
} = usePagination(api.tasks.listPaginated, {
|
|
271
|
-
limit: 20,
|
|
272
|
-
filter: { completed: false }
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
return (
|
|
276
|
-
<div>
|
|
277
|
-
<ul>
|
|
278
|
-
{items.map(task => (
|
|
279
|
-
<li key={task._id}>{task.text}</li>
|
|
280
|
-
))}
|
|
281
|
-
</ul>
|
|
282
|
-
|
|
283
|
-
{hasMore && (
|
|
284
|
-
<button
|
|
285
|
-
onClick={loadMore}
|
|
286
|
-
disabled={isLoading}
|
|
287
|
-
>
|
|
288
|
-
{isLoading ? "Loading..." : "Load More"}
|
|
289
|
-
</button>
|
|
290
|
-
)}
|
|
291
|
-
</div>
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
**Server-side query must return:**
|
|
297
|
-
|
|
298
|
-
```ts
|
|
299
|
-
interface PaginatedResult<T> {
|
|
300
|
-
items: T[];
|
|
301
|
-
continueCursor: string | null;
|
|
302
|
-
isDone: boolean;
|
|
303
|
-
}
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
### useUpload
|
|
307
|
-
|
|
308
|
-
Upload files with progress tracking.
|
|
309
|
-
|
|
310
|
-
```ts
|
|
311
|
-
import { useUpload } from "@archlast/client/react";
|
|
312
|
-
|
|
313
|
-
function FileUploader() {
|
|
314
|
-
const {
|
|
315
|
-
upload,
|
|
316
|
-
progress,
|
|
317
|
-
isUploading,
|
|
318
|
-
storageId,
|
|
319
|
-
error
|
|
320
|
-
} = useUpload();
|
|
321
|
-
|
|
322
|
-
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
|
323
|
-
const file = e.target.files?.[0];
|
|
324
|
-
if (!file) return;
|
|
325
|
-
|
|
326
|
-
const id = await upload(file, "images");
|
|
327
|
-
console.log("Uploaded with ID:", id);
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
return (
|
|
331
|
-
<div>
|
|
332
|
-
<input type="file" onChange={handleFileChange} disabled={isUploading} />
|
|
333
|
-
|
|
334
|
-
{isUploading && (
|
|
335
|
-
<div>
|
|
336
|
-
<progress value={progress} max={100} />
|
|
337
|
-
<span>{progress}%</span>
|
|
338
|
-
</div>
|
|
339
|
-
)}
|
|
340
|
-
|
|
341
|
-
{storageId && <p>Uploaded: {storageId}</p>}
|
|
342
|
-
{error && <p className="error">{error.message}</p>}
|
|
343
|
-
</div>
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### useAuth
|
|
349
|
-
|
|
350
|
-
Authentication state and actions.
|
|
351
|
-
|
|
352
|
-
```ts
|
|
353
|
-
import { useAuth } from "@archlast/client/react";
|
|
354
|
-
|
|
355
|
-
function AuthButton() {
|
|
356
|
-
const {
|
|
357
|
-
signIn,
|
|
358
|
-
signOut,
|
|
359
|
-
isAuthenticated,
|
|
360
|
-
user,
|
|
361
|
-
isLoading
|
|
362
|
-
} = useAuth();
|
|
363
|
-
|
|
364
|
-
if (isLoading) return <Spinner />;
|
|
365
|
-
|
|
366
|
-
if (isAuthenticated) {
|
|
367
|
-
return (
|
|
368
|
-
<div>
|
|
369
|
-
<span>Welcome, {user?.name}</span>
|
|
370
|
-
<button onClick={signOut}>Sign Out</button>
|
|
371
|
-
</div>
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return (
|
|
376
|
-
<button onClick={() => signIn({ provider: "google" })}>
|
|
377
|
-
Sign in with Google
|
|
378
|
-
</button>
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## Direct Client Usage
|
|
386
|
-
|
|
387
|
-
Use the client directly without React for Node.js, scripts, or non-React frameworks.
|
|
388
|
-
|
|
389
|
-
### Fetch (Query)
|
|
390
|
-
|
|
391
|
-
```ts
|
|
392
|
-
// Fetch a single query
|
|
393
|
-
const tasks = await client.fetch("tasks.list", {});
|
|
394
|
-
|
|
395
|
-
// With arguments
|
|
396
|
-
const task = await client.fetch("tasks.get", { id: "123" });
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### Mutate
|
|
400
|
-
|
|
401
|
-
```ts
|
|
402
|
-
// Execute a mutation
|
|
403
|
-
const created = await client.mutate("tasks.create", {
|
|
404
|
-
text: "New task",
|
|
405
|
-
priority: "high"
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
// Update
|
|
409
|
-
await client.mutate("tasks.update", {
|
|
410
|
-
id: "123",
|
|
411
|
-
completed: true
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Delete
|
|
415
|
-
await client.mutate("tasks.delete", { id: "123" });
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
### Subscribe
|
|
419
|
-
|
|
420
|
-
```ts
|
|
421
|
-
// Manual WebSocket subscription
|
|
422
|
-
const unsubscribe = client.subscribe("tasks.list", {}, (data) => {
|
|
423
|
-
console.log("Tasks updated:", data);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
// Later: unsubscribe
|
|
427
|
-
unsubscribe();
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
113
|
## Authentication
|
|
433
114
|
|
|
434
|
-
|
|
115
|
+
Auth uses Better Auth endpoints under `/api/auth/*`.
|
|
435
116
|
|
|
436
117
|
```ts
|
|
437
|
-
import { ArchlastAuthClient } from "@archlast/client/auth";
|
|
438
|
-
|
|
439
|
-
const auth = new ArchlastAuthClient({
|
|
440
|
-
baseUrl: "http://localhost:4000"
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// Email/password sign up
|
|
444
|
-
await auth.signUp({
|
|
445
|
-
email: "user@example.com",
|
|
446
|
-
password: "secure_password",
|
|
447
|
-
name: "John Doe"
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// Email/password sign in
|
|
451
|
-
await auth.signIn({
|
|
452
|
-
email: "user@example.com",
|
|
453
|
-
password: "secure_password"
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// OAuth sign in
|
|
457
|
-
await auth.signIn({ provider: "google" });
|
|
458
|
-
await auth.signIn({ provider: "github" });
|
|
459
|
-
|
|
460
|
-
// Get current session
|
|
461
|
-
const session = await auth.getSession();
|
|
462
|
-
|
|
463
|
-
// Sign out
|
|
464
|
-
await auth.signOut();
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### Auth State in Components
|
|
468
|
-
|
|
469
|
-
```tsx
|
|
470
118
|
import { useAuth } from "@archlast/client/react";
|
|
471
119
|
|
|
472
|
-
|
|
473
|
-
const { isAuthenticated, isLoading } = useAuth();
|
|
474
|
-
|
|
475
|
-
if (isLoading) return <LoadingScreen />;
|
|
476
|
-
if (!isAuthenticated) return <Navigate to="/login" />;
|
|
477
|
-
|
|
478
|
-
return children;
|
|
479
|
-
}
|
|
120
|
+
const { signIn, signOut, isAuthenticated } = useAuth();
|
|
480
121
|
```
|
|
481
122
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
## File Storage
|
|
485
|
-
|
|
486
|
-
### Storage Client
|
|
123
|
+
Or use the auth client directly:
|
|
487
124
|
|
|
488
125
|
```ts
|
|
489
|
-
|
|
490
|
-
const storage = client.storage;
|
|
491
|
-
|
|
492
|
-
// Or import directly
|
|
493
|
-
import { StorageClient } from "@archlast/client/storage";
|
|
494
|
-
const storage = new StorageClient(httpUrl, getAuthHeaders);
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
### Upload Files
|
|
498
|
-
|
|
499
|
-
```ts
|
|
500
|
-
const file = new File(["Hello, World!"], "hello.txt", { type: "text/plain" });
|
|
501
|
-
|
|
502
|
-
// Basic upload
|
|
503
|
-
const result = await client.storage.upload(file);
|
|
504
|
-
console.log("Storage ID:", result.id);
|
|
505
|
-
console.log("URL:", result.url);
|
|
506
|
-
|
|
507
|
-
// Upload to specific bucket
|
|
508
|
-
const imageResult = await client.storage.upload(imageFile, "images");
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### List Files
|
|
512
|
-
|
|
513
|
-
```ts
|
|
514
|
-
// List all files
|
|
515
|
-
const files = await client.storage.list();
|
|
516
|
-
|
|
517
|
-
// List files in bucket
|
|
518
|
-
const images = await client.storage.list("images");
|
|
519
|
-
|
|
520
|
-
// With pagination
|
|
521
|
-
const page1 = await client.storage.list("images", { limit: 20 });
|
|
522
|
-
const page2 = await client.storage.list("images", {
|
|
523
|
-
limit: 20,
|
|
524
|
-
cursor: page1.nextCursor
|
|
525
|
-
});
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
### Get Presigned URLs
|
|
529
|
-
|
|
530
|
-
```ts
|
|
531
|
-
// Get temporary signed URL for download
|
|
532
|
-
const { url, expiresAt } = await client.storage.presign(storageId);
|
|
126
|
+
import { ArchlastAuthClient } from "@archlast/client/auth";
|
|
533
127
|
|
|
534
|
-
|
|
535
|
-
<img src={url} alt="Uploaded file" />
|
|
128
|
+
const auth = new ArchlastAuthClient({ baseUrl: "http://localhost:4000" });
|
|
536
129
|
```
|
|
537
130
|
|
|
538
|
-
|
|
131
|
+
## Storage
|
|
539
132
|
|
|
540
133
|
```ts
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
console.log(metadata.createdAt); // Date
|
|
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);
|
|
546
138
|
```
|
|
547
139
|
|
|
548
|
-
---
|
|
549
|
-
|
|
550
140
|
## Admin APIs
|
|
551
141
|
|
|
552
|
-
Access admin functionality (requires admin privileges).
|
|
553
|
-
|
|
554
142
|
```ts
|
|
555
|
-
// Auth operations
|
|
556
143
|
const profile = await client.admin.auth.getProfile();
|
|
557
|
-
const users = await client.admin.auth.listUsers();
|
|
558
|
-
await client.admin.auth.updateUser(userId, { role: "admin" });
|
|
559
|
-
|
|
560
|
-
// API key management
|
|
561
|
-
const keys = await client.admin.apiKeys.list();
|
|
562
|
-
const newKey = await client.admin.apiKeys.create({ name: "CI/CD" });
|
|
563
|
-
await client.admin.apiKeys.revoke(keyId);
|
|
564
|
-
|
|
565
|
-
// Data operations
|
|
566
|
-
const collections = await client.admin.data.listCollections();
|
|
567
|
-
const docs = await client.admin.data.query("tasks", { limit: 100 });
|
|
568
144
|
```
|
|
569
145
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
## tRPC Integration
|
|
573
|
-
|
|
574
|
-
For type-safe RPC with full TypeScript inference.
|
|
575
|
-
|
|
576
|
-
### Setup
|
|
146
|
+
## tRPC integration
|
|
577
147
|
|
|
578
148
|
```ts
|
|
579
149
|
import { createArchlastTRPCClient } from "@archlast/client/trpc";
|
|
@@ -581,246 +151,24 @@ import type { AppRouter } from "./_generated/rpc";
|
|
|
581
151
|
|
|
582
152
|
const trpc = createArchlastTRPCClient<AppRouter>({
|
|
583
153
|
baseUrl: "http://localhost:4000",
|
|
584
|
-
apiKey: "
|
|
154
|
+
apiKey: "arch_example"
|
|
585
155
|
});
|
|
586
156
|
```
|
|
587
157
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
```ts
|
|
591
|
-
// Queries
|
|
592
|
-
const tasks = await trpc.tasks.list.query({});
|
|
593
|
-
const task = await trpc.tasks.get.query({ id: "123" });
|
|
594
|
-
|
|
595
|
-
// Mutations
|
|
596
|
-
const created = await trpc.tasks.create.mutate({ text: "New task" });
|
|
597
|
-
await trpc.tasks.update.mutate({ id: "123", completed: true });
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
### React Query Integration
|
|
601
|
-
|
|
602
|
-
```tsx
|
|
603
|
-
import { createTRPCReact } from "@trpc/react-query";
|
|
604
|
-
import type { AppRouter } from "./_generated/rpc";
|
|
605
|
-
|
|
606
|
-
export const trpc = createTRPCReact<AppRouter>();
|
|
607
|
-
|
|
608
|
-
// In component
|
|
609
|
-
function TaskList() {
|
|
610
|
-
const { data, isLoading } = trpc.tasks.list.useQuery({});
|
|
611
|
-
const createTask = trpc.tasks.create.useMutation();
|
|
612
|
-
|
|
613
|
-
// ...
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
---
|
|
618
|
-
|
|
619
|
-
## Subpath Exports
|
|
620
|
-
|
|
621
|
-
Import specific functionality to reduce bundle size:
|
|
622
|
-
|
|
623
|
-
| Import | Description |
|
|
624
|
-
|--------|-------------|
|
|
625
|
-
| `@archlast/client` | Core `ArchlastClient` class |
|
|
626
|
-
| `@archlast/client/react` | React hooks and provider |
|
|
627
|
-
| `@archlast/client/auth` | Authentication client |
|
|
628
|
-
| `@archlast/client/storage` | File storage client |
|
|
629
|
-
| `@archlast/client/admin` | Admin API client |
|
|
630
|
-
| `@archlast/client/trpc` | tRPC client factory |
|
|
631
|
-
|
|
632
|
-
```ts
|
|
633
|
-
// Minimal import for core functionality
|
|
634
|
-
import { ArchlastClient } from "@archlast/client";
|
|
635
|
-
|
|
636
|
-
// React-specific imports
|
|
637
|
-
import {
|
|
638
|
-
ArchlastProvider,
|
|
639
|
-
useQuery,
|
|
640
|
-
useMutation,
|
|
641
|
-
usePagination,
|
|
642
|
-
useUpload,
|
|
643
|
-
useAuth
|
|
644
|
-
} from "@archlast/client/react";
|
|
645
|
-
|
|
646
|
-
// Auth-only import
|
|
647
|
-
import { ArchlastAuthClient } from "@archlast/client/auth";
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
---
|
|
651
|
-
|
|
652
|
-
## TypeScript Support
|
|
653
|
-
|
|
654
|
-
The client is fully typed. Types are generated by the CLI based on your schema.
|
|
655
|
-
|
|
656
|
-
### Generated Types
|
|
657
|
-
|
|
658
|
-
```ts
|
|
659
|
-
// _generated/api.ts
|
|
660
|
-
export const api = {
|
|
661
|
-
tasks: {
|
|
662
|
-
list: { _path: "tasks.list", _returnType: {} as Task[] },
|
|
663
|
-
get: { _path: "tasks.get", _returnType: {} as Task | null },
|
|
664
|
-
create: { _path: "tasks.create", _returnType: {} as Task },
|
|
665
|
-
// ...
|
|
666
|
-
}
|
|
667
|
-
};
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### Usage with Types
|
|
671
|
-
|
|
672
|
-
```ts
|
|
673
|
-
import { useQuery } from "@archlast/client/react";
|
|
674
|
-
import { api } from "./_generated/api";
|
|
675
|
-
|
|
676
|
-
// TypeScript knows `tasks` is Task[]
|
|
677
|
-
const tasks = useQuery(api.tasks.list, {});
|
|
678
|
-
|
|
679
|
-
// TypeScript knows `task` is Task | null
|
|
680
|
-
const task = useQuery(api.tasks.get, { id: "123" });
|
|
681
|
-
```
|
|
682
|
-
|
|
683
|
-
---
|
|
684
|
-
|
|
685
|
-
## Error Handling
|
|
686
|
-
|
|
687
|
-
### Query Errors
|
|
688
|
-
|
|
689
|
-
```tsx
|
|
690
|
-
function TaskList() {
|
|
691
|
-
const { data, error, isError } = useQuery(api.tasks.list, {});
|
|
692
|
-
|
|
693
|
-
if (isError) {
|
|
694
|
-
return (
|
|
695
|
-
<div className="error">
|
|
696
|
-
<h3>Failed to load tasks</h3>
|
|
697
|
-
<p>{error.message}</p>
|
|
698
|
-
<button onClick={() => refetch()}>Retry</button>
|
|
699
|
-
</div>
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
return <TaskGrid tasks={data} />;
|
|
704
|
-
}
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
### Mutation Errors
|
|
708
|
-
|
|
709
|
-
```ts
|
|
710
|
-
const createTask = useMutation(api.tasks.create);
|
|
711
|
-
|
|
712
|
-
try {
|
|
713
|
-
await createTask({ text: "" }); // Invalid
|
|
714
|
-
} catch (error) {
|
|
715
|
-
if (error instanceof ValidationError) {
|
|
716
|
-
console.log("Validation failed:", error.issues);
|
|
717
|
-
} else if (error instanceof AuthenticationError) {
|
|
718
|
-
console.log("Not authenticated");
|
|
719
|
-
} else {
|
|
720
|
-
console.log("Unknown error:", error);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### Global Error Boundary
|
|
726
|
-
|
|
727
|
-
```tsx
|
|
728
|
-
import { QueryErrorResetBoundary } from "@tanstack/react-query";
|
|
729
|
-
import { ErrorBoundary } from "react-error-boundary";
|
|
730
|
-
|
|
731
|
-
function App() {
|
|
732
|
-
return (
|
|
733
|
-
<QueryErrorResetBoundary>
|
|
734
|
-
{({ reset }) => (
|
|
735
|
-
<ErrorBoundary
|
|
736
|
-
onReset={reset}
|
|
737
|
-
fallbackRender={({ error, resetErrorBoundary }) => (
|
|
738
|
-
<div>
|
|
739
|
-
<p>Something went wrong: {error.message}</p>
|
|
740
|
-
<button onClick={resetErrorBoundary}>Try again</button>
|
|
741
|
-
</div>
|
|
742
|
-
)}
|
|
743
|
-
>
|
|
744
|
-
<ArchlastProvider client={client}>
|
|
745
|
-
<YourApp />
|
|
746
|
-
</ArchlastProvider>
|
|
747
|
-
</ErrorBoundary>
|
|
748
|
-
)}
|
|
749
|
-
</QueryErrorResetBoundary>
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
---
|
|
755
|
-
|
|
756
|
-
## Advanced Patterns
|
|
757
|
-
|
|
758
|
-
### Optimistic Updates
|
|
759
|
-
|
|
760
|
-
```ts
|
|
761
|
-
const queryClient = useQueryClient();
|
|
762
|
-
|
|
763
|
-
const updateTask = useMutation(api.tasks.update, {
|
|
764
|
-
onMutate: async (newData) => {
|
|
765
|
-
// Cancel outgoing refetches
|
|
766
|
-
await queryClient.cancelQueries(["tasks.list"]);
|
|
767
|
-
|
|
768
|
-
// Snapshot previous value
|
|
769
|
-
const previous = queryClient.getQueryData(["tasks.list"]);
|
|
770
|
-
|
|
771
|
-
// Optimistically update
|
|
772
|
-
queryClient.setQueryData(["tasks.list"], (old: Task[]) =>
|
|
773
|
-
old.map(t => t._id === newData.id ? { ...t, ...newData } : t)
|
|
774
|
-
);
|
|
775
|
-
|
|
776
|
-
return { previous };
|
|
777
|
-
},
|
|
778
|
-
onError: (err, newData, context) => {
|
|
779
|
-
// Rollback on error
|
|
780
|
-
queryClient.setQueryData(["tasks.list"], context.previous);
|
|
781
|
-
}
|
|
782
|
-
});
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### SSR / Server Components
|
|
786
|
-
|
|
787
|
-
```ts
|
|
788
|
-
// For Next.js SSR
|
|
789
|
-
const client = new ArchlastClient(
|
|
790
|
-
wsUrl,
|
|
791
|
-
httpUrl,
|
|
792
|
-
"web",
|
|
793
|
-
undefined,
|
|
794
|
-
{
|
|
795
|
-
autoConnect: false, // Don't connect on server
|
|
796
|
-
betterAuthCookies: cookies() // Pass request cookies
|
|
797
|
-
}
|
|
798
|
-
);
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
### Multiple Clients
|
|
802
|
-
|
|
803
|
-
```tsx
|
|
804
|
-
const mainClient = new ArchlastClient(mainWsUrl, mainHttpUrl, "main");
|
|
805
|
-
const analyticsClient = new ArchlastClient(analyticsWsUrl, analyticsHttpUrl, "analytics");
|
|
806
|
-
|
|
807
|
-
function App() {
|
|
808
|
-
return (
|
|
809
|
-
<ArchlastProvider client={mainClient}>
|
|
810
|
-
<ArchlastProvider client={analyticsClient} contextKey="analytics">
|
|
811
|
-
<YourApp />
|
|
812
|
-
</ArchlastProvider>
|
|
813
|
-
</ArchlastProvider>
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
```
|
|
158
|
+
## Subpath exports
|
|
817
159
|
|
|
818
|
-
|
|
160
|
+
- `@archlast/client/react`
|
|
161
|
+
- `@archlast/client/trpc`
|
|
162
|
+
- `@archlast/client/auth`
|
|
163
|
+
- `@archlast/client/storage`
|
|
164
|
+
- `@archlast/client/admin`
|
|
819
165
|
|
|
820
|
-
##
|
|
166
|
+
## Troubleshooting
|
|
821
167
|
|
|
822
|
-
|
|
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.
|
|
823
171
|
|
|
824
|
-
##
|
|
172
|
+
## Publishing (maintainers)
|
|
825
173
|
|
|
826
|
-
|
|
174
|
+
See `docs/npm-publishing.md` for release and publish steps.
|