@archlast/client 0.0.1

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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/admin/index.cjs +93 -0
  4. package/dist/admin/index.cjs.map +1 -0
  5. package/dist/admin/index.d.cts +51 -0
  6. package/dist/admin/index.d.ts +51 -0
  7. package/dist/admin/index.js +59 -0
  8. package/dist/admin/index.js.map +1 -0
  9. package/dist/auth/index.cjs +174 -0
  10. package/dist/auth/index.cjs.map +1 -0
  11. package/dist/auth/index.d.cts +128 -0
  12. package/dist/auth/index.d.ts +128 -0
  13. package/dist/auth/index.js +141 -0
  14. package/dist/auth/index.js.map +1 -0
  15. package/dist/client.cjs +677 -0
  16. package/dist/client.cjs.map +1 -0
  17. package/dist/client.d.cts +84 -0
  18. package/dist/client.d.ts +84 -0
  19. package/dist/client.js +642 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/function-reference.cjs +50 -0
  22. package/dist/function-reference.cjs.map +1 -0
  23. package/dist/function-reference.d.cts +22 -0
  24. package/dist/function-reference.d.ts +22 -0
  25. package/dist/function-reference.js +24 -0
  26. package/dist/function-reference.js.map +1 -0
  27. package/dist/index.cjs +1163 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +12 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.js +1111 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/react.cjs +455 -0
  34. package/dist/react.cjs.map +1 -0
  35. package/dist/react.d.cts +137 -0
  36. package/dist/react.d.ts +137 -0
  37. package/dist/react.js +410 -0
  38. package/dist/react.js.map +1 -0
  39. package/dist/storage/index.cjs +150 -0
  40. package/dist/storage/index.cjs.map +1 -0
  41. package/dist/storage/index.d.cts +59 -0
  42. package/dist/storage/index.d.ts +59 -0
  43. package/dist/storage/index.js +117 -0
  44. package/dist/storage/index.js.map +1 -0
  45. package/dist/trpc.cjs +66 -0
  46. package/dist/trpc.cjs.map +1 -0
  47. package/dist/trpc.d.cts +59 -0
  48. package/dist/trpc.d.ts +59 -0
  49. package/dist/trpc.js +41 -0
  50. package/dist/trpc.js.map +1 -0
  51. package/package.json +90 -0
