@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
@@ -0,0 +1,677 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
31
+
32
+ // src/client.ts
33
+ var client_exports = {};
34
+ __export(client_exports, {
35
+ ArchlastClient: () => ArchlastClient
36
+ });
37
+ module.exports = __toCommonJS(client_exports);
38
+
39
+ // src/auth/index.ts
40
+ var import_axios = __toESM(require("axios"), 1);
41
+ function resolveBaseUrl(explicit) {
42
+ if (explicit && explicit.trim()) return explicit.replace(/\/+$/, "");
43
+ if (typeof window !== "undefined" && window.location?.origin) return window.location.origin;
44
+ return "";
45
+ }
46
+ var ArchlastAuthClient = class {
47
+ constructor(options = {}) {
48
+ __publicField(this, "baseUrl");
49
+ __publicField(this, "axios");
50
+ __publicField(this, "appId");
51
+ __publicField(this, "apiKey");
52
+ this.baseUrl = resolveBaseUrl(options.baseUrl);
53
+ this.appId = options.appId;
54
+ this.apiKey = options.apiKey;
55
+ this.axios = import_axios.default.create({
56
+ baseURL: this.baseUrl,
57
+ withCredentials: !options.apiKey,
58
+ // Only use cookies if no API key
59
+ headers: {
60
+ "content-type": "application/json",
61
+ ...this.appId ? { "x-archlast-app-id": this.appId } : {},
62
+ ...options.apiKey ? { "x-api-key": options.apiKey } : {}
63
+ }
64
+ });
65
+ }
66
+ /**
67
+ * Get current authentication state
68
+ * Uses Better-Auth's getSession endpoint
69
+ */
70
+ async getState() {
71
+ try {
72
+ const response = await this.axios.get("/api/auth/get-session");
73
+ const { user, session } = response.data;
74
+ return {
75
+ user,
76
+ session,
77
+ isAuthenticated: !!user
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ user: null,
82
+ session: null,
83
+ isAuthenticated: false
84
+ };
85
+ }
86
+ }
87
+ /**
88
+ * Sign up new user
89
+ * Uses Better-Auth's email signUp endpoint
90
+ */
91
+ async signUp(input) {
92
+ const response = await this.axios.post("/api/auth/sign-up/email", {
93
+ email: input.email,
94
+ password: input.password,
95
+ name: input.name,
96
+ username: input.username
97
+ });
98
+ return {
99
+ user: response.data.user,
100
+ session: null,
101
+ // Session is managed via cookies
102
+ isAuthenticated: !!response.data.user
103
+ };
104
+ }
105
+ /**
106
+ * Sign in with email/username and password
107
+ * Uses Better-Auth's credential sign-in endpoint
108
+ */
109
+ async signIn(input) {
110
+ if (!input.email && !input.username) {
111
+ throw new Error("Either email or username is required");
112
+ }
113
+ const response = await this.axios.post("/api/auth/sign-in/email", {
114
+ email: input.email,
115
+ username: input.username,
116
+ password: input.password
117
+ });
118
+ return {
119
+ user: response.data.user,
120
+ session: null,
121
+ // Session is managed via cookies
122
+ isAuthenticated: !!response.data.user
123
+ };
124
+ }
125
+ /**
126
+ * Sign out and revoke session
127
+ * Uses Better-Auth's sign-out endpoint
128
+ */
129
+ async signOut() {
130
+ const response = await this.axios.post(
131
+ "/api/auth/sign-out",
132
+ {},
133
+ { withCredentials: true }
134
+ );
135
+ return response.data;
136
+ }
137
+ /**
138
+ * Request password reset email
139
+ * Uses Better-Auth's password reset flow
140
+ */
141
+ async requestPasswordReset(email, callbackURL) {
142
+ const response = await this.axios.post("/api/auth/forgot-password", {
143
+ email,
144
+ redirectTo: callbackURL
145
+ });
146
+ return response.data;
147
+ }
148
+ /**
149
+ * Reset password with token
150
+ * Uses Better-Auth's reset password endpoint
151
+ */
152
+ async resetPassword(token, password) {
153
+ const response = await this.axios.post("/api/auth/reset-password", {
154
+ token,
155
+ password
156
+ });
157
+ return response.data;
158
+ }
159
+ /**
160
+ * Verify email with token
161
+ * Uses Better-Auth's email verification endpoint
162
+ */
163
+ async verifyEmail(token) {
164
+ const response = await this.axios.post("/api/auth/verify-email", {
165
+ token
166
+ });
167
+ return response.data;
168
+ }
169
+ };
170
+
171
+ // src/storage/index.ts
172
+ var import_axios2 = __toESM(require("axios"), 1);
173
+ var StorageClient = class {
174
+ constructor(baseUrl, appId, options) {
175
+ __publicField(this, "axios");
176
+ this.axios = import_axios2.default.create({
177
+ baseURL: baseUrl,
178
+ withCredentials: !options?.apiKey,
179
+ // Only use cookies if no API key
180
+ headers: {
181
+ ...appId ? { "x-archlast-app-id": appId } : {},
182
+ ...options?.apiKey ? { "x-api-key": options.apiKey } : {}
183
+ }
184
+ });
185
+ }
186
+ /**
187
+ * Upload a file
188
+ */
189
+ async upload(file, contentType) {
190
+ let body = file;
191
+ let headers = {};
192
+ if (file instanceof File) {
193
+ const formData = new FormData();
194
+ formData.append("file", file);
195
+ body = formData;
196
+ } else {
197
+ if (contentType) {
198
+ headers["Content-Type"] = contentType;
199
+ }
200
+ if (file instanceof Uint8Array) {
201
+ }
202
+ }
203
+ const response = await this.axios.post(
204
+ "/_archlast/storage/upload",
205
+ body,
206
+ {
207
+ headers
208
+ }
209
+ );
210
+ const data = response.data;
211
+ if (!data.id) throw new Error("Upload failed: No ID returned");
212
+ return this.hydrate(data);
213
+ }
214
+ /**
215
+ * List files
216
+ */
217
+ async list(limit = 20, offset = 0) {
218
+ const response = await this.axios.get("/_archlast/storage/list", {
219
+ params: { limit, offset }
220
+ });
221
+ return {
222
+ items: response.data.items.map((item) => this.hydrate(item))
223
+ };
224
+ }
225
+ /**
226
+ * Get file metadata
227
+ */
228
+ async getMetadata(id) {
229
+ const response = await this.axios.get(`/_archlast/storage/files/${id}`);
230
+ return this.hydrate(response.data);
231
+ }
232
+ /**
233
+ * Generate a presigned download URL
234
+ */
235
+ async presign(id, expiresInSeconds = 300) {
236
+ const response = await this.axios.post("/_archlast/storage/presign", {
237
+ id,
238
+ expiresInSeconds
239
+ });
240
+ return response.data;
241
+ }
242
+ /**
243
+ * Delete file
244
+ */
245
+ async delete(id) {
246
+ const response = await this.axios.delete(
247
+ `/_archlast/storage/files/${id}`
248
+ );
249
+ return response.data;
250
+ }
251
+ /**
252
+ * Get public URL for a file
253
+ */
254
+ getPublicUrl(id) {
255
+ const base = this.axios.defaults.baseURL?.replace(/\/+$/, "") || "";
256
+ return `${base}/_storage/${id}`;
257
+ }
258
+ getDownloadUrl(id) {
259
+ const base = this.axios.defaults.baseURL?.replace(/\/+$/, "") || "";
260
+ return `${base}/_archlast/storage/files/${id}/download`;
261
+ }
262
+ hydrate(file) {
263
+ if (!file.id) throw new Error("Invalid file object: missing id");
264
+ return {
265
+ id: file.id,
266
+ name: file.name ?? file.fileName ?? "Untitled",
267
+ // Handle aliasing
268
+ fileName: file.fileName ?? file.name ?? null,
269
+ url: file.url ?? this.getPublicUrl(file.id),
270
+ downloadUrl: file.downloadUrl ?? this.getDownloadUrl(file.id),
271
+ hash: file.hash ?? "",
272
+ contentType: file.contentType ?? "application/octet-stream",
273
+ size: file.size ?? 0,
274
+ createdAt: file.createdAt ?? Date.now(),
275
+ updatedAt: file.updatedAt ?? Date.now(),
276
+ refCount: file.refCount ?? 1
277
+ };
278
+ }
279
+ };
280
+
281
+ // src/admin/index.ts
282
+ var import_axios3 = __toESM(require("axios"), 1);
283
+ var AdminAuthClient = class {
284
+ constructor(baseUrl, options) {
285
+ __publicField(this, "axios");
286
+ this.axios = import_axios3.default.create({
287
+ baseURL: baseUrl,
288
+ withCredentials: !options?.apiKey,
289
+ headers: {
290
+ ...options?.apiKey ? { "x-api-key": options.apiKey } : {}
291
+ }
292
+ });
293
+ }
294
+ async signIn(email, password) {
295
+ const response = await this.axios.post("/_archlast/admin/auth/sign-in", {
296
+ email,
297
+ password
298
+ });
299
+ return response.data;
300
+ }
301
+ async signOut() {
302
+ const response = await this.axios.post("/_archlast/admin/auth/sign-out");
303
+ return response.data;
304
+ }
305
+ async getProfile() {
306
+ const response = await this.axios.get("/_archlast/admin/auth/me");
307
+ return response.data;
308
+ }
309
+ async getSessions() {
310
+ const response = await this.axios.get("/_archlast/admin/auth/sessions");
311
+ return response.data;
312
+ }
313
+ async revokeSession(sessionId) {
314
+ const response = await this.axios.post("/_archlast/admin/auth/revoke-session", {
315
+ sessionId
316
+ });
317
+ return response.data;
318
+ }
319
+ async signOutAll() {
320
+ const response = await this.axios.post("/_archlast/admin/auth/sign-out-all");
321
+ return response.data;
322
+ }
323
+ };
324
+ var AdminClient = class {
325
+ // Add other admin clients here (users, tenants, etc.)
326
+ constructor(baseUrl, options) {
327
+ __publicField(this, "auth");
328
+ this.auth = new AdminAuthClient(baseUrl, options);
329
+ }
330
+ };
331
+
332
+ // src/client.ts
333
+ var ArchlastClient = class {
334
+ /**
335
+ * @param url WebSocket URL
336
+ * @param httpUrl Backend HTTP URL (used for WS derivation fallback)
337
+ * @param appId App ID for session isolation
338
+ * @param authUrl Optional same-origin URL for auth requests (to avoid cross-origin cookie issues)
339
+ * @param options Configuration options
340
+ */
341
+ constructor(url, httpUrl, appId, authUrl, options = {}) {
342
+ __publicField(this, "ws", null);
343
+ __publicField(this, "url");
344
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
345
+ __publicField(this, "subscriptions", /* @__PURE__ */ new Map());
346
+ __publicField(this, "pendingMutations", /* @__PURE__ */ new Map());
347
+ __publicField(this, "messageQueue", []);
348
+ __publicField(this, "isConnected", false);
349
+ __publicField(this, "reconnectAttempts", 0);
350
+ __publicField(this, "maxReconnectAttempts", 5);
351
+ __publicField(this, "reconnectDelay", 1e3);
352
+ __publicField(this, "reconnectTimeout", null);
353
+ __publicField(this, "isExplicitlyClosed", false);
354
+ __publicField(this, "sessionId", null);
355
+ // Event listeners
356
+ __publicField(this, "connectionListeners", /* @__PURE__ */ new Set());
357
+ __publicField(this, "disconnectionListeners", /* @__PURE__ */ new Set());
358
+ __publicField(this, "errorListeners", /* @__PURE__ */ new Set());
359
+ __publicField(this, "auth");
360
+ __publicField(this, "storage");
361
+ __publicField(this, "admin");
362
+ __publicField(this, "baseUrl");
363
+ __publicField(this, "appId");
364
+ __publicField(this, "isAdmin");
365
+ __publicField(this, "apiKey");
366
+ __publicField(this, "betterAuthCookies");
367
+ __publicField(this, "heartbeatInterval", null);
368
+ this.url = url;
369
+ this.appId = appId;
370
+ this.isAdmin = options.isAdmin ?? false;
371
+ this.apiKey = options.apiKey;
372
+ this.betterAuthCookies = options.betterAuthCookies;
373
+ const derivedHttpUrl = httpUrl !== void 0 ? httpUrl : url.replace(/^ws/, "http");
374
+ const authBaseUrl = authUrl !== void 0 ? authUrl : derivedHttpUrl;
375
+ this.baseUrl = authBaseUrl;
376
+ this.auth = new ArchlastAuthClient({ baseUrl: authBaseUrl, appId, apiKey: options.apiKey });
377
+ this.storage = new StorageClient(authBaseUrl, appId, { apiKey: options.apiKey });
378
+ this.admin = new AdminClient(authBaseUrl, { apiKey: options.apiKey });
379
+ if (options.autoConnect !== false) {
380
+ this.connect();
381
+ }
382
+ }
383
+ /**
384
+ * Update Better-Auth cookies (call this when session changes)
385
+ */
386
+ setBetterAuthCookies(cookies) {
387
+ this.betterAuthCookies = cookies;
388
+ if (this.isConnected) {
389
+ this.connect();
390
+ }
391
+ }
392
+ /**
393
+ * Update Better-Auth API key (call this when API key changes)
394
+ */
395
+ setApiKey(apiKey) {
396
+ this.apiKey = apiKey;
397
+ if (this.isConnected) {
398
+ this.connect();
399
+ }
400
+ }
401
+ connect() {
402
+ if (typeof window === "undefined") {
403
+ return;
404
+ }
405
+ this.isExplicitlyClosed = false;
406
+ if (this.ws) {
407
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
408
+ return;
409
+ }
410
+ try {
411
+ this.ws.close();
412
+ } catch (e) {
413
+ }
414
+ this.ws = null;
415
+ }
416
+ try {
417
+ this.ws = new WebSocket(this.url);
418
+ } catch (e) {
419
+ this.handleClose();
420
+ return;
421
+ }
422
+ this.ws.onopen = async () => {
423
+ console.log("Connected to Archlast Server");
424
+ this.isConnected = true;
425
+ this.reconnectAttempts = 0;
426
+ const headers = {};
427
+ if (this.appId) {
428
+ headers["x-archlast-app-id"] = this.appId;
429
+ }
430
+ if (this.apiKey) {
431
+ headers["x-api-key"] = this.apiKey;
432
+ }
433
+ const cookies = {};
434
+ if (this.betterAuthCookies) {
435
+ Object.assign(cookies, this.betterAuthCookies);
436
+ }
437
+ this.sendMessage({
438
+ type: "connect",
439
+ auth: {
440
+ headers: Object.keys(headers).length > 0 ? headers : void 0,
441
+ cookies: Object.keys(cookies).length > 0 ? cookies : void 0
442
+ }
443
+ });
444
+ if (this.isAdmin) {
445
+ this.sendMessage({
446
+ type: "subscribe_logs"
447
+ });
448
+ this.sendMessage({
449
+ type: "subscribe_admin_events"
450
+ });
451
+ } else {
452
+ try {
453
+ const state = await this.auth.getState();
454
+ console.log("Auth state:", state);
455
+ if (state.isAuthenticated) {
456
+ this.sendMessage({
457
+ type: "subscribe_logs"
458
+ });
459
+ this.sendMessage({
460
+ type: "subscribe_admin_events"
461
+ });
462
+ }
463
+ } catch (err) {
464
+ }
465
+ }
466
+ this.flushQueue();
467
+ this.resubscribeAll();
468
+ this.startHeartbeat();
469
+ };
470
+ this.ws.onmessage = (event) => this.handleMessage(event);
471
+ this.ws.onclose = () => this.handleClose();
472
+ this.ws.onerror = (err) => {
473
+ if (!this.isExplicitlyClosed) {
474
+ console.error("WebSocket error:", err);
475
+ this.ws?.close();
476
+ }
477
+ };
478
+ }
479
+ startHeartbeat() {
480
+ this.stopHeartbeat();
481
+ this.heartbeatInterval = setInterval(() => {
482
+ if (this.isConnected) {
483
+ this.sendMessage({ type: "ping" });
484
+ }
485
+ }, 3e4);
486
+ }
487
+ stopHeartbeat() {
488
+ if (this.heartbeatInterval) {
489
+ clearInterval(this.heartbeatInterval);
490
+ this.heartbeatInterval = null;
491
+ }
492
+ }
493
+ handleClose() {
494
+ this.isConnected = false;
495
+ this.sessionId = null;
496
+ this.disconnectionListeners.forEach((l) => l("Disconnected"));
497
+ if (this.isExplicitlyClosed) return;
498
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
499
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
500
+ console.log(`Disconnected. Reconnecting in ${delay}ms...`);
501
+ this.reconnectTimeout = setTimeout(() => {
502
+ this.reconnectAttempts++;
503
+ this.connect();
504
+ }, delay);
505
+ }
506
+ }
507
+ handleMessage(event) {
508
+ try {
509
+ const msg = JSON.parse(event.data);
510
+ if (msg.type === "data" || msg.type === "patch") {
511
+ const queryId = msg.queryId;
512
+ const data = msg.data || msg.patch;
513
+ const listeners = this.listeners.get(queryId);
514
+ if (listeners) {
515
+ listeners.forEach((l) => l(data));
516
+ }
517
+ } else if (msg.type === "mutationResponse") {
518
+ const pending = this.pendingMutations.get(msg.mutationId);
519
+ if (pending) {
520
+ clearTimeout(pending.timeout);
521
+ this.pendingMutations.delete(msg.mutationId);
522
+ if (msg.success) {
523
+ pending.resolve(msg.data);
524
+ } else {
525
+ pending.reject(new Error(msg.error));
526
+ }
527
+ }
528
+ } else if (msg.type === "connected") {
529
+ console.log("Session established:", msg.sessionId);
530
+ this.sessionId = msg.sessionId;
531
+ this.isConnected = true;
532
+ this.reconnectAttempts = 0;
533
+ this.connectionListeners.forEach((l) => l(msg.sessionId));
534
+ } else if (msg.type === "error") {
535
+ console.error("Server error:", msg.message);
536
+ this.errorListeners.forEach((l) => l(msg.message));
537
+ if (msg.message === "Authentication required") {
538
+ this.isExplicitlyClosed = true;
539
+ this.ws?.close();
540
+ }
541
+ }
542
+ } catch (e) {
543
+ console.error("Error handling message:", e);
544
+ }
545
+ }
546
+ getWs() {
547
+ return this.ws;
548
+ }
549
+ onConnected(cb) {
550
+ this.connectionListeners.add(cb);
551
+ return () => this.connectionListeners.delete(cb);
552
+ }
553
+ onDisconnected(cb) {
554
+ this.disconnectionListeners.add(cb);
555
+ return () => this.disconnectionListeners.delete(cb);
556
+ }
557
+ onError(cb) {
558
+ this.errorListeners.add(cb);
559
+ return () => this.errorListeners.delete(cb);
560
+ }
561
+ getStatus() {
562
+ return this.isConnected ? "connected" : "disconnected";
563
+ }
564
+ getSessionId() {
565
+ return this.sessionId;
566
+ }
567
+ sendMessage(msg) {
568
+ const data = JSON.stringify(msg);
569
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
570
+ this.ws.send(data);
571
+ } else {
572
+ this.messageQueue.push(data);
573
+ }
574
+ }
575
+ flushQueue() {
576
+ while (this.messageQueue.length > 0) {
577
+ const msg = this.messageQueue.shift();
578
+ if (msg) this.ws?.send(msg);
579
+ }
580
+ }
581
+ resubscribeAll() {
582
+ for (const [queryId, sub] of Array.from(this.subscriptions.entries())) {
583
+ this.sendMessage({
584
+ type: "query",
585
+ queryId,
586
+ name: sub.name,
587
+ args: sub.args
588
+ });
589
+ }
590
+ }
591
+ getQueryId(name, args) {
592
+ const stableArgs = JSON.stringify(args, Object.keys(args || {}).sort());
593
+ return `${name}:${stableArgs}`;
594
+ }
595
+ subscribe(queryName, args, onUpdate) {
596
+ const queryId = this.getQueryId(queryName, args);
597
+ if (!this.listeners.has(queryId)) {
598
+ this.listeners.set(queryId, /* @__PURE__ */ new Set());
599
+ this.subscriptions.set(queryId, { name: queryName, args });
600
+ this.sendMessage({
601
+ type: "query",
602
+ queryId,
603
+ name: queryName,
604
+ args
605
+ });
606
+ }
607
+ this.listeners.get(queryId).add(onUpdate);
608
+ return () => {
609
+ const set = this.listeners.get(queryId);
610
+ if (set) {
611
+ set.delete(onUpdate);
612
+ if (set.size === 0) {
613
+ this.listeners.delete(queryId);
614
+ this.subscriptions.delete(queryId);
615
+ }
616
+ }
617
+ };
618
+ }
619
+ async fetch(name, args) {
620
+ const queryId = this.getQueryId(name, args);
621
+ return new Promise((resolve, reject) => {
622
+ let resolved = false;
623
+ const unsubscribe = this.subscribe(name, args, (data) => {
624
+ if (!resolved) {
625
+ resolved = true;
626
+ unsubscribe();
627
+ resolve(data);
628
+ }
629
+ });
630
+ setTimeout(() => {
631
+ if (!resolved) {
632
+ resolved = true;
633
+ unsubscribe();
634
+ reject(new Error("Timeout waiting for data"));
635
+ }
636
+ }, 5e3);
637
+ });
638
+ }
639
+ async mutate(name, args) {
640
+ return new Promise((resolve, reject) => {
641
+ const mutationId = Math.random().toString(36).slice(2);
642
+ const timeout = setTimeout(() => {
643
+ this.pendingMutations.delete(mutationId);
644
+ reject(new Error("Mutation timeout"));
645
+ }, 1e4);
646
+ this.pendingMutations.set(mutationId, { resolve, reject, timeout });
647
+ this.sendMessage({
648
+ type: "mutation",
649
+ mutationId,
650
+ name,
651
+ args
652
+ });
653
+ });
654
+ }
655
+ close() {
656
+ this.isExplicitlyClosed = true;
657
+ if (this.reconnectTimeout) {
658
+ clearTimeout(this.reconnectTimeout);
659
+ this.reconnectTimeout = null;
660
+ }
661
+ if (this.ws) {
662
+ this.ws.onclose = null;
663
+ this.ws.onopen = null;
664
+ this.ws.onerror = null;
665
+ this.ws.onmessage = null;
666
+ this.ws.close();
667
+ this.ws = null;
668
+ }
669
+ this.isConnected = false;
670
+ this.stopHeartbeat();
671
+ }
672
+ };
673
+ // Annotate the CommonJS export names for ESM import in node:
674
+ 0 && (module.exports = {
675
+ ArchlastClient
676
+ });
677
+ //# sourceMappingURL=client.cjs.map