@archlast/client 0.0.1 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +783 -17
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,50 +1,579 @@
1
1
  # @archlast/client
2
2
 
3
- Archlast client SDK for React hooks, auth helpers, storage utilities, and tRPC client access.
3
+ The Archlast client SDK provides a complete solution for building reactive frontend applications with Archlast backends. It includes WebSocket-based real-time queries, React hooks with automatic cache management, authentication helpers, file storage utilities, admin APIs, and optional tRPC integration.
4
4
 
5
- ## Install
5
+ ## Table of Contents
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)
25
+
26
+ ## Installation
6
27
 
7
28
  ```bash
8
29
  npm install @archlast/client
30
+
31
+ # Or with other package managers
32
+ pnpm add @archlast/client
33
+ yarn add @archlast/client
34
+ bun add @archlast/client
9
35
  ```
10
36
 
11
- Peer dependencies (install in your app):
37
+ ### Peer Dependencies
38
+
39
+ Install required peer dependencies in your application:
12
40
 
13
41
  ```bash
14
42
  npm install react react-dom @tanstack/react-query
15
43
  ```
16
44
 
17
- ## Usage
45
+ ### Optional Dependencies
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
18
56
 
19
57
  ```ts
20
- import { ArchlastClient, ArchlastProvider, useQuery } from "@archlast/client";
21
- import { api } from "./_generated/api";
58
+ import { ArchlastClient } from "@archlast/client";
59
+
60
+ const client = new ArchlastClient(
61
+ "ws://localhost:4000/ws", // WebSocket URL
62
+ "http://localhost:4000", // HTTP URL
63
+ "web" // App ID
64
+ );
65
+ ```
66
+
67
+ ### 2. Set Up the Provider
22
68
 
23
- const client = new ArchlastClient({ baseUrl: "http://localhost:4000" });
69
+ ```tsx
70
+ import { ArchlastProvider } from "@archlast/client/react";
24
71
 
25
72
  function App() {
26
73
  return (
27
74
  <ArchlastProvider client={client}>
28
- <TodoList />
75
+ <YourApp />
29
76
  </ArchlastProvider>
30
77
  );
31
78
  }
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";
32
86
 
33
87
  function TodoList() {
88
+ // Subscribe to live updates
89
+ const tasks = useQuery(api.tasks.list, {});
90
+
91
+ // Mutation with automatic cache invalidation
92
+ const createTask = useMutation(api.tasks.create);
93
+
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
+ return (
102
+ <div>
103
+ <ul>
104
+ {tasks.map(task => (
105
+ <li key={task._id}>{task.text}</li>
106
+ ))}
107
+ </ul>
108
+ <button onClick={handleAdd}>Add Task</button>
109
+ </div>
110
+ );
111
+ }
112
+ ```
113
+
114
+ > **Note:** The `api` object is auto-generated by the CLI in `_generated/api.ts`. Run `archlast dev` or `archlast build` to generate it.
115
+
116
+ ---
117
+
118
+ ## Client Configuration
119
+
120
+ ### Constructor Signature
121
+
122
+ ```ts
123
+ new ArchlastClient(wsUrl, httpUrl, appId, authUrl?, options?)
124
+ ```
125
+
126
+ ### Parameters
127
+
128
+ | Parameter | Type | Description |
129
+ |-----------|------|-------------|
130
+ | `wsUrl` | `string` | WebSocket endpoint (e.g., `ws://localhost:4000/ws`) |
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
155
+
156
+ ```ts
157
+ const client = new ArchlastClient(
158
+ "wss://api.myapp.com/ws",
159
+ "https://api.myapp.com",
160
+ "web",
161
+ "https://auth.myapp.com", // Custom auth URL
162
+ {
163
+ autoConnect: true,
164
+ apiKey: "arch_your_api_key",
165
+ isAdmin: false,
166
+ betterAuthCookies: {
167
+ "better-auth.session": "session_token_here"
168
+ }
169
+ }
170
+ );
171
+ ```
172
+
173
+ ### Runtime Credential Updates
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
+ });
183
+ ```
184
+
185
+ ---
186
+
187
+ ## React Hooks
188
+
189
+ ### useQuery
190
+
191
+ Subscribe to real-time query updates with automatic caching via TanStack Query.
192
+
193
+ ```ts
194
+ import { useQuery } from "@archlast/client/react";
195
+ import { api } from "./_generated/api";
196
+
197
+ function TaskList() {
198
+ // Basic usage
34
199
  const tasks = useQuery(api.tasks.list, {});
35
- return <pre>{JSON.stringify(tasks, null, 2)}</pre>;
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
+ }
220
+ ```
221
+
222
+ ### useMutation
223
+
224
+ Execute mutations with automatic cache invalidation.
225
+
226
+ ```ts
227
+ import { useMutation } from "@archlast/client/react";
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
+ }
254
+ ```
255
+
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
+ ## Authentication
433
+
434
+ ### Using the Auth Client
435
+
436
+ ```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
+ import { useAuth } from "@archlast/client/react";
471
+
472
+ function ProtectedRoute({ children }) {
473
+ const { isAuthenticated, isLoading } = useAuth();
474
+
475
+ if (isLoading) return <LoadingScreen />;
476
+ if (!isAuthenticated) return <Navigate to="/login" />;
477
+
478
+ return children;
36
479
  }
37
480
  ```
38
481
 
39
- ## Subpath exports
482
+ ---
483
+
484
+ ## File Storage
485
+
486
+ ### Storage Client
487
+
488
+ ```ts
489
+ // Access via main client
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);
533
+
534
+ // Use in img tag
535
+ <img src={url} alt="Uploaded file" />
536
+ ```
537
+
538
+ ### Get Metadata
539
+
540
+ ```ts
541
+ const metadata = await client.storage.getMetadata(storageId);
542
+ console.log(metadata.filename); // "hello.txt"
543
+ console.log(metadata.contentType); // "text/plain"
544
+ console.log(metadata.size); // 13
545
+ console.log(metadata.createdAt); // Date
546
+ ```
547
+
548
+ ---
549
+
550
+ ## Admin APIs
551
+
552
+ Access admin functionality (requires admin privileges).
553
+
554
+ ```ts
555
+ // Auth operations
556
+ 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
+ ```
569
+
570
+ ---
571
+
572
+ ## tRPC Integration
40
573
 
41
- - `@archlast/client/react`
42
- - `@archlast/client/trpc`
43
- - `@archlast/client/auth`
44
- - `@archlast/client/storage`
45
- - `@archlast/client/admin`
574
+ For type-safe RPC with full TypeScript inference.
46
575
 
47
- ## tRPC client
576
+ ### Setup
48
577
 
49
578
  ```ts
50
579
  import { createArchlastTRPCClient } from "@archlast/client/trpc";
@@ -52,9 +581,246 @@ import type { AppRouter } from "./_generated/rpc";
52
581
 
53
582
  const trpc = createArchlastTRPCClient<AppRouter>({
54
583
  baseUrl: "http://localhost:4000",
584
+ apiKey: "arch_your_api_key"
585
+ });
586
+ ```
587
+
588
+ ### Usage
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
+ }
55
782
  });
56
783
  ```
57
784
 
58
- ## Publishing (maintainers)
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
+ ```
817
+
818
+ ---
819
+
820
+ ## Publishing (Maintainers)
59
821
 
60
822
  See `docs/npm-publishing.md` for release and publish steps.
823
+
824
+ ## License
825
+
826
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archlast/client",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Archlast client SDK for React and API access",
5
5
  "license": "MIT",
6
6
  "repository": {