package/dist/index.js ADDED
@@ -0,0 +1,1111 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/auth/index.ts
6
+ import axios from "axios";
7
+ function resolveBaseUrl(explicit) {
8
+ if (explicit && explicit.trim()) return explicit.replace(/\/+$/, "");
9
+ if (typeof window !== "undefined" && window.location?.origin) return window.location.origin;
10
+ return "";
11
+ }
12
+ var ArchlastAuthClient = class {
13
+ constructor(options = {}) {
14
+ __publicField(this, "baseUrl");
15
+ __publicField(this, "axios");
16
+ __publicField(this, "appId");
17
+ __publicField(this, "apiKey");
18
+ this.baseUrl = resolveBaseUrl(options.baseUrl);
19
+ this.appId = options.appId;
20
+ this.apiKey = options.apiKey;
21
+ this.axios = axios.create({
22
+ baseURL: this.baseUrl,
23
+ withCredentials: !options.apiKey,
24
+ // Only use cookies if no API key
25
+ headers: {
26
+ "content-type": "application/json",
27
+ ...this.appId ? { "x-archlast-app-id": this.appId } : {},
28
+ ...options.apiKey ? { "x-api-key": options.apiKey } : {}
29
+ }
30
+ });
31
+ }
32
+ /**
33
+ * Get current authentication state
34
+ * Uses Better-Auth's getSession endpoint
35
+ */
36
+ async getState() {
37
+ try {
38
+ const response = await this.axios.get("/api/auth/get-session");
39
+ const { user, session } = response.data;
40
+ return {
41
+ user,
42
+ session,
43
+ isAuthenticated: !!user
44
+ };
45
+ } catch (error) {
46
+ return {
47
+ user: null,
48
+ session: null,
49
+ isAuthenticated: false
50
+ };
51
+ }
52
+ }
53
+ /**
54
+ * Sign up new user
55
+ * Uses Better-Auth's email signUp endpoint
56
+ */
57
+ async signUp(input) {
58
+ const response = await this.axios.post("/api/auth/sign-up/email", {
59
+ email: input.email,
60
+ password: input.password,
61
+ name: input.name,
62
+ username: input.username
63
+ });
64
+ return {
65
+ user: response.data.user,
66
+ session: null,
67
+ // Session is managed via cookies
68
+ isAuthenticated: !!response.data.user
69
+ };
70
+ }
71
+ /**
72
+ * Sign in with email/username and password
73
+ * Uses Better-Auth's credential sign-in endpoint
74
+ */
75
+ async signIn(input) {
76
+ if (!input.email && !input.username) {
77
+ throw new Error("Either email or username is required");
78
+ }
79
+ const response = await this.axios.post("/api/auth/sign-in/email", {
80
+ email: input.email,
81
+ username: input.username,
82
+ password: input.password
83
+ });
84
+ return {
85
+ user: response.data.user,
86
+ session: null,
87
+ // Session is managed via cookies
88
+ isAuthenticated: !!response.data.user
89
+ };
90
+ }
91
+ /**
92
+ * Sign out and revoke session
93
+ * Uses Better-Auth's sign-out endpoint
94
+ */
95
+ async signOut() {
96
+ const response = await this.axios.post(
97
+ "/api/auth/sign-out",
98
+ {},
99
+ { withCredentials: true }
100
+ );
101
+ return response.data;
102
+ }
103
+ /**
104
+ * Request password reset email
105
+ * Uses Better-Auth's password reset flow
106
+ */
107
+ async requestPasswordReset(email, callbackURL) {
108
+ const response = await this.axios.post("/api/auth/forgot-password", {
109
+ email,
110
+ redirectTo: callbackURL
111
+ });
112
+ return response.data;
113
+ }
114
+ /**
115
+ * Reset password with token
116
+ * Uses Better-Auth's reset password endpoint
117
+ */
118
+ async resetPassword(token, password) {
119
+ const response = await this.axios.post("/api/auth/reset-password", {
120
+ token,
121
+ password
122
+ });
123
+ return response.data;
124
+ }
125
+ /**
126
+ * Verify email with token
127
+ * Uses Better-Auth's email verification endpoint
128
+ */
129
+ async verifyEmail(token) {
130
+ const response = await this.axios.post("/api/auth/verify-email", {
131
+ token
132
+ });
133
+ return response.data;
134
+ }
135
+ };
136
+
137
+ // src/storage/index.ts
138
+ import axios2 from "axios";
139
+ var StorageClient = class {
140
+ constructor(baseUrl, appId, options) {
141
+ __publicField(this, "axios");
142
+ this.axios = axios2.create({
143
+ baseURL: baseUrl,
144
+ withCredentials: !options?.apiKey,
145
+ // Only use cookies if no API key
146
+ headers: {
147
+ ...appId ? { "x-archlast-app-id": appId } : {},
148
+ ...options?.apiKey ? { "x-api-key": options.apiKey } : {}
149
+ }
150
+ });
151
+ }
152
+ /**
153
+ * Upload a file
154
+ */
155
+ async upload(file, contentType) {
156
+ let body = file;
157
+ let headers = {};
158
+ if (file instanceof File) {
159
+ const formData = new FormData();
160
+ formData.append("file", file);
161
+ body = formData;
162
+ } else {
163
+ if (contentType) {
164
+ headers["Content-Type"] = contentType;
165
+ }
166
+ if (file instanceof Uint8Array) {
167
+ }
168
+ }
169
+ const response = await this.axios.post(
170
+ "/_archlast/storage/upload",
171
+ body,
172
+ {
173
+ headers
174
+ }
175
+ );
176
+ const data = response.data;
177
+ if (!data.id) throw new Error("Upload failed: No ID returned");
178
+ return this.hydrate(data);
179
+ }
180
+ /**
181
+ * List files
182
+ */
183
+ async list(limit = 20, offset = 0) {
184
+ const response = await this.axios.get("/_archlast/storage/list", {
185
+ params: { limit, offset }
186
+ });
187
+ return {
188
+ items: response.data.items.map((item) => this.hydrate(item))
189
+ };
190
+ }
191
+ /**
192
+ * Get file metadata
193
+ */
194
+ async getMetadata(id) {
195
+ const response = await this.axios.get(`/_archlast/storage/files/${id}`);
196
+ return this.hydrate(response.data);
197
+ }
198
+ /**
199
+ * Generate a presigned download URL
200
+ */
201
+ async presign(id, expiresInSeconds = 300) {
202
+ const response = await this.axios.post("/_archlast/storage/presign", {
203
+ id,
204
+ expiresInSeconds
205
+ });
206
+ return response.data;
207
+ }
208
+ /**
209
+ * Delete file
210
+ */
211
+ async delete(id) {
212
+ const response = await this.axios.delete(
213
+ `/_archlast/storage/files/${id}`
214
+ );
215
+ return response.data;
216
+ }
217
+ /**
218
+ * Get public URL for a file
219
+ */
220
+ getPublicUrl(id) {
221
+ const base = this.axios.defaults.baseURL?.replace(/\/+$/, "") || "";
222
+ return `${base}/_storage/${id}`;
223
+ }
224
+ getDownloadUrl(id) {
225
+ const base = this.axios.defaults.baseURL?.replace(/\/+$/, "") || "";
226
+ return `${base}/_archlast/storage/files/${id}/download`;
227
+ }
228
+ hydrate(file) {
229
+ if (!file.id) throw new Error("Invalid file object: missing id");
230
+ return {
231
+ id: file.id,
232
+ name: file.name ?? file.fileName ?? "Untitled",
233
+ // Handle aliasing
234
+ fileName: file.fileName ?? file.name ?? null,
235
+ url: file.url ?? this.getPublicUrl(file.id),
236
+ downloadUrl: file.downloadUrl ?? this.getDownloadUrl(file.id),
237
+ hash: file.hash ?? "",
238
+ contentType: file.contentType ?? "application/octet-stream",
239
+ size: file.size ?? 0,
240
+ createdAt: file.createdAt ?? Date.now(),
241
+ updatedAt: file.updatedAt ?? Date.now(),
242
+ refCount: file.refCount ?? 1
243
+ };
244
+ }
245
+ };
246
+
247
+ // src/admin/index.ts
248
+ import axios3 from "axios";
249
+ var AdminAuthClient = class {
250
+ constructor(baseUrl, options) {
251
+ __publicField(this, "axios");
252
+ this.axios = axios3.create({
253
+ baseURL: baseUrl,
254
+ withCredentials: !options?.apiKey,
255
+ headers: {
256
+ ...options?.apiKey ? { "x-api-key": options.apiKey } : {}
257
+ }
258
+ });
259
+ }
260
+ async signIn(email, password) {
261
+ const response = await this.axios.post("/_archlast/admin/auth/sign-in", {
262
+ email,
263
+ password
264
+ });
265
+ return response.data;
266
+ }
267
+ async signOut() {
268
+ const response = await this.axios.post("/_archlast/admin/auth/sign-out");
269
+ return response.data;
270
+ }
271
+ async getProfile() {
272
+ const response = await this.axios.get("/_archlast/admin/auth/me");
273
+ return response.data;
274
+ }
275
+ async getSessions() {
276
+ const response = await this.axios.get("/_archlast/admin/auth/sessions");
277
+ return response.data;
278
+ }
279
+ async revokeSession(sessionId) {
280
+ const response = await this.axios.post("/_archlast/admin/auth/revoke-session", {
281
+ sessionId
282
+ });
283
+ return response.data;
284
+ }
285
+ async signOutAll() {
286
+ const response = await this.axios.post("/_archlast/admin/auth/sign-out-all");
287
+ return response.data;
288
+ }
289
+ };
290
+ var AdminClient = class {
291
+ // Add other admin clients here (users, tenants, etc.)
292
+ constructor(baseUrl, options) {
293
+ __publicField(this, "auth");
294
+ this.auth = new AdminAuthClient(baseUrl, options);
295
+ }
296
+ };
297
+
298
+ // src/client.ts
299
+ var ArchlastClient = class {
300
+ /**
301
+ * @param url WebSocket URL
302
+ * @param httpUrl Backend HTTP URL (used for WS derivation fallback)
303
+ * @param appId App ID for session isolation
304
+ * @param authUrl Optional same-origin URL for auth requests (to avoid cross-origin cookie issues)
305
+ * @param options Configuration options
306
+ */
307
+ constructor(url, httpUrl, appId, authUrl, options = {}) {
308
+ __publicField(this, "ws", null);
309
+ __publicField(this, "url");
310
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
311
+ __publicField(this, "subscriptions", /* @__PURE__ */ new Map());
312
+ __publicField(this, "pendingMutations", /* @__PURE__ */ new Map());
313
+ __publicField(this, "messageQueue", []);
314
+ __publicField(this, "isConnected", false);
315
+ __publicField(this, "reconnectAttempts", 0);
316
+ __publicField(this, "maxReconnectAttempts", 5);
317
+ __publicField(this, "reconnectDelay", 1e3);
318
+ __publicField(this, "reconnectTimeout", null);
319
+ __publicField(this, "isExplicitlyClosed", false);
320
+ __publicField(this, "sessionId", null);
321
+ // Event listeners
322
+ __publicField(this, "connectionListeners", /* @__PURE__ */ new Set());
323
+ __publicField(this, "disconnectionListeners", /* @__PURE__ */ new Set());
324
+ __publicField(this, "errorListeners", /* @__PURE__ */ new Set());
325
+ __publicField(this, "auth");
326
+ __publicField(this, "storage");
327
+ __publicField(this, "admin");
328
+ __publicField(this, "baseUrl");
329
+ __publicField(this, "appId");
330
+ __publicField(this, "isAdmin");
331
+ __publicField(this, "apiKey");
332
+ __publicField(this, "betterAuthCookies");
333
+ __publicField(this, "heartbeatInterval", null);
334
+ this.url = url;
335
+ this.appId = appId;
336
+ this.isAdmin = options.isAdmin ?? false;
337
+ this.apiKey = options.apiKey;
338
+ this.betterAuthCookies = options.betterAuthCookies;
339
+ const derivedHttpUrl = httpUrl !== void 0 ? httpUrl : url.replace(/^ws/, "http");
340
+ const authBaseUrl = authUrl !== void 0 ? authUrl : derivedHttpUrl;
341
+ this.baseUrl = authBaseUrl;
342
+ this.auth = new ArchlastAuthClient({ baseUrl: authBaseUrl, appId, apiKey: options.apiKey });
343
+ this.storage = new StorageClient(authBaseUrl, appId, { apiKey: options.apiKey });
344
+ this.admin = new AdminClient(authBaseUrl, { apiKey: options.apiKey });
345
+ if (options.autoConnect !== false) {
346
+ this.connect();
347
+ }
348
+ }
349
+ /**
350
+ * Update Better-Auth cookies (call this when session changes)
351
+ */
352
+ setBetterAuthCookies(cookies) {
353
+ this.betterAuthCookies = cookies;
354
+ if (this.isConnected) {
355
+ this.connect();
356
+ }
357
+ }
358
+ /**
359
+ * Update Better-Auth API key (call this when API key changes)
360
+ */
361
+ setApiKey(apiKey) {
362
+ this.apiKey = apiKey;
363
+ if (this.isConnected) {
364
+ this.connect();
365
+ }
366
+ }
367
+ connect() {
368
+ if (typeof window === "undefined") {
369
+ return;
370
+ }
371
+ this.isExplicitlyClosed = false;
372
+ if (this.ws) {
373
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
374
+ return;
375
+ }
376
+ try {
377
+ this.ws.close();
378
+ } catch (e) {
379
+ }
380
+ this.ws = null;
381
+ }
382
+ try {
383
+ this.ws = new WebSocket(this.url);
384
+ } catch (e) {
385
+ this.handleClose();
386
+ return;
387
+ }
388
+ this.ws.onopen = async () => {
389
+ console.log("Connected to Archlast Server");
390
+ this.isConnected = true;
391
+ this.reconnectAttempts = 0;
392
+ const headers = {};
393
+ if (this.appId) {
394
+ headers["x-archlast-app-id"] = this.appId;
395
+ }
396
+ if (this.apiKey) {
397
+ headers["x-api-key"] = this.apiKey;
398
+ }
399
+ const cookies = {};
400
+ if (this.betterAuthCookies) {
401
+ Object.assign(cookies, this.betterAuthCookies);
402
+ }
403
+ this.sendMessage({
404
+ type: "connect",
405
+ auth: {
406
+ headers: Object.keys(headers).length > 0 ? headers : void 0,
407
+ cookies: Object.keys(cookies).length > 0 ? cookies : void 0
408
+ }
409
+ });
410
+ if (this.isAdmin) {
411
+ this.sendMessage({
412
+ type: "subscribe_logs"
413
+ });
414
+ this.sendMessage({
415
+ type: "subscribe_admin_events"
416
+ });
417
+ } else {
418
+ try {
419
+ const state = await this.auth.getState();
420
+ console.log("Auth state:", state);
421
+ if (state.isAuthenticated) {
422
+ this.sendMessage({
423
+ type: "subscribe_logs"
424
+ });
425
+ this.sendMessage({
426
+ type: "subscribe_admin_events"
427
+ });
428
+ }
429
+ } catch (err) {
430
+ }
431
+ }
432
+ this.flushQueue();
433
+ this.resubscribeAll();
434
+ this.startHeartbeat();
435
+ };
436
+ this.ws.onmessage = (event) => this.handleMessage(event);
437
+ this.ws.onclose = () => this.handleClose();
438
+ this.ws.onerror = (err) => {
439
+ if (!this.isExplicitlyClosed) {
440
+ console.error("WebSocket error:", err);
441
+ this.ws?.close();
442
+ }
443
+ };
444
+ }
445
+ startHeartbeat() {
446
+ this.stopHeartbeat();
447
+ this.heartbeatInterval = setInterval(() => {
448
+ if (this.isConnected) {
449
+ this.sendMessage({ type: "ping" });
450
+ }
451
+ }, 3e4);
452
+ }
453
+ stopHeartbeat() {
454
+ if (this.heartbeatInterval) {
455
+ clearInterval(this.heartbeatInterval);
456
+ this.heartbeatInterval = null;
457
+ }
458
+ }
459
+ handleClose() {
460
+ this.isConnected = false;
461
+ this.sessionId = null;
462
+ this.disconnectionListeners.forEach((l) => l("Disconnected"));
463
+ if (this.isExplicitlyClosed) return;
464
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
465
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
466
+ console.log(`Disconnected. Reconnecting in ${delay}ms...`);
467
+ this.reconnectTimeout = setTimeout(() => {
468
+ this.reconnectAttempts++;
469
+ this.connect();
470
+ }, delay);
471
+ }
472
+ }
473
+ handleMessage(event) {
474
+ try {
475
+ const msg = JSON.parse(event.data);
476
+ if (msg.type === "data" || msg.type === "patch") {
477
+ const queryId = msg.queryId;
478
+ const data = msg.data || msg.patch;
479
+ const listeners = this.listeners.get(queryId);
480
+ if (listeners) {
481
+ listeners.forEach((l) => l(data));
482
+ }
483
+ } else if (msg.type === "mutationResponse") {
484
+ const pending = this.pendingMutations.get(msg.mutationId);
485
+ if (pending) {
486
+ clearTimeout(pending.timeout);
487
+ this.pendingMutations.delete(msg.mutationId);
488
+ if (msg.success) {
489
+ pending.resolve(msg.data);
490
+ } else {
491
+ pending.reject(new Error(msg.error));
492
+ }
493
+ }
494
+ } else if (msg.type === "connected") {
495
+ console.log("Session established:", msg.sessionId);
496
+ this.sessionId = msg.sessionId;
497
+ this.isConnected = true;
498
+ this.reconnectAttempts = 0;
499
+ this.connectionListeners.forEach((l) => l(msg.sessionId));
500
+ } else if (msg.type === "error") {
501
+ console.error("Server error:", msg.message);
502
+ this.errorListeners.forEach((l) => l(msg.message));
503
+ if (msg.message === "Authentication required") {
504
+ this.isExplicitlyClosed = true;
505
+ this.ws?.close();
506
+ }
507
+ }
508
+ } catch (e) {
509
+ console.error("Error handling message:", e);
510
+ }
511
+ }
512
+ getWs() {
513
+ return this.ws;
514
+ }
515
+ onConnected(cb) {
516
+ this.connectionListeners.add(cb);
517
+ return () => this.connectionListeners.delete(cb);
518
+ }
519
+ onDisconnected(cb) {
520
+ this.disconnectionListeners.add(cb);
521
+ return () => this.disconnectionListeners.delete(cb);
522
+ }
523
+ onError(cb) {
524
+ this.errorListeners.add(cb);
525
+ return () => this.errorListeners.delete(cb);
526
+ }
527
+ getStatus() {
528
+ return this.isConnected ? "connected" : "disconnected";
529
+ }
530
+ getSessionId() {
531
+ return this.sessionId;
532
+ }
533
+ sendMessage(msg) {
534
+ const data = JSON.stringify(msg);
535
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
536
+ this.ws.send(data);
537
+ } else {
538
+ this.messageQueue.push(data);
539
+ }
540
+ }
541
+ flushQueue() {
542
+ while (this.messageQueue.length > 0) {
543
+ const msg = this.messageQueue.shift();
544
+ if (msg) this.ws?.send(msg);
545
+ }
546
+ }
547
+ resubscribeAll() {
548
+ for (const [queryId, sub] of Array.from(this.subscriptions.entries())) {
549
+ this.sendMessage({
550
+ type: "query",
551
+ queryId,
552
+ name: sub.name,
553
+ args: sub.args
554
+ });
555
+ }
556
+ }
557
+ getQueryId(name, args) {
558
+ const stableArgs = JSON.stringify(args, Object.keys(args || {}).sort());
559
+ return `${name}:${stableArgs}`;
560
+ }
561
+ subscribe(queryName, args, onUpdate) {
562
+ const queryId = this.getQueryId(queryName, args);
563
+ if (!this.listeners.has(queryId)) {
564
+ this.listeners.set(queryId, /* @__PURE__ */ new Set());
565
+ this.subscriptions.set(queryId, { name: queryName, args });
566
+ this.sendMessage({
567
+ type: "query",
568
+ queryId,
569
+ name: queryName,
570
+ args
571
+ });
572
+ }
573
+ this.listeners.get(queryId).add(onUpdate);
574
+ return () => {
575
+ const set = this.listeners.get(queryId);
576
+ if (set) {
577
+ set.delete(onUpdate);
578
+ if (set.size === 0) {
579
+ this.listeners.delete(queryId);
580
+ this.subscriptions.delete(queryId);
581
+ }
582
+ }
583
+ };
584
+ }
585
+ async fetch(name, args) {
586
+ const queryId = this.getQueryId(name, args);
587
+ return new Promise((resolve, reject) => {
588
+ let resolved = false;
589
+ const unsubscribe = this.subscribe(name, args, (data) => {
590
+ if (!resolved) {
591
+ resolved = true;
592
+ unsubscribe();
593
+ resolve(data);
594
+ }
595
+ });
596
+ setTimeout(() => {
597
+ if (!resolved) {
598
+ resolved = true;
599
+ unsubscribe();
600
+ reject(new Error("Timeout waiting for data"));
601
+ }
602
+ }, 5e3);
603
+ });
604
+ }
605
+ async mutate(name, args) {
606
+ return new Promise((resolve, reject) => {
607
+ const mutationId = Math.random().toString(36).slice(2);
608
+ const timeout = setTimeout(() => {
609
+ this.pendingMutations.delete(mutationId);
610
+ reject(new Error("Mutation timeout"));
611
+ }, 1e4);
612
+ this.pendingMutations.set(mutationId, { resolve, reject, timeout });
613
+ this.sendMessage({
614
+ type: "mutation",
615
+ mutationId,
616
+ name,
617
+ args
618
+ });
619
+ });
620
+ }
621
+ close() {
622
+ this.isExplicitlyClosed = true;
623
+ if (this.reconnectTimeout) {
624
+ clearTimeout(this.reconnectTimeout);
625
+ this.reconnectTimeout = null;
626
+ }
627
+ if (this.ws) {
628
+ this.ws.onclose = null;
629
+ this.ws.onopen = null;
630
+ this.ws.onerror = null;
631
+ this.ws.onmessage = null;
632
+ this.ws.close();
633
+ this.ws = null;
634
+ }
635
+ this.isConnected = false;
636
+ this.stopHeartbeat();
637
+ }
638
+ };
639
+
640
+ // src/react.tsx
641
+ import { useEffect, useState, createContext, useContext, useCallback } from "react";
642
+ import {
643
+ useQuery as useTanStackQuery,
644
+ useMutation as useTanStackMutation,
645
+ useQueryClient,
646
+ QueryClient,
647
+ QueryClientProvider
648
+ } from "@tanstack/react-query";
649
+ import axios4 from "axios";
650
+ import { jsx } from "react/jsx-runtime";
651
+ var ArchlastContext = createContext(null);
652
+ var useArchlast = () => {
653
+ const client = useContext(ArchlastContext);
654
+ if (!client) throw new Error("ArchlastProvider not found");
655
+ return client;
656
+ };
657
+ function ArchlastProvider({ client, children }) {
658
+ const [queryClient] = useState(() => new QueryClient());
659
+ return /* @__PURE__ */ jsx(ArchlastContext.Provider, { value: client, children: /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children }) });
660
+ }
661
+ function useQuery(queryRef, args, options) {
662
+ const client = useArchlast();
663
+ const queryClient = useQueryClient();
664
+ const queryKey = [queryRef._name, args];
665
+ useEffect(() => {
666
+ const unsubscribe = client.subscribe(queryRef._name, args, (data) => {
667
+ queryClient.setQueryData(queryKey, data);
668
+ });
669
+ return () => unsubscribe();
670
+ }, [client, queryRef._name, JSON.stringify(args), queryClient]);
671
+ const query = useTanStackQuery({
672
+ queryKey,
673
+ queryFn: async () => {
674
+ const result = await client.fetch(queryRef._name, args);
675
+ return result === void 0 ? null : result;
676
+ },
677
+ // staleTime: 0,
678
+ // gcTime: 1000 * 60 * 5,
679
+ ...options
680
+ });
681
+ return query.data;
682
+ }
683
+ function useMutation(mutationRef) {
684
+ const client = useArchlast();
685
+ const queryClient = useQueryClient();
686
+ const mutation = useTanStackMutation({
687
+ mutationFn: async (args) => {
688
+ return client.mutate(mutationRef._name, args);
689
+ },
690
+ onSuccess: () => {
691
+ queryClient.invalidateQueries();
692
+ }
693
+ });
694
+ return mutation.mutateAsync;
695
+ }
696
+ function useUpload(options) {
697
+ const client = useArchlast();
698
+ const [isUploading, setUploading] = useState(false);
699
+ const [error, setError] = useState(null);
700
+ const [result, setResult] = useState(null);
701
+ const upload = useCallback(
702
+ async (file, contentType) => {
703
+ setUploading(true);
704
+ setError(null);
705
+ setResult(null);
706
+ try {
707
+ const result2 = await client.storage.upload(file, contentType);
708
+ setResult({ id: result2.id, url: result2.url });
709
+ return { id: result2.id, url: result2.url };
710
+ } catch (err) {
711
+ const e = err instanceof Error ? err : new Error(String(err));
712
+ setError(e);
713
+ throw e;
714
+ } finally {
715
+ setUploading(false);
716
+ }
717
+ },
718
+ [client]
719
+ );
720
+ const presign = useCallback(
721
+ async (id, expiresInSeconds = 300) => {
722
+ const { url: relativeUrl } = await client.storage.presign(id, expiresInSeconds);
723
+ const absoluteUrl = relativeUrl.startsWith("http") ? relativeUrl : `${options?.baseUrl ?? ""}${relativeUrl.startsWith("/") ? "" : "/"}${relativeUrl}`;
724
+ setResult((prev) => prev ? { ...prev, url: absoluteUrl } : { id, url: absoluteUrl });
725
+ return absoluteUrl;
726
+ },
727
+ [client, options?.baseUrl]
728
+ );
729
+ return { upload, presign, isUploading, error, result };
730
+ }
731
+ var DEFAULT_AUTH_STATE = {
732
+ isAuthenticated: false,
733
+ user: null,
734
+ session: null
735
+ };
736
+ function useAuth(options) {
737
+ const client = useArchlast();
738
+ const queryClient = useQueryClient();
739
+ const enabled = options?.enabled ?? true;
740
+ const pollInterval = options?.pollIntervalMs ?? 3e4;
741
+ const {
742
+ data: authState,
743
+ isLoading,
744
+ error,
745
+ refetch
746
+ } = useTanStackQuery({
747
+ queryKey: ["auth", "state"],
748
+ queryFn: async () => {
749
+ try {
750
+ return await client.auth.getState();
751
+ } catch (err) {
752
+ if (axios4.isAxiosError(err) && err.response?.status === 401) {
753
+ return DEFAULT_AUTH_STATE;
754
+ }
755
+ throw err;
756
+ }
757
+ },
758
+ retry: false,
759
+ // Don't retry auth checks, fail fast
760
+ staleTime: 0,
761
+ // Auth state is volatile, consider it always stale
762
+ refetchInterval: enabled ? pollInterval : false,
763
+ refetchOnWindowFocus: true,
764
+ // Security: re-check when user tabbs back
765
+ refetchOnReconnect: true,
766
+ enabled,
767
+ initialData: DEFAULT_AUTH_STATE
768
+ // Optimistic default
769
+ });
770
+ const invalidateAuth = useCallback(() => {
771
+ return queryClient.invalidateQueries({ queryKey: ["auth", "state"] });
772
+ }, [queryClient]);
773
+ const signIn = useCallback(
774
+ async (input) => {
775
+ await client.auth.signIn(input);
776
+ await invalidateAuth();
777
+ },
778
+ [client, invalidateAuth]
779
+ );
780
+ const signUp = useCallback(
781
+ async (input) => {
782
+ await client.auth.signUp(input);
783
+ await invalidateAuth();
784
+ },
785
+ [client, invalidateAuth]
786
+ );
787
+ const signOut = useCallback(async () => {
788
+ try {
789
+ await client.auth.signOut();
790
+ } finally {
791
+ queryClient.setQueryData(["auth", "state"], DEFAULT_AUTH_STATE);
792
+ await invalidateAuth();
793
+ }
794
+ }, [client, queryClient, invalidateAuth]);
795
+ const currentState = authState ?? DEFAULT_AUTH_STATE;
796
+ return {
797
+ ...currentState,
798
+ // Ensure booleans
799
+ isAuthenticated: !!currentState.isAuthenticated,
800
+ isLoading,
801
+ error,
802
+ refresh: async () => {
803
+ await refetch();
804
+ },
805
+ signIn,
806
+ signUp,
807
+ signOut
808
+ };
809
+ }
810
+ function useRequestPasswordReset() {
811
+ const client = useArchlast();
812
+ return useTanStackMutation({
813
+ mutationFn: async (email) => {
814
+ return client.auth.requestPasswordReset(email);
815
+ }
816
+ });
817
+ }
818
+ function useResetPassword() {
819
+ const client = useArchlast();
820
+ return useTanStackMutation({
821
+ mutationFn: async ({ token, password }) => {
822
+ return client.auth.resetPassword(token, password);
823
+ }
824
+ });
825
+ }
826
+ function useDownload(options) {
827
+ const client = useArchlast();
828
+ const [isDownloading, setDownloading] = useState(false);
829
+ const [error, setError] = useState(null);
830
+ const presign = useCallback(
831
+ async (id, expiresInSeconds = 300) => {
832
+ const { url: relativeUrl } = await client.storage.presign(id, expiresInSeconds);
833
+ const absoluteUrl = relativeUrl.startsWith("http") ? relativeUrl : `${options?.baseUrl ?? client.baseUrl ?? ""}${relativeUrl.startsWith("/") ? "" : "/"}${relativeUrl}`;
834
+ return absoluteUrl;
835
+ },
836
+ [client, options?.baseUrl]
837
+ );
838
+ const download = useCallback(
839
+ async (id, expiresInSeconds = 300) => {
840
+ setDownloading(true);
841
+ setError(null);
842
+ try {
843
+ const signedUrl = await presign(id, expiresInSeconds);
844
+ const response = await axios4.get(signedUrl, {
845
+ responseType: "blob"
846
+ });
847
+ const blob = response.data;
848
+ const objectUrl = URL.createObjectURL(blob);
849
+ return { blob, url: objectUrl };
850
+ } catch (err) {
851
+ const e = err instanceof Error ? err : new Error(String(err));
852
+ setError(e);
853
+ throw e;
854
+ } finally {
855
+ setDownloading(false);
856
+ }
857
+ },
858
+ [presign]
859
+ );
860
+ return { getUrl: presign, download, isDownloading, error };
861
+ }
862
+ function useStorageDelete(options) {
863
+ const client = useArchlast();
864
+ const [isDeleting, setDeleting] = useState(false);
865
+ const [error, setError] = useState(null);
866
+ const deleteFile = useCallback(
867
+ async (id) => {
868
+ setDeleting(true);
869
+ setError(null);
870
+ try {
871
+ const deleteUrl = `${options?.baseUrl ?? ""}/_archlast/storage/files/${encodeURIComponent(id)}`;
872
+ await axios4.delete(deleteUrl, {
873
+ withCredentials: true
874
+ });
875
+ } catch (err) {
876
+ const e = err instanceof Error ? err : new Error(String(err));
877
+ setError(e);
878
+ throw e;
879
+ } finally {
880
+ setDeleting(false);
881
+ }
882
+ },
883
+ [options?.baseUrl]
884
+ );
885
+ return { deleteFile, isDeleting, error };
886
+ }
887
+ function useClientPagination(data, initialPageSize = 10) {
888
+ const [page, setPage] = useState(1);
889
+ const [pageSize, setPageSize] = useState(initialPageSize);
890
+ const items = data || [];
891
+ const total = items.length;
892
+ const totalPages = Math.ceil(total / pageSize);
893
+ const startIndex = (page - 1) * pageSize;
894
+ const endIndex = startIndex + pageSize;
895
+ const paginatedItems = items.slice(startIndex, endIndex);
896
+ const hasMore = page < totalPages;
897
+ const hasPrevious = page > 1;
898
+ const nextPage = () => {
899
+ if (hasMore) setPage((p) => p + 1);
900
+ };
901
+ const previousPage = () => {
902
+ if (hasPrevious) setPage((p) => p - 1);
903
+ };
904
+ const handleSetPage = (newPage) => {
905
+ const validPage = Math.max(1, Math.min(newPage, totalPages || 1));
906
+ setPage(validPage);
907
+ };
908
+ const handleSetPageSize = (size) => {
909
+ setPageSize(size);
910
+ setPage(1);
911
+ };
912
+ useEffect(() => {
913
+ setPage(1);
914
+ }, [data?.length]);
915
+ return {
916
+ items: paginatedItems,
917
+ page,
918
+ pageSize,
919
+ total,
920
+ totalPages,
921
+ hasMore,
922
+ hasPrevious,
923
+ nextPage,
924
+ previousPage,
925
+ setPage: handleSetPage,
926
+ setPageSize: handleSetPageSize
927
+ };
928
+ }
929
+ function usePagination(queryRef, baseArgs, options) {
930
+ const client = useArchlast();
931
+ const queryClient = useQueryClient();
932
+ const [cursor, setCursor] = useState(null);
933
+ const [allItems, setAllItems] = useState([]);
934
+ const limit = options?.limit || 20;
935
+ const args = { ...baseArgs, cursor, limit };
936
+ const queryKey = [queryRef._name, args];
937
+ useEffect(() => {
938
+ const unsubscribe = client.subscribe(queryRef._name, args, (data2) => {
939
+ queryClient.setQueryData(queryKey, data2);
940
+ });
941
+ return () => unsubscribe();
942
+ }, [client, queryRef._name, JSON.stringify(args), queryClient]);
943
+ const query = useTanStackQuery({
944
+ queryKey,
945
+ queryFn: async () => {
946
+ const result = await client.fetch(queryRef._name, args);
947
+ return result === void 0 ? { items: [], continueCursor: null, isDone: true } : result;
948
+ },
949
+ // staleTime: 0,
950
+ // gcTime: 1000 * 60 * 5,
951
+ enabled: options?.enabled !== false
952
+ });
953
+ const data = query.data;
954
+ useEffect(() => {
955
+ if (data?.items) {
956
+ if (cursor === null) {
957
+ setAllItems(data.items);
958
+ } else {
959
+ setAllItems((prev) => [...prev, ...data.items]);
960
+ }
961
+ }
962
+ }, [data, cursor]);
963
+ const loadMore = () => {
964
+ if (data && !data.isDone && data.continueCursor) {
965
+ setCursor(data.continueCursor);
966
+ }
967
+ };
968
+ const refresh = () => {
969
+ setCursor(null);
970
+ setAllItems([]);
971
+ queryClient.invalidateQueries({ queryKey: [queryRef._name] });
972
+ };
973
+ return {
974
+ items: allItems,
975
+ cursor: data?.continueCursor ?? null,
976
+ isDone: data?.isDone ?? false,
977
+ page: data?.page,
978
+ pageSize: data?.pageSize,
979
+ total: data?.total,
980
+ hasMore: !data?.isDone && !!data?.continueCursor,
981
+ loadMore,
982
+ refresh,
983
+ isLoading: query.isLoading
984
+ };
985
+ }
986
+ function useStorageList(limit = 20, offset = 0) {
987
+ const client = useArchlast();
988
+ return useTanStackQuery({
989
+ queryKey: ["storage", "list", limit, offset],
990
+ queryFn: () => client.storage.list(limit, offset)
991
+ });
992
+ }
993
+ function useStorageMetadata(id) {
994
+ const client = useArchlast();
995
+ return useTanStackQuery({
996
+ queryKey: ["storage", "file", id],
997
+ queryFn: () => client.storage.getMetadata(id),
998
+ enabled: !!id
999
+ });
1000
+ }
1001
+ function useStorageUpload() {
1002
+ const client = useArchlast();
1003
+ const queryClient = useQueryClient();
1004
+ return useTanStackMutation({
1005
+ mutationFn: (file) => client.storage.upload(file),
1006
+ onSuccess: () => {
1007
+ queryClient.invalidateQueries({ queryKey: ["storage", "list"] });
1008
+ }
1009
+ });
1010
+ }
1011
+ function useStorageDeleteFile() {
1012
+ const client = useArchlast();
1013
+ const queryClient = useQueryClient();
1014
+ return useTanStackMutation({
1015
+ mutationFn: (id) => client.storage.delete(id),
1016
+ onSuccess: () => {
1017
+ queryClient.invalidateQueries({ queryKey: ["storage", "list"] });
1018
+ }
1019
+ });
1020
+ }
1021
+ function useStorageHelpers() {
1022
+ const client = useArchlast();
1023
+ return {
1024
+ getPublicUrl: (id) => client.storage.getPublicUrl(id)
1025
+ };
1026
+ }
1027
+
1028
+ // src/function-reference.ts
1029
+ function makeFunctionReference(name) {
1030
+ return {
1031
+ _type: "query",
1032
+ _name: name,
1033
+ _args: void 0,
1034
+ _return: void 0,
1035
+ _auth: void 0
1036
+ };
1037
+ }
1038
+ function makeRpcReference(name) {
1039
+ return {
1040
+ _procedureType: "query",
1041
+ _name: name,
1042
+ _args: void 0,
1043
+ _return: void 0,
1044
+ _auth: void 0
1045
+ };
1046
+ }
1047
+
1048
+ // src/trpc.ts
1049
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
1050
+ var BETTER_AUTH_API_KEY_PREFIX = "arch_";
1051
+ function isBetterAuthApiKey(token) {
1052
+ return token.startsWith(BETTER_AUTH_API_KEY_PREFIX);
1053
+ }
1054
+ function createArchlastTRPCClient(options = {}) {
1055
+ const {
1056
+ baseUrl = "http://localhost:4000",
1057
+ apiKey,
1058
+ sessionToken,
1059
+ batch = true,
1060
+ headers: customHeaders = {}
1061
+ } = options;
1062
+ const headers = {
1063
+ "Content-Type": "application/json",
1064
+ ...customHeaders
1065
+ };
1066
+ if (apiKey) {
1067
+ if (isBetterAuthApiKey(apiKey)) {
1068
+ headers["x-api-key"] = apiKey;
1069
+ } else {
1070
+ headers["Authorization"] = `Bearer ${apiKey}`;
1071
+ }
1072
+ }
1073
+ if (sessionToken) {
1074
+ headers["Cookie"] = `archlast-session=${sessionToken}`;
1075
+ }
1076
+ return createTRPCClient({
1077
+ links: [
1078
+ httpBatchLink({
1079
+ url: `${baseUrl}/api/trpc`,
1080
+ headers
1081
+ })
1082
+ ]
1083
+ });
1084
+ }
1085
+ export {
1086
+ ArchlastAuthClient,
1087
+ ArchlastClient,
1088
+ ArchlastContext,
1089
+ ArchlastProvider,
1090
+ StorageClient,
1091
+ createArchlastTRPCClient,
1092
+ makeFunctionReference,
1093
+ makeRpcReference,
1094
+ useArchlast,
1095
+ useAuth,
1096
+ useClientPagination,
1097
+ useDownload,
1098
+ useMutation,
1099
+ usePagination,
1100
+ useQuery,
1101
+ useRequestPasswordReset,
1102
+ useResetPassword,
1103
+ useStorageDelete,
1104
+ useStorageDeleteFile,
1105
+ useStorageHelpers,
1106
+ useStorageList,
1107
+ useStorageMetadata,
1108
+ useStorageUpload,
1109
+ useUpload
1110
+ };
1111
+ //# sourceMappingURL=index.js.map