@vaiftechnologies/vaif-client 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.
package/dist/index.mjs ADDED
@@ -0,0 +1,2391 @@
1
+ // src/lib/database.ts
2
+ var VaifDatabase = class {
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ /**
7
+ * Start a query on a table
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const users = await vaif.db.from('users').select('*').execute();
12
+ * ```
13
+ */
14
+ from(table) {
15
+ return new QueryBuilder(this.client, table);
16
+ }
17
+ /**
18
+ * Execute raw SQL query (admin only)
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const result = await vaif.db.raw('SELECT * FROM users WHERE id = $1', [userId]);
23
+ * ```
24
+ */
25
+ async raw(sql, params) {
26
+ const response = await this.client.request("/db/query", {
27
+ method: "POST",
28
+ body: { sql, params }
29
+ });
30
+ if (response.error) {
31
+ return {
32
+ data: [],
33
+ error: {
34
+ message: response.error.message,
35
+ code: response.error.code ?? "QUERY_ERROR"
36
+ },
37
+ status: response.status
38
+ };
39
+ }
40
+ return {
41
+ data: response.data?.rows ?? [],
42
+ error: null,
43
+ status: response.status
44
+ };
45
+ }
46
+ /**
47
+ * Execute a function (stored procedure)
48
+ */
49
+ async rpc(functionName, params) {
50
+ const response = await this.client.request(`/db/rpc/${functionName}`, {
51
+ method: "POST",
52
+ body: params ?? {}
53
+ });
54
+ return {
55
+ data: response.data,
56
+ error: response.error ? {
57
+ message: response.error.message,
58
+ code: response.error.code ?? "RPC_ERROR"
59
+ } : null,
60
+ status: response.status
61
+ };
62
+ }
63
+ };
64
+ var QueryBuilder = class {
65
+ constructor(client, table) {
66
+ this.queryType = "select";
67
+ this.selectColumns = "*";
68
+ this.insertData = null;
69
+ this.updateData = null;
70
+ this.filters = [];
71
+ this.orderSpecs = [];
72
+ this.limitCount = null;
73
+ this.offsetCount = null;
74
+ this.returning = null;
75
+ this.countOption = null;
76
+ this.negateNext = false;
77
+ this.onConflictColumns = null;
78
+ this.onConflictAction = null;
79
+ this.onConflictUpdate = null;
80
+ this.client = client;
81
+ this.table = table;
82
+ }
83
+ /**
84
+ * Select columns
85
+ */
86
+ select(columns = "*") {
87
+ this.queryType = "select";
88
+ this.selectColumns = Array.isArray(columns) ? columns.join(", ") : columns;
89
+ return this;
90
+ }
91
+ /**
92
+ * Insert data
93
+ */
94
+ insert(data) {
95
+ this.queryType = "insert";
96
+ this.insertData = data;
97
+ return this;
98
+ }
99
+ /**
100
+ * Update data
101
+ */
102
+ update(data) {
103
+ this.queryType = "update";
104
+ this.updateData = data;
105
+ return this;
106
+ }
107
+ /**
108
+ * Delete rows
109
+ */
110
+ delete() {
111
+ this.queryType = "delete";
112
+ return this;
113
+ }
114
+ /**
115
+ * Handle insert conflicts (upsert)
116
+ */
117
+ onConflict(columns) {
118
+ this.onConflictColumns = Array.isArray(columns) ? columns : [columns];
119
+ return this;
120
+ }
121
+ /**
122
+ * Do nothing on conflict
123
+ */
124
+ doNothing() {
125
+ this.onConflictAction = "nothing";
126
+ return this;
127
+ }
128
+ /**
129
+ * Update on conflict
130
+ */
131
+ doUpdate(data) {
132
+ this.onConflictAction = "update";
133
+ this.onConflictUpdate = data;
134
+ return this;
135
+ }
136
+ // Filter methods
137
+ eq(column, value) {
138
+ this.addFilter(column, this.negateNext ? "neq" : "eq", value);
139
+ this.negateNext = false;
140
+ return this;
141
+ }
142
+ neq(column, value) {
143
+ this.addFilter(column, this.negateNext ? "eq" : "neq", value);
144
+ this.negateNext = false;
145
+ return this;
146
+ }
147
+ gt(column, value) {
148
+ this.addFilter(column, this.negateNext ? "lte" : "gt", value);
149
+ this.negateNext = false;
150
+ return this;
151
+ }
152
+ gte(column, value) {
153
+ this.addFilter(column, this.negateNext ? "lt" : "gte", value);
154
+ this.negateNext = false;
155
+ return this;
156
+ }
157
+ lt(column, value) {
158
+ this.addFilter(column, this.negateNext ? "gte" : "lt", value);
159
+ this.negateNext = false;
160
+ return this;
161
+ }
162
+ lte(column, value) {
163
+ this.addFilter(column, this.negateNext ? "gt" : "lte", value);
164
+ this.negateNext = false;
165
+ return this;
166
+ }
167
+ like(column, pattern) {
168
+ this.addFilter(column, this.negateNext ? "nlike" : "like", pattern);
169
+ this.negateNext = false;
170
+ return this;
171
+ }
172
+ ilike(column, pattern) {
173
+ this.addFilter(column, this.negateNext ? "nilike" : "ilike", pattern);
174
+ this.negateNext = false;
175
+ return this;
176
+ }
177
+ is(column, value) {
178
+ this.addFilter(column, this.negateNext ? "isnot" : "is", value);
179
+ this.negateNext = false;
180
+ return this;
181
+ }
182
+ in(column, values) {
183
+ this.addFilter(column, this.negateNext ? "nin" : "in", values);
184
+ this.negateNext = false;
185
+ return this;
186
+ }
187
+ contains(column, value) {
188
+ this.addFilter(column, "cs", value);
189
+ this.negateNext = false;
190
+ return this;
191
+ }
192
+ containedBy(column, value) {
193
+ this.addFilter(column, "cd", value);
194
+ this.negateNext = false;
195
+ return this;
196
+ }
197
+ overlaps(column, value) {
198
+ this.addFilter(column, "ov", value);
199
+ this.negateNext = false;
200
+ return this;
201
+ }
202
+ textSearch(column, query, options) {
203
+ this.addFilter(column, "fts", query, options?.config);
204
+ this.negateNext = false;
205
+ return this;
206
+ }
207
+ /**
208
+ * Negate the next filter
209
+ */
210
+ get not() {
211
+ this.negateNext = true;
212
+ return this;
213
+ }
214
+ /**
215
+ * Combine filters with OR
216
+ */
217
+ or(filters) {
218
+ this.filters.push({ type: "or", value: filters });
219
+ return this;
220
+ }
221
+ /**
222
+ * Order results
223
+ */
224
+ order(column, options = {}) {
225
+ this.orderSpecs.push({
226
+ column,
227
+ ascending: options.ascending ?? true,
228
+ nullsFirst: options.nullsFirst
229
+ });
230
+ return this;
231
+ }
232
+ /**
233
+ * Limit results
234
+ */
235
+ limit(count) {
236
+ this.limitCount = count;
237
+ return this;
238
+ }
239
+ /**
240
+ * Offset results
241
+ */
242
+ offset(count) {
243
+ this.offsetCount = count;
244
+ return this;
245
+ }
246
+ /**
247
+ * Range of results
248
+ */
249
+ range(from, to) {
250
+ this.offsetCount = from;
251
+ this.limitCount = to - from + 1;
252
+ return this;
253
+ }
254
+ /**
255
+ * Include count
256
+ */
257
+ count(type = "exact") {
258
+ this.countOption = type;
259
+ return this;
260
+ }
261
+ /**
262
+ * Return single result
263
+ */
264
+ async single() {
265
+ this.limitCount = 1;
266
+ const result = await this.execute();
267
+ if (result.error) {
268
+ return {
269
+ data: null,
270
+ error: result.error,
271
+ status: result.status
272
+ };
273
+ }
274
+ if (!result.data || result.data.length === 0) {
275
+ return {
276
+ data: null,
277
+ error: {
278
+ message: "No rows returned",
279
+ code: "PGRST116"
280
+ },
281
+ status: 406
282
+ };
283
+ }
284
+ return {
285
+ data: result.data[0],
286
+ error: null,
287
+ status: result.status
288
+ };
289
+ }
290
+ /**
291
+ * Return first result or null
292
+ */
293
+ async maybeSingle() {
294
+ this.limitCount = 1;
295
+ const result = await this.execute();
296
+ if (result.error) {
297
+ return {
298
+ data: null,
299
+ error: result.error,
300
+ status: result.status
301
+ };
302
+ }
303
+ return {
304
+ data: result.data?.[0] ?? null,
305
+ error: null,
306
+ status: result.status
307
+ };
308
+ }
309
+ /**
310
+ * Execute the query
311
+ */
312
+ async execute() {
313
+ const body = this.buildQuery();
314
+ const response = await this.client.request(
315
+ `/db/${this.table}`,
316
+ {
317
+ method: this.getHttpMethod(),
318
+ body
319
+ }
320
+ );
321
+ if (response.error) {
322
+ return {
323
+ data: [],
324
+ error: {
325
+ message: response.error.message,
326
+ code: response.error.code ?? "QUERY_ERROR",
327
+ details: response.error.details?.toString()
328
+ },
329
+ status: response.status
330
+ };
331
+ }
332
+ return {
333
+ data: response.data?.data ?? [],
334
+ error: null,
335
+ status: response.status,
336
+ count: response.data?.count
337
+ };
338
+ }
339
+ // Private helpers
340
+ addFilter(column, operator, value, extra) {
341
+ this.filters.push({ type: "filter", column, operator, value, extra });
342
+ }
343
+ getHttpMethod() {
344
+ switch (this.queryType) {
345
+ case "insert":
346
+ return "POST";
347
+ case "update":
348
+ return "PATCH";
349
+ case "delete":
350
+ return "DELETE";
351
+ default:
352
+ return "GET";
353
+ }
354
+ }
355
+ buildQuery() {
356
+ const query = {
357
+ type: this.queryType
358
+ };
359
+ if (this.queryType === "select") {
360
+ query.select = this.selectColumns;
361
+ }
362
+ if (this.queryType === "insert" && this.insertData) {
363
+ query.data = this.insertData;
364
+ if (this.onConflictColumns) {
365
+ query.onConflict = {
366
+ columns: this.onConflictColumns,
367
+ action: this.onConflictAction,
368
+ update: this.onConflictUpdate
369
+ };
370
+ }
371
+ }
372
+ if (this.queryType === "update" && this.updateData) {
373
+ query.data = this.updateData;
374
+ }
375
+ if (this.filters.length > 0) {
376
+ query.filters = this.filters;
377
+ }
378
+ if (this.orderSpecs.length > 0) {
379
+ query.order = this.orderSpecs;
380
+ }
381
+ if (this.limitCount !== null) {
382
+ query.limit = this.limitCount;
383
+ }
384
+ if (this.offsetCount !== null) {
385
+ query.offset = this.offsetCount;
386
+ }
387
+ if (this.countOption) {
388
+ query.count = this.countOption;
389
+ }
390
+ if (this.returning) {
391
+ query.returning = this.returning;
392
+ }
393
+ return query;
394
+ }
395
+ };
396
+
397
+ // src/lib/realtime.ts
398
+ var VaifRealtime = class {
399
+ constructor(client) {
400
+ this.socket = null;
401
+ this.channels = /* @__PURE__ */ new Map();
402
+ this.reconnectAttempts = 0;
403
+ this.maxReconnectAttempts = 10;
404
+ this.reconnectInterval = 1e3;
405
+ this.heartbeatInterval = null;
406
+ this.client = client;
407
+ }
408
+ /**
409
+ * Connect to realtime server
410
+ */
411
+ connect(options = {}) {
412
+ if (this.socket?.readyState === WebSocket.OPEN) {
413
+ return;
414
+ }
415
+ const config = this.client.getConfig();
416
+ const url = new URL(config.realtimeUrl);
417
+ url.searchParams.set("apikey", config.apiKey);
418
+ url.searchParams.set("project", config.projectId);
419
+ const token = this.client.getAccessToken();
420
+ if (token) {
421
+ url.searchParams.set("token", token);
422
+ }
423
+ this.socket = new WebSocket(url.toString());
424
+ this.socket.onopen = () => {
425
+ this.client.debug("Realtime connected");
426
+ this.reconnectAttempts = 0;
427
+ if (options.heartbeat !== false) {
428
+ this.startHeartbeat(options.heartbeatInterval ?? 3e4);
429
+ }
430
+ this.channels.forEach((channel) => {
431
+ channel.resubscribe();
432
+ });
433
+ };
434
+ this.socket.onmessage = (event) => {
435
+ try {
436
+ const message = JSON.parse(event.data);
437
+ this.handleMessage(message);
438
+ } catch {
439
+ this.client.debug("Failed to parse realtime message");
440
+ }
441
+ };
442
+ this.socket.onclose = () => {
443
+ this.client.debug("Realtime disconnected");
444
+ this.stopHeartbeat();
445
+ if (options.autoReconnect !== false && this.reconnectAttempts < (options.maxReconnectAttempts ?? this.maxReconnectAttempts)) {
446
+ this.reconnectAttempts++;
447
+ const delay = Math.min(
448
+ (options.reconnectInterval ?? this.reconnectInterval) * this.reconnectAttempts,
449
+ 3e4
450
+ );
451
+ setTimeout(() => this.connect(options), delay);
452
+ }
453
+ };
454
+ this.socket.onerror = (error) => {
455
+ this.client.debug("Realtime error", error);
456
+ };
457
+ }
458
+ /**
459
+ * Disconnect from realtime server
460
+ */
461
+ disconnect() {
462
+ this.stopHeartbeat();
463
+ this.channels.forEach((channel) => channel.unsubscribe());
464
+ this.channels.clear();
465
+ this.socket?.close();
466
+ this.socket = null;
467
+ }
468
+ /**
469
+ * Create or get a channel
470
+ */
471
+ channel(name) {
472
+ let channel = this.channels.get(name);
473
+ if (!channel) {
474
+ channel = new RealtimeChannel(this, name);
475
+ this.channels.set(name, channel);
476
+ }
477
+ return channel;
478
+ }
479
+ /**
480
+ * Remove a channel
481
+ */
482
+ removeChannel(name) {
483
+ const channel = this.channels.get(name);
484
+ if (channel) {
485
+ channel.unsubscribe();
486
+ this.channels.delete(name);
487
+ }
488
+ }
489
+ /**
490
+ * Send message to server
491
+ */
492
+ send(message) {
493
+ if (this.socket?.readyState === WebSocket.OPEN) {
494
+ this.socket.send(JSON.stringify(message));
495
+ }
496
+ }
497
+ /**
498
+ * Get WebSocket instance
499
+ */
500
+ getSocket() {
501
+ return this.socket;
502
+ }
503
+ handleMessage(message) {
504
+ const channel = this.channels.get(message.topic);
505
+ if (channel) {
506
+ channel.handleMessage(message);
507
+ }
508
+ }
509
+ startHeartbeat(interval) {
510
+ this.heartbeatInterval = setInterval(() => {
511
+ this.send({ topic: "phoenix", event: "heartbeat", payload: {} });
512
+ }, interval);
513
+ }
514
+ stopHeartbeat() {
515
+ if (this.heartbeatInterval) {
516
+ clearInterval(this.heartbeatInterval);
517
+ this.heartbeatInterval = null;
518
+ }
519
+ }
520
+ };
521
+ var RealtimeChannel = class {
522
+ constructor(realtime, name) {
523
+ this.subscriptions = /* @__PURE__ */ new Map();
524
+ this.status = "CLOSED";
525
+ this.presenceState = /* @__PURE__ */ new Map();
526
+ this.realtime = realtime;
527
+ this.name = name;
528
+ }
529
+ on(event, filter, callback) {
530
+ const key = `${event}:${JSON.stringify(filter)}`;
531
+ const callbacks = this.subscriptions.get(key) ?? [];
532
+ callbacks.push(callback);
533
+ this.subscriptions.set(key, callbacks);
534
+ return this;
535
+ }
536
+ /**
537
+ * Subscribe to channel
538
+ */
539
+ subscribe(callback) {
540
+ this.realtime.send({
541
+ topic: this.name,
542
+ event: "phx_join",
543
+ payload: {
544
+ subscriptions: Array.from(this.subscriptions.keys())
545
+ },
546
+ ref: `${this.name}-join`
547
+ });
548
+ this.status = "SUBSCRIBED";
549
+ callback?.(this.status);
550
+ return this;
551
+ }
552
+ /**
553
+ * Unsubscribe from channel
554
+ */
555
+ unsubscribe() {
556
+ this.realtime.send({
557
+ topic: this.name,
558
+ event: "phx_leave",
559
+ payload: {},
560
+ ref: `${this.name}-leave`
561
+ });
562
+ this.status = "CLOSED";
563
+ this.subscriptions.clear();
564
+ }
565
+ /**
566
+ * Broadcast message to channel
567
+ */
568
+ broadcast(event, payload) {
569
+ this.realtime.send({
570
+ topic: this.name,
571
+ event: "broadcast",
572
+ payload: { event, payload }
573
+ });
574
+ }
575
+ /**
576
+ * Track presence
577
+ */
578
+ track(payload) {
579
+ this.realtime.send({
580
+ topic: this.name,
581
+ event: "presence",
582
+ payload: { type: "track", payload }
583
+ });
584
+ }
585
+ /**
586
+ * Untrack presence
587
+ */
588
+ untrack() {
589
+ this.realtime.send({
590
+ topic: this.name,
591
+ event: "presence",
592
+ payload: { type: "untrack" }
593
+ });
594
+ }
595
+ /**
596
+ * Get current presence state
597
+ */
598
+ getPresenceState() {
599
+ return this.presenceState;
600
+ }
601
+ /**
602
+ * Handle incoming message
603
+ */
604
+ handleMessage(message) {
605
+ this.subscriptions.forEach((callbacks, key) => {
606
+ const [event] = key.split(":");
607
+ if (message.event === event || message.event === "broadcast") {
608
+ callbacks.forEach((callback) => callback(message.payload));
609
+ }
610
+ });
611
+ }
612
+ /**
613
+ * Resubscribe after reconnect
614
+ */
615
+ resubscribe() {
616
+ if (this.subscriptions.size > 0) {
617
+ this.subscribe();
618
+ }
619
+ }
620
+ };
621
+
622
+ // src/lib/storage.ts
623
+ var VaifStorage = class {
624
+ constructor(client) {
625
+ this.client = client;
626
+ }
627
+ /**
628
+ * Get a storage bucket
629
+ */
630
+ from(bucket) {
631
+ return new StorageBucket(this.client, bucket);
632
+ }
633
+ /**
634
+ * List all buckets
635
+ */
636
+ async listBuckets() {
637
+ const response = await this.client.request("/storage/buckets");
638
+ return {
639
+ data: response.data,
640
+ error: response.error ? {
641
+ message: response.error.message,
642
+ statusCode: String(response.status),
643
+ error: response.error.code ?? "STORAGE_ERROR"
644
+ } : null
645
+ };
646
+ }
647
+ /**
648
+ * Create a bucket
649
+ */
650
+ async createBucket(name, options = {}) {
651
+ const response = await this.client.request("/storage/buckets", {
652
+ method: "POST",
653
+ body: { name, ...options }
654
+ });
655
+ return {
656
+ data: response.data,
657
+ error: response.error ? {
658
+ message: response.error.message,
659
+ statusCode: String(response.status),
660
+ error: response.error.code ?? "STORAGE_ERROR"
661
+ } : null
662
+ };
663
+ }
664
+ /**
665
+ * Delete a bucket
666
+ */
667
+ async deleteBucket(name) {
668
+ const response = await this.client.request(`/storage/buckets/${name}`, {
669
+ method: "DELETE"
670
+ });
671
+ return {
672
+ error: response.error ? {
673
+ message: response.error.message,
674
+ statusCode: String(response.status),
675
+ error: response.error.code ?? "STORAGE_ERROR"
676
+ } : null
677
+ };
678
+ }
679
+ };
680
+ var StorageBucket = class {
681
+ constructor(client, bucket) {
682
+ this.client = client;
683
+ this.bucket = bucket;
684
+ }
685
+ /**
686
+ * Upload a file
687
+ */
688
+ async upload(path, file, options = {}) {
689
+ const formData = new FormData();
690
+ if (file instanceof File) {
691
+ formData.append("file", file);
692
+ } else if (file instanceof Blob) {
693
+ formData.append("file", file, path.split("/").pop() ?? "file");
694
+ } else if (file instanceof ArrayBuffer) {
695
+ formData.append("file", new Blob([file]), path.split("/").pop() ?? "file");
696
+ } else if (typeof file === "string") {
697
+ const blob = await fetch(file).then((r) => r.blob());
698
+ formData.append("file", blob, path.split("/").pop() ?? "file");
699
+ }
700
+ if (options.contentType) {
701
+ formData.append("contentType", options.contentType);
702
+ }
703
+ if (options.cacheControl) {
704
+ formData.append("cacheControl", options.cacheControl);
705
+ }
706
+ if (options.upsert) {
707
+ formData.append("upsert", "true");
708
+ }
709
+ if (options.metadata) {
710
+ formData.append("metadata", JSON.stringify(options.metadata));
711
+ }
712
+ const response = await this.client.request(
713
+ `/storage/${this.bucket}/${path}`,
714
+ {
715
+ method: "POST",
716
+ headers: {
717
+ // Don't set Content-Type, let browser set it with boundary
718
+ },
719
+ body: formData
720
+ }
721
+ );
722
+ return {
723
+ data: response.data,
724
+ error: response.error ? {
725
+ message: response.error.message,
726
+ statusCode: String(response.status),
727
+ error: response.error.code ?? "UPLOAD_ERROR"
728
+ } : null
729
+ };
730
+ }
731
+ /**
732
+ * Download a file
733
+ */
734
+ async download(path, options = {}) {
735
+ const params = {};
736
+ if (options.transform) {
737
+ if (options.transform.width) params.width = String(options.transform.width);
738
+ if (options.transform.height) params.height = String(options.transform.height);
739
+ if (options.transform.resize) params.resize = options.transform.resize;
740
+ if (options.transform.format) params.format = options.transform.format;
741
+ if (options.transform.quality) params.quality = String(options.transform.quality);
742
+ }
743
+ const url = new URL(`/storage/${this.bucket}/${path}`, this.client.getConfig().apiUrl);
744
+ Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
745
+ try {
746
+ const response = await fetch(url.toString(), {
747
+ headers: {
748
+ apikey: this.client.getConfig().apiKey,
749
+ Authorization: `Bearer ${this.client.getAccessToken() ?? this.client.getConfig().apiKey}`
750
+ }
751
+ });
752
+ if (!response.ok) {
753
+ return {
754
+ data: null,
755
+ error: {
756
+ message: `Download failed: ${response.statusText}`,
757
+ statusCode: String(response.status),
758
+ error: "DOWNLOAD_ERROR"
759
+ }
760
+ };
761
+ }
762
+ return { data: await response.blob(), error: null };
763
+ } catch (err) {
764
+ return {
765
+ data: null,
766
+ error: {
767
+ message: err instanceof Error ? err.message : "Download failed",
768
+ statusCode: "0",
769
+ error: "NETWORK_ERROR"
770
+ }
771
+ };
772
+ }
773
+ }
774
+ /**
775
+ * Get public URL for a file
776
+ */
777
+ getPublicUrl(path) {
778
+ const config = this.client.getConfig();
779
+ return {
780
+ data: {
781
+ publicUrl: `${config.apiUrl}/storage/${this.bucket}/${path}`
782
+ }
783
+ };
784
+ }
785
+ /**
786
+ * Create signed URL
787
+ */
788
+ async createSignedUrl(path, options) {
789
+ const response = await this.client.request(
790
+ `/storage/${this.bucket}/${path}/sign`,
791
+ {
792
+ method: "POST",
793
+ body: {
794
+ expiresIn: options.expiresIn,
795
+ transform: options.transform,
796
+ download: options.download
797
+ }
798
+ }
799
+ );
800
+ return {
801
+ data: response.data,
802
+ error: response.error ? {
803
+ message: response.error.message,
804
+ statusCode: String(response.status),
805
+ error: response.error.code ?? "SIGN_ERROR"
806
+ } : null
807
+ };
808
+ }
809
+ /**
810
+ * List files in a path
811
+ */
812
+ async list(path = "", options = {}) {
813
+ const response = await this.client.request(
814
+ `/storage/${this.bucket}/list`,
815
+ {
816
+ method: "POST",
817
+ body: {
818
+ prefix: path,
819
+ limit: options.limit,
820
+ offset: options.offset,
821
+ search: options.search,
822
+ sortBy: options.sortBy
823
+ }
824
+ }
825
+ );
826
+ return {
827
+ data: response.data,
828
+ error: response.error ? {
829
+ message: response.error.message,
830
+ statusCode: String(response.status),
831
+ error: response.error.code ?? "LIST_ERROR"
832
+ } : null
833
+ };
834
+ }
835
+ /**
836
+ * Move/rename a file
837
+ */
838
+ async move(fromPath, toPath) {
839
+ const response = await this.client.request(`/storage/${this.bucket}/move`, {
840
+ method: "POST",
841
+ body: { from: fromPath, to: toPath }
842
+ });
843
+ return {
844
+ error: response.error ? {
845
+ message: response.error.message,
846
+ statusCode: String(response.status),
847
+ error: response.error.code ?? "MOVE_ERROR"
848
+ } : null
849
+ };
850
+ }
851
+ /**
852
+ * Copy a file
853
+ */
854
+ async copy(fromPath, toPath) {
855
+ const response = await this.client.request(`/storage/${this.bucket}/copy`, {
856
+ method: "POST",
857
+ body: { from: fromPath, to: toPath }
858
+ });
859
+ return {
860
+ error: response.error ? {
861
+ message: response.error.message,
862
+ statusCode: String(response.status),
863
+ error: response.error.code ?? "COPY_ERROR"
864
+ } : null
865
+ };
866
+ }
867
+ /**
868
+ * Delete a file
869
+ */
870
+ async remove(paths) {
871
+ const pathArray = Array.isArray(paths) ? paths : [paths];
872
+ const response = await this.client.request(`/storage/${this.bucket}/delete`, {
873
+ method: "DELETE",
874
+ body: { paths: pathArray }
875
+ });
876
+ return {
877
+ error: response.error ? {
878
+ message: response.error.message,
879
+ statusCode: String(response.status),
880
+ error: response.error.code ?? "DELETE_ERROR"
881
+ } : null
882
+ };
883
+ }
884
+ };
885
+
886
+ // src/lib/functions.ts
887
+ var VaifFunctions = class {
888
+ constructor(client) {
889
+ this.client = client;
890
+ }
891
+ /**
892
+ * Invoke an edge function
893
+ *
894
+ * @example
895
+ * ```typescript
896
+ * const { data, error } = await vaif.functions.invoke('send-email', {
897
+ * body: { to: 'user@example.com', subject: 'Hello' }
898
+ * });
899
+ * ```
900
+ */
901
+ async invoke(functionName, options = {}) {
902
+ const config = this.client.getConfig();
903
+ const url = new URL(
904
+ `/functions/${functionName}`,
905
+ config.apiUrl
906
+ );
907
+ const headers = {
908
+ "X-Project-ID": config.projectId,
909
+ apikey: config.apiKey,
910
+ ...options.headers
911
+ };
912
+ const token = this.client.getAccessToken();
913
+ if (token) {
914
+ headers.Authorization = `Bearer ${token}`;
915
+ }
916
+ if (options.body !== void 0) {
917
+ if (options.body instanceof FormData) {
918
+ } else if (options.body instanceof Blob) {
919
+ headers["Content-Type"] = options.body.type || "application/octet-stream";
920
+ } else {
921
+ headers["Content-Type"] = "application/json";
922
+ }
923
+ }
924
+ const timeoutMs = options.timeout ?? config.timeout;
925
+ const controller = new AbortController();
926
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
927
+ try {
928
+ const response = await fetch(url.toString(), {
929
+ method: options.method ?? "POST",
930
+ headers,
931
+ body: this.serializeBody(options.body),
932
+ signal: controller.signal
933
+ });
934
+ clearTimeout(timeoutId);
935
+ const contentType = response.headers.get("content-type");
936
+ let data = null;
937
+ if (contentType?.includes("application/json")) {
938
+ data = await response.json();
939
+ } else if (contentType?.includes("text/")) {
940
+ data = await response.text();
941
+ } else if (response.body) {
942
+ data = await response.blob();
943
+ }
944
+ if (!response.ok) {
945
+ const error = {
946
+ message: typeof data === "object" && data !== null && "message" in data ? data.message : `Function invocation failed with status ${response.status}`,
947
+ status: response.status,
948
+ name: functionName
949
+ };
950
+ return { data: null, error };
951
+ }
952
+ return { data, error: null };
953
+ } catch (err) {
954
+ clearTimeout(timeoutId);
955
+ const error = {
956
+ message: err instanceof Error ? err.name === "AbortError" ? "Function invocation timed out" : err.message : "Function invocation failed",
957
+ status: 0,
958
+ name: functionName
959
+ };
960
+ return { data: null, error };
961
+ }
962
+ }
963
+ /**
964
+ * Create a function URL for direct invocation
965
+ */
966
+ createUrl(functionName) {
967
+ const config = this.client.getConfig();
968
+ return `${config.apiUrl}/functions/${functionName}`;
969
+ }
970
+ /**
971
+ * Get list of deployed functions
972
+ */
973
+ async list() {
974
+ const response = await this.client.request("/functions");
975
+ return {
976
+ data: response.data,
977
+ error: response.error ? {
978
+ message: response.error.message,
979
+ status: response.status,
980
+ name: "list"
981
+ } : null
982
+ };
983
+ }
984
+ /**
985
+ * Get function details
986
+ */
987
+ async get(functionName) {
988
+ const response = await this.client.request(`/functions/${functionName}`);
989
+ return {
990
+ data: response.data,
991
+ error: response.error ? {
992
+ message: response.error.message,
993
+ status: response.status,
994
+ name: functionName
995
+ } : null
996
+ };
997
+ }
998
+ serializeBody(body) {
999
+ if (body === void 0) {
1000
+ return void 0;
1001
+ }
1002
+ if (body instanceof FormData || body instanceof Blob) {
1003
+ return body;
1004
+ }
1005
+ return JSON.stringify(body);
1006
+ }
1007
+ };
1008
+
1009
+ // src/lib/auth.ts
1010
+ var VaifAuth = class {
1011
+ constructor(client) {
1012
+ this.currentSession = null;
1013
+ this.currentUser = null;
1014
+ this.stateChangeCallbacks = /* @__PURE__ */ new Set();
1015
+ this.refreshTimeout = null;
1016
+ this.client = client;
1017
+ this.restoreSession();
1018
+ }
1019
+ /**
1020
+ * Get current session
1021
+ */
1022
+ async getSession() {
1023
+ return {
1024
+ data: { session: this.currentSession },
1025
+ error: null
1026
+ };
1027
+ }
1028
+ /**
1029
+ * Get current user synchronously from cache
1030
+ */
1031
+ get user() {
1032
+ return this.currentUser;
1033
+ }
1034
+ /**
1035
+ * Get current user (fetches fresh data)
1036
+ */
1037
+ async getUser() {
1038
+ if (!this.currentSession) {
1039
+ return { data: { user: this.currentUser }, error: null };
1040
+ }
1041
+ const response = await this.client.request("/auth/user");
1042
+ if (response.error) {
1043
+ return {
1044
+ data: { user: this.currentUser },
1045
+ error: {
1046
+ message: response.error.message,
1047
+ status: response.status
1048
+ }
1049
+ };
1050
+ }
1051
+ this.currentUser = response.data ?? null;
1052
+ return { data: { user: this.currentUser }, error: null };
1053
+ }
1054
+ /**
1055
+ * Sign up with email and password
1056
+ */
1057
+ async signUp(options) {
1058
+ const response = await this.client.request("/auth/signup", {
1059
+ method: "POST",
1060
+ body: {
1061
+ email: options.email,
1062
+ password: options.password,
1063
+ phone: options.phone,
1064
+ data: options.data,
1065
+ redirectTo: options.redirectTo
1066
+ }
1067
+ });
1068
+ if (response.error) {
1069
+ return {
1070
+ data: { session: null, user: null },
1071
+ error: {
1072
+ message: response.error.message,
1073
+ status: response.status
1074
+ }
1075
+ };
1076
+ }
1077
+ const { session, user } = response.data ?? { session: null, user: null };
1078
+ if (session) {
1079
+ await this.setSession(session);
1080
+ this.notifyStateChange("SIGNED_IN", session);
1081
+ }
1082
+ return {
1083
+ data: { session, user },
1084
+ error: null
1085
+ };
1086
+ }
1087
+ /**
1088
+ * Sign in with email and password
1089
+ */
1090
+ async signInWithPassword(options) {
1091
+ const response = await this.client.request("/auth/token?grant_type=password", {
1092
+ method: "POST",
1093
+ body: {
1094
+ email: options.email,
1095
+ password: options.password,
1096
+ phone: options.phone
1097
+ }
1098
+ });
1099
+ if (response.error) {
1100
+ return {
1101
+ data: { session: null, user: null },
1102
+ error: {
1103
+ message: response.error.message,
1104
+ status: response.status
1105
+ }
1106
+ };
1107
+ }
1108
+ const { session, user } = response.data ?? { session: null, user: null };
1109
+ if (session) {
1110
+ await this.setSession(session);
1111
+ this.notifyStateChange("SIGNED_IN", session);
1112
+ }
1113
+ return {
1114
+ data: { session, user },
1115
+ error: null
1116
+ };
1117
+ }
1118
+ /**
1119
+ * Sign in with OAuth provider
1120
+ */
1121
+ async signInWithOAuth(options) {
1122
+ const config = this.client.getConfig();
1123
+ const redirectTo = options.redirectTo ?? (typeof window !== "undefined" ? window.location?.origin : "") ?? "";
1124
+ const params = new URLSearchParams({
1125
+ provider: options.provider,
1126
+ redirect_to: redirectTo
1127
+ });
1128
+ if (options.scopes) {
1129
+ params.set("scopes", options.scopes);
1130
+ }
1131
+ if (options.queryParams) {
1132
+ Object.entries(options.queryParams).forEach(([key, value]) => {
1133
+ params.set(key, value);
1134
+ });
1135
+ }
1136
+ const url = `${config.apiUrl}/auth/authorize?${params.toString()}`;
1137
+ return {
1138
+ data: {
1139
+ provider: options.provider,
1140
+ url
1141
+ },
1142
+ error: null
1143
+ };
1144
+ }
1145
+ /**
1146
+ * Sign in with magic link (passwordless)
1147
+ */
1148
+ async signInWithOtp(options) {
1149
+ const response = await this.client.request("/auth/otp", {
1150
+ method: "POST",
1151
+ body: {
1152
+ email: options.email,
1153
+ phone: options.phone,
1154
+ create_user: options.createUser ?? true,
1155
+ redirect_to: options.redirectTo
1156
+ }
1157
+ });
1158
+ if (response.error) {
1159
+ return {
1160
+ data: {},
1161
+ error: {
1162
+ message: response.error.message,
1163
+ status: response.status
1164
+ }
1165
+ };
1166
+ }
1167
+ return {
1168
+ data: { messageId: response.data?.messageId },
1169
+ error: null
1170
+ };
1171
+ }
1172
+ /**
1173
+ * Verify OTP code
1174
+ */
1175
+ async verifyOtp(options) {
1176
+ const response = await this.client.request("/auth/verify", {
1177
+ method: "POST",
1178
+ body: {
1179
+ email: options.email,
1180
+ phone: options.phone,
1181
+ token: options.token,
1182
+ type: options.type
1183
+ }
1184
+ });
1185
+ if (response.error) {
1186
+ return {
1187
+ data: { session: null, user: null },
1188
+ error: {
1189
+ message: response.error.message,
1190
+ status: response.status
1191
+ }
1192
+ };
1193
+ }
1194
+ const { session, user } = response.data ?? { session: null, user: null };
1195
+ if (session) {
1196
+ await this.setSession(session);
1197
+ this.notifyStateChange("SIGNED_IN", session);
1198
+ }
1199
+ return {
1200
+ data: { session, user },
1201
+ error: null
1202
+ };
1203
+ }
1204
+ /**
1205
+ * Sign out
1206
+ */
1207
+ async signOut(options) {
1208
+ const scope = options?.scope ?? "local";
1209
+ if (scope !== "local") {
1210
+ await this.client.request("/auth/logout", {
1211
+ method: "POST",
1212
+ body: { scope }
1213
+ });
1214
+ }
1215
+ await this.clearSession();
1216
+ this.notifyStateChange("SIGNED_OUT", null);
1217
+ return { error: null };
1218
+ }
1219
+ /**
1220
+ * Reset password
1221
+ */
1222
+ async resetPasswordForEmail(options) {
1223
+ const response = await this.client.request("/auth/recover", {
1224
+ method: "POST",
1225
+ body: {
1226
+ email: options.email,
1227
+ redirect_to: options.redirectTo,
1228
+ captcha_token: options.captchaToken
1229
+ }
1230
+ });
1231
+ if (response.error) {
1232
+ return {
1233
+ error: {
1234
+ message: response.error.message,
1235
+ status: response.status
1236
+ }
1237
+ };
1238
+ }
1239
+ return { error: null };
1240
+ }
1241
+ /**
1242
+ * Update user
1243
+ */
1244
+ async updateUser(options) {
1245
+ const response = await this.client.request("/auth/user", {
1246
+ method: "PUT",
1247
+ body: {
1248
+ email: options.email,
1249
+ password: options.password,
1250
+ phone: options.phone,
1251
+ data: options.data,
1252
+ nonce: options.nonce
1253
+ }
1254
+ });
1255
+ if (response.error) {
1256
+ return {
1257
+ data: { user: null },
1258
+ error: {
1259
+ message: response.error.message,
1260
+ status: response.status
1261
+ }
1262
+ };
1263
+ }
1264
+ this.currentUser = response.data;
1265
+ this.notifyStateChange("USER_UPDATED", this.currentSession);
1266
+ return { data: { user: response.data }, error: null };
1267
+ }
1268
+ /**
1269
+ * Refresh session
1270
+ */
1271
+ async refreshSession() {
1272
+ if (!this.currentSession?.refreshToken) {
1273
+ return {
1274
+ data: { session: null, user: null },
1275
+ error: { message: "No refresh token available", status: 401 }
1276
+ };
1277
+ }
1278
+ const response = await this.client.request("/auth/token?grant_type=refresh_token", {
1279
+ method: "POST",
1280
+ body: {
1281
+ refresh_token: this.currentSession.refreshToken
1282
+ }
1283
+ });
1284
+ if (response.error) {
1285
+ await this.clearSession();
1286
+ this.notifyStateChange("TOKEN_REFRESHED", null);
1287
+ return {
1288
+ data: { session: null, user: null },
1289
+ error: {
1290
+ message: response.error.message,
1291
+ status: response.status
1292
+ }
1293
+ };
1294
+ }
1295
+ const { session, user } = response.data ?? { session: null, user: null };
1296
+ if (session) {
1297
+ await this.setSession(session);
1298
+ this.notifyStateChange("TOKEN_REFRESHED", session);
1299
+ }
1300
+ return {
1301
+ data: { session, user },
1302
+ error: null
1303
+ };
1304
+ }
1305
+ /**
1306
+ * Set session from URL (after OAuth redirect)
1307
+ */
1308
+ async setSessionFromUrl() {
1309
+ if (typeof window === "undefined") {
1310
+ return {
1311
+ data: { session: null, user: null },
1312
+ error: { message: "Not in browser environment", status: 0 }
1313
+ };
1314
+ }
1315
+ const hashParams = new URLSearchParams(window.location.hash.slice(1));
1316
+ const accessToken = hashParams.get("access_token");
1317
+ const refreshToken = hashParams.get("refresh_token");
1318
+ const expiresIn = hashParams.get("expires_in");
1319
+ const tokenType = hashParams.get("token_type");
1320
+ if (!accessToken) {
1321
+ const error = hashParams.get("error_description") || hashParams.get("error");
1322
+ return {
1323
+ data: { session: null, user: null },
1324
+ error: error ? { message: error, status: 0 } : null
1325
+ };
1326
+ }
1327
+ const session = {
1328
+ accessToken,
1329
+ refreshToken: refreshToken ?? "",
1330
+ expiresIn: expiresIn ? parseInt(expiresIn, 10) : 3600,
1331
+ expiresAt: Date.now() + (expiresIn ? parseInt(expiresIn, 10) * 1e3 : 36e5),
1332
+ tokenType: tokenType ?? "bearer",
1333
+ user: null
1334
+ };
1335
+ this.client.setAccessToken(accessToken);
1336
+ const userResponse = await this.client.request("/auth/user");
1337
+ if (userResponse.data) {
1338
+ session.user = userResponse.data;
1339
+ }
1340
+ await this.setSession(session);
1341
+ this.notifyStateChange("SIGNED_IN", session);
1342
+ window.history.replaceState(null, "", window.location.pathname);
1343
+ return {
1344
+ data: { session, user: session.user },
1345
+ error: null
1346
+ };
1347
+ }
1348
+ /**
1349
+ * Subscribe to auth state changes
1350
+ */
1351
+ onAuthStateChange(callback) {
1352
+ this.stateChangeCallbacks.add(callback);
1353
+ if (this.currentSession) {
1354
+ callback("INITIAL_SESSION", this.currentSession);
1355
+ }
1356
+ return {
1357
+ data: {
1358
+ subscription: {
1359
+ unsubscribe: () => {
1360
+ this.stateChangeCallbacks.delete(callback);
1361
+ }
1362
+ }
1363
+ }
1364
+ };
1365
+ }
1366
+ /**
1367
+ * Exchange code for session (PKCE flow)
1368
+ */
1369
+ async exchangeCodeForSession(code) {
1370
+ const response = await this.client.request("/auth/token?grant_type=authorization_code", {
1371
+ method: "POST",
1372
+ body: { code }
1373
+ });
1374
+ if (response.error) {
1375
+ return {
1376
+ data: { session: null, user: null },
1377
+ error: {
1378
+ message: response.error.message,
1379
+ status: response.status
1380
+ }
1381
+ };
1382
+ }
1383
+ const { session, user } = response.data ?? { session: null, user: null };
1384
+ if (session) {
1385
+ await this.setSession(session);
1386
+ this.notifyStateChange("SIGNED_IN", session);
1387
+ }
1388
+ return {
1389
+ data: { session, user },
1390
+ error: null
1391
+ };
1392
+ }
1393
+ /**
1394
+ * Resend confirmation email
1395
+ */
1396
+ async resend(options) {
1397
+ const response = await this.client.request("/auth/resend", {
1398
+ method: "POST",
1399
+ body: options
1400
+ });
1401
+ if (response.error) {
1402
+ return {
1403
+ error: {
1404
+ message: response.error.message,
1405
+ status: response.status
1406
+ }
1407
+ };
1408
+ }
1409
+ return { error: null };
1410
+ }
1411
+ // Private methods
1412
+ async setSession(session) {
1413
+ this.currentSession = session;
1414
+ this.currentUser = session.user;
1415
+ this.client.setAccessToken(session.accessToken);
1416
+ const config = this.client.getConfig();
1417
+ if (config.persistSession) {
1418
+ await config.storage.setItem(
1419
+ `vaif-session-${config.projectId}`,
1420
+ JSON.stringify({
1421
+ accessToken: session.accessToken,
1422
+ refreshToken: session.refreshToken,
1423
+ expiresAt: session.expiresAt
1424
+ })
1425
+ );
1426
+ }
1427
+ if (config.autoRefreshToken) {
1428
+ this.scheduleRefresh(session);
1429
+ }
1430
+ }
1431
+ async clearSession() {
1432
+ this.currentSession = null;
1433
+ this.currentUser = null;
1434
+ this.client.setAccessToken(null);
1435
+ if (this.refreshTimeout) {
1436
+ clearTimeout(this.refreshTimeout);
1437
+ this.refreshTimeout = null;
1438
+ }
1439
+ const config = this.client.getConfig();
1440
+ if (config.persistSession) {
1441
+ await config.storage.removeItem(`vaif-session-${config.projectId}`);
1442
+ }
1443
+ }
1444
+ async restoreSession() {
1445
+ const config = this.client.getConfig();
1446
+ if (!config.persistSession) return;
1447
+ try {
1448
+ const sessionStr = await config.storage.getItem(`vaif-session-${config.projectId}`);
1449
+ if (!sessionStr) return;
1450
+ const stored = JSON.parse(sessionStr);
1451
+ if (!stored.accessToken || stored.expiresAt < Date.now()) {
1452
+ await this.clearSession();
1453
+ return;
1454
+ }
1455
+ this.client.setAccessToken(stored.accessToken);
1456
+ const response = await this.client.request("/auth/session");
1457
+ if (response.data?.session) {
1458
+ await this.setSession(response.data.session);
1459
+ this.notifyStateChange("INITIAL_SESSION", response.data.session);
1460
+ } else {
1461
+ await this.clearSession();
1462
+ }
1463
+ } catch {
1464
+ await this.clearSession();
1465
+ }
1466
+ }
1467
+ scheduleRefresh(session) {
1468
+ if (this.refreshTimeout) {
1469
+ clearTimeout(this.refreshTimeout);
1470
+ }
1471
+ const expiresIn = session.expiresAt - Date.now() - 6e4;
1472
+ if (expiresIn > 0) {
1473
+ this.refreshTimeout = setTimeout(() => {
1474
+ this.refreshSession();
1475
+ }, expiresIn);
1476
+ }
1477
+ }
1478
+ notifyStateChange(event, session) {
1479
+ this.stateChangeCallbacks.forEach((callback) => {
1480
+ try {
1481
+ callback(event, session);
1482
+ } catch {
1483
+ }
1484
+ });
1485
+ }
1486
+ };
1487
+
1488
+ // src/lib/ai.ts
1489
+ var VaifAI = class {
1490
+ constructor(client) {
1491
+ this.client = client;
1492
+ }
1493
+ /**
1494
+ * Generate database schema from natural language
1495
+ *
1496
+ * @example
1497
+ * ```typescript
1498
+ * const { data, error } = await vaif.ai.generateSchema({
1499
+ * prompt: 'Create a blog with posts, authors, and comments',
1500
+ * format: 'drizzle',
1501
+ * includeRelations: true
1502
+ * });
1503
+ * ```
1504
+ */
1505
+ async generateSchema(request, options = {}) {
1506
+ const response = await this.client.request(
1507
+ "/ai/generate/schema",
1508
+ {
1509
+ method: "POST",
1510
+ body: {
1511
+ ...request,
1512
+ model: options.model,
1513
+ temperature: options.temperature,
1514
+ maxTokens: options.maxTokens
1515
+ }
1516
+ }
1517
+ );
1518
+ if (response.error) {
1519
+ return {
1520
+ data: null,
1521
+ error: {
1522
+ message: response.error.message,
1523
+ code: response.error.code,
1524
+ status: response.status
1525
+ }
1526
+ };
1527
+ }
1528
+ return { data: response.data, error: null };
1529
+ }
1530
+ /**
1531
+ * Generate edge function from natural language
1532
+ *
1533
+ * @example
1534
+ * ```typescript
1535
+ * const { data, error } = await vaif.ai.generateFunction({
1536
+ * prompt: 'Create a function that sends welcome emails to new users',
1537
+ * name: 'send-welcome-email',
1538
+ * runtime: 'edge'
1539
+ * });
1540
+ * ```
1541
+ */
1542
+ async generateFunction(request, options = {}) {
1543
+ const response = await this.client.request(
1544
+ "/ai/generate/function",
1545
+ {
1546
+ method: "POST",
1547
+ body: {
1548
+ ...request,
1549
+ model: options.model,
1550
+ temperature: options.temperature,
1551
+ maxTokens: options.maxTokens
1552
+ }
1553
+ }
1554
+ );
1555
+ if (response.error) {
1556
+ return {
1557
+ data: null,
1558
+ error: {
1559
+ message: response.error.message,
1560
+ code: response.error.code,
1561
+ status: response.status
1562
+ }
1563
+ };
1564
+ }
1565
+ return { data: response.data, error: null };
1566
+ }
1567
+ /**
1568
+ * Generate API endpoint from natural language
1569
+ *
1570
+ * @example
1571
+ * ```typescript
1572
+ * const { data, error } = await vaif.ai.generateEndpoint({
1573
+ * prompt: 'Create an endpoint to get user profile with their recent posts',
1574
+ * method: 'GET',
1575
+ * path: '/users/:id/profile',
1576
+ * requiresAuth: true
1577
+ * });
1578
+ * ```
1579
+ */
1580
+ async generateEndpoint(request, options = {}) {
1581
+ const response = await this.client.request(
1582
+ "/ai/generate/endpoint",
1583
+ {
1584
+ method: "POST",
1585
+ body: {
1586
+ ...request,
1587
+ model: options.model,
1588
+ temperature: options.temperature,
1589
+ maxTokens: options.maxTokens
1590
+ }
1591
+ }
1592
+ );
1593
+ if (response.error) {
1594
+ return {
1595
+ data: null,
1596
+ error: {
1597
+ message: response.error.message,
1598
+ code: response.error.code,
1599
+ status: response.status
1600
+ }
1601
+ };
1602
+ }
1603
+ return { data: response.data, error: null };
1604
+ }
1605
+ /**
1606
+ * Chat with AI assistant for project help
1607
+ *
1608
+ * @example
1609
+ * ```typescript
1610
+ * const { data, error } = await vaif.ai.chat({
1611
+ * messages: [
1612
+ * { role: 'user', content: 'How do I add pagination to my posts query?' }
1613
+ * ],
1614
+ * context: {
1615
+ * schema: mySchemaString
1616
+ * }
1617
+ * });
1618
+ * ```
1619
+ */
1620
+ async chat(request, options = {}) {
1621
+ const response = await this.client.request("/ai/chat", {
1622
+ method: "POST",
1623
+ body: {
1624
+ ...request,
1625
+ model: options.model,
1626
+ temperature: options.temperature,
1627
+ maxTokens: options.maxTokens
1628
+ }
1629
+ });
1630
+ if (response.error) {
1631
+ return {
1632
+ data: null,
1633
+ error: {
1634
+ message: response.error.message,
1635
+ code: response.error.code,
1636
+ status: response.status
1637
+ }
1638
+ };
1639
+ }
1640
+ return { data: response.data, error: null };
1641
+ }
1642
+ /**
1643
+ * Stream chat response
1644
+ *
1645
+ * @example
1646
+ * ```typescript
1647
+ * for await (const chunk of vaif.ai.chatStream({
1648
+ * messages: [{ role: 'user', content: 'Explain this schema' }]
1649
+ * })) {
1650
+ * process.stdout.write(chunk);
1651
+ * }
1652
+ * ```
1653
+ */
1654
+ async *chatStream(request, options = {}) {
1655
+ const config = this.client.getConfig();
1656
+ const url = new URL("/ai/chat/stream", config.apiUrl);
1657
+ const headers = {
1658
+ "Content-Type": "application/json",
1659
+ "X-Project-ID": config.projectId,
1660
+ apikey: config.apiKey
1661
+ };
1662
+ const token = this.client.getAccessToken();
1663
+ if (token) {
1664
+ headers.Authorization = `Bearer ${token}`;
1665
+ }
1666
+ const response = await fetch(url.toString(), {
1667
+ method: "POST",
1668
+ headers,
1669
+ body: JSON.stringify({
1670
+ ...request,
1671
+ model: options.model,
1672
+ temperature: options.temperature,
1673
+ maxTokens: options.maxTokens,
1674
+ stream: true
1675
+ })
1676
+ });
1677
+ if (!response.ok) {
1678
+ throw new Error(`AI chat failed: ${response.statusText}`);
1679
+ }
1680
+ const reader = response.body?.getReader();
1681
+ if (!reader) {
1682
+ throw new Error("No response body");
1683
+ }
1684
+ const decoder = new TextDecoder();
1685
+ try {
1686
+ while (true) {
1687
+ const { done, value } = await reader.read();
1688
+ if (done) break;
1689
+ const chunk = decoder.decode(value, { stream: true });
1690
+ const lines = chunk.split("\n");
1691
+ for (const line of lines) {
1692
+ if (line.startsWith("data: ")) {
1693
+ const data = line.slice(6);
1694
+ if (data === "[DONE]") {
1695
+ return;
1696
+ }
1697
+ try {
1698
+ const parsed = JSON.parse(data);
1699
+ if (parsed.content) {
1700
+ yield parsed.content;
1701
+ }
1702
+ } catch {
1703
+ }
1704
+ }
1705
+ }
1706
+ }
1707
+ } finally {
1708
+ reader.releaseLock();
1709
+ }
1710
+ }
1711
+ /**
1712
+ * Get AI credits information
1713
+ *
1714
+ * @example
1715
+ * ```typescript
1716
+ * const { data, error } = await vaif.ai.getCredits();
1717
+ * console.log(`Balance: ${data.balance} credits`);
1718
+ * ```
1719
+ */
1720
+ async getCredits() {
1721
+ const response = await this.client.request("/ai/credits");
1722
+ if (response.error) {
1723
+ return {
1724
+ data: null,
1725
+ error: {
1726
+ message: response.error.message,
1727
+ code: response.error.code,
1728
+ status: response.status
1729
+ }
1730
+ };
1731
+ }
1732
+ return { data: response.data, error: null };
1733
+ }
1734
+ /**
1735
+ * Explain code or schema
1736
+ */
1737
+ async explain(code, options = {}) {
1738
+ const response = await this.client.request(
1739
+ "/ai/explain",
1740
+ {
1741
+ method: "POST",
1742
+ body: {
1743
+ code,
1744
+ language: options.language,
1745
+ context: options.context,
1746
+ model: options.model,
1747
+ temperature: options.temperature
1748
+ }
1749
+ }
1750
+ );
1751
+ if (response.error) {
1752
+ return {
1753
+ data: null,
1754
+ error: {
1755
+ message: response.error.message,
1756
+ code: response.error.code,
1757
+ status: response.status
1758
+ }
1759
+ };
1760
+ }
1761
+ return { data: response.data, error: null };
1762
+ }
1763
+ /**
1764
+ * Review code for issues and improvements
1765
+ */
1766
+ async review(code, options = {}) {
1767
+ const response = await this.client.request("/ai/review", {
1768
+ method: "POST",
1769
+ body: {
1770
+ code,
1771
+ language: options.language,
1772
+ focus: options.focus,
1773
+ model: options.model,
1774
+ temperature: options.temperature
1775
+ }
1776
+ });
1777
+ if (response.error) {
1778
+ return {
1779
+ data: null,
1780
+ error: {
1781
+ message: response.error.message,
1782
+ code: response.error.code,
1783
+ status: response.status
1784
+ }
1785
+ };
1786
+ }
1787
+ return { data: response.data, error: null };
1788
+ }
1789
+ /**
1790
+ * Generate TypeScript types from schema
1791
+ */
1792
+ async generateTypes(schema, options = {}) {
1793
+ const response = await this.client.request(
1794
+ "/ai/generate/types",
1795
+ {
1796
+ method: "POST",
1797
+ body: {
1798
+ schema,
1799
+ format: options.format ?? "typescript",
1800
+ model: options.model
1801
+ }
1802
+ }
1803
+ );
1804
+ if (response.error) {
1805
+ return {
1806
+ data: null,
1807
+ error: {
1808
+ message: response.error.message,
1809
+ code: response.error.code,
1810
+ status: response.status
1811
+ }
1812
+ };
1813
+ }
1814
+ return { data: response.data, error: null };
1815
+ }
1816
+ /**
1817
+ * Generate SQL migration from schema diff
1818
+ */
1819
+ async generateMigration(options) {
1820
+ const response = await this.client.request("/ai/generate/migration", {
1821
+ method: "POST",
1822
+ body: {
1823
+ currentSchema: options.currentSchema,
1824
+ targetSchema: options.targetSchema,
1825
+ database: options.database ?? "postgresql",
1826
+ model: options.model
1827
+ }
1828
+ });
1829
+ if (response.error) {
1830
+ return {
1831
+ data: null,
1832
+ error: {
1833
+ message: response.error.message,
1834
+ code: response.error.code,
1835
+ status: response.status
1836
+ }
1837
+ };
1838
+ }
1839
+ return { data: response.data, error: null };
1840
+ }
1841
+ };
1842
+
1843
+ // src/lib/typegen.ts
1844
+ var PG_TO_TS_TYPES = {
1845
+ // Numeric
1846
+ smallint: "number",
1847
+ integer: "number",
1848
+ bigint: "number",
1849
+ int2: "number",
1850
+ int4: "number",
1851
+ int8: "number",
1852
+ decimal: "number",
1853
+ numeric: "number",
1854
+ real: "number",
1855
+ "double precision": "number",
1856
+ float4: "number",
1857
+ float8: "number",
1858
+ serial: "number",
1859
+ bigserial: "number",
1860
+ smallserial: "number",
1861
+ money: "number",
1862
+ // String
1863
+ character: "string",
1864
+ "character varying": "string",
1865
+ varchar: "string",
1866
+ char: "string",
1867
+ text: "string",
1868
+ name: "string",
1869
+ citext: "string",
1870
+ // Boolean
1871
+ boolean: "boolean",
1872
+ bool: "boolean",
1873
+ // Date/Time
1874
+ date: "string",
1875
+ time: "string",
1876
+ "time with time zone": "string",
1877
+ "time without time zone": "string",
1878
+ timestamp: "string",
1879
+ "timestamp with time zone": "string",
1880
+ "timestamp without time zone": "string",
1881
+ timestamptz: "string",
1882
+ timetz: "string",
1883
+ interval: "string",
1884
+ // Binary
1885
+ bytea: "string",
1886
+ // UUID
1887
+ uuid: "string",
1888
+ // JSON
1889
+ json: "unknown",
1890
+ jsonb: "unknown",
1891
+ // Arrays (handled separately)
1892
+ "ARRAY": "unknown[]",
1893
+ // Network
1894
+ inet: "string",
1895
+ cidr: "string",
1896
+ macaddr: "string",
1897
+ macaddr8: "string",
1898
+ // Geometric
1899
+ point: "{ x: number; y: number }",
1900
+ line: "string",
1901
+ lseg: "string",
1902
+ box: "string",
1903
+ path: "string",
1904
+ polygon: "string",
1905
+ circle: "string",
1906
+ // Text search
1907
+ tsvector: "string",
1908
+ tsquery: "string",
1909
+ // Other
1910
+ xml: "string",
1911
+ void: "void",
1912
+ unknown: "unknown"
1913
+ };
1914
+ var VaifTypeGen = class {
1915
+ constructor(client) {
1916
+ this.client = client;
1917
+ }
1918
+ /**
1919
+ * Fetch database schema from the API.
1920
+ */
1921
+ async fetchSchema(schema = "public") {
1922
+ const response = await this.client.request(
1923
+ `/database/schema?schema=${schema}`
1924
+ );
1925
+ if (response.error) {
1926
+ throw new Error(response.error.message);
1927
+ }
1928
+ return response.data;
1929
+ }
1930
+ /**
1931
+ * Generate TypeScript types from schema.
1932
+ */
1933
+ async generate(options = {}) {
1934
+ const {
1935
+ includeRow = true,
1936
+ includeInsert = true,
1937
+ includeUpdate = true,
1938
+ includeRelationships = true,
1939
+ schema = "public",
1940
+ tables = [],
1941
+ excludeTables = [],
1942
+ includeComments = true,
1943
+ exportFormat = "named",
1944
+ namespace = "Database"
1945
+ } = options;
1946
+ const dbSchema = await this.fetchSchema(schema);
1947
+ const lines = [];
1948
+ lines.push("/**");
1949
+ lines.push(" * VAIF Studio Database Types");
1950
+ lines.push(` * Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
1951
+ lines.push(" * DO NOT EDIT - This file is auto-generated");
1952
+ lines.push(" */");
1953
+ lines.push("");
1954
+ if (dbSchema.enums.length > 0) {
1955
+ lines.push("// Enums");
1956
+ for (const enumDef of dbSchema.enums) {
1957
+ lines.push(...this.generateEnum(enumDef, includeComments));
1958
+ lines.push("");
1959
+ }
1960
+ }
1961
+ let filteredTables = dbSchema.tables.filter((t) => {
1962
+ if (tables.length > 0 && !tables.includes(t.name)) return false;
1963
+ if (excludeTables.includes(t.name)) return false;
1964
+ return true;
1965
+ });
1966
+ const tableTypes = [];
1967
+ for (const table of filteredTables) {
1968
+ const types = this.generateTableTypes(table, {
1969
+ includeRow,
1970
+ includeInsert,
1971
+ includeUpdate,
1972
+ includeRelationships,
1973
+ includeComments,
1974
+ allTables: dbSchema.tables
1975
+ });
1976
+ tableTypes.push(types);
1977
+ }
1978
+ for (const view of dbSchema.views) {
1979
+ if (tables.length > 0 && !tables.includes(view.name)) continue;
1980
+ if (excludeTables.includes(view.name)) continue;
1981
+ const types = this.generateViewTypes(view, includeComments);
1982
+ tableTypes.push(types);
1983
+ }
1984
+ if (exportFormat === "namespace") {
1985
+ lines.push(`export namespace ${namespace} {`);
1986
+ for (const types of tableTypes) {
1987
+ lines.push(this.indent(types, 2));
1988
+ }
1989
+ lines.push("}");
1990
+ } else {
1991
+ lines.push(...tableTypes);
1992
+ }
1993
+ lines.push("");
1994
+ lines.push("// Database type helper");
1995
+ lines.push("export interface Database {");
1996
+ lines.push(" public: {");
1997
+ lines.push(" Tables: {");
1998
+ for (const table of filteredTables) {
1999
+ const pascalName = this.toPascalCase(table.name);
2000
+ lines.push(` ${table.name}: {`);
2001
+ if (includeRow) lines.push(` Row: ${pascalName}Row;`);
2002
+ if (includeInsert) lines.push(` Insert: ${pascalName}Insert;`);
2003
+ if (includeUpdate) lines.push(` Update: ${pascalName}Update;`);
2004
+ lines.push(" };");
2005
+ }
2006
+ lines.push(" };");
2007
+ lines.push(" Views: {");
2008
+ for (const view of dbSchema.views) {
2009
+ const pascalName = this.toPascalCase(view.name);
2010
+ lines.push(` ${view.name}: {`);
2011
+ lines.push(` Row: ${pascalName}Row;`);
2012
+ lines.push(" };");
2013
+ }
2014
+ lines.push(" };");
2015
+ lines.push(" Enums: {");
2016
+ for (const enumDef of dbSchema.enums) {
2017
+ lines.push(` ${enumDef.name}: ${this.toPascalCase(enumDef.name)};`);
2018
+ }
2019
+ lines.push(" };");
2020
+ lines.push(" };");
2021
+ lines.push("}");
2022
+ return lines.join("\n");
2023
+ }
2024
+ /**
2025
+ * Generate TypeScript from schema JSON directly.
2026
+ */
2027
+ generateFromSchema(schema, options = {}) {
2028
+ const {
2029
+ includeRow = true,
2030
+ includeInsert = true,
2031
+ includeUpdate = true,
2032
+ includeRelationships = true,
2033
+ tables = [],
2034
+ excludeTables = [],
2035
+ includeComments = true,
2036
+ exportFormat = "named",
2037
+ namespace = "Database"
2038
+ } = options;
2039
+ const lines = [];
2040
+ lines.push("/**");
2041
+ lines.push(" * VAIF Studio Database Types");
2042
+ lines.push(` * Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
2043
+ lines.push(" * DO NOT EDIT - This file is auto-generated");
2044
+ lines.push(" */");
2045
+ lines.push("");
2046
+ if (schema.enums && schema.enums.length > 0) {
2047
+ lines.push("// Enums");
2048
+ for (const enumDef of schema.enums) {
2049
+ lines.push(...this.generateEnum(enumDef, includeComments));
2050
+ lines.push("");
2051
+ }
2052
+ }
2053
+ let filteredTables = (schema.tables || []).filter((t) => {
2054
+ if (tables.length > 0 && !tables.includes(t.name)) return false;
2055
+ if (excludeTables.includes(t.name)) return false;
2056
+ return true;
2057
+ });
2058
+ const tableTypes = [];
2059
+ for (const table of filteredTables) {
2060
+ const types = this.generateTableTypes(table, {
2061
+ includeRow,
2062
+ includeInsert,
2063
+ includeUpdate,
2064
+ includeRelationships,
2065
+ includeComments,
2066
+ allTables: schema.tables
2067
+ });
2068
+ tableTypes.push(types);
2069
+ }
2070
+ if (exportFormat === "namespace") {
2071
+ lines.push(`export namespace ${namespace} {`);
2072
+ for (const types of tableTypes) {
2073
+ lines.push(this.indent(types, 2));
2074
+ }
2075
+ lines.push("}");
2076
+ } else {
2077
+ lines.push(...tableTypes);
2078
+ }
2079
+ return lines.join("\n");
2080
+ }
2081
+ generateEnum(enumDef, includeComments) {
2082
+ const lines = [];
2083
+ const name = this.toPascalCase(enumDef.name);
2084
+ if (includeComments) {
2085
+ lines.push(`/** Enum: ${enumDef.name} */`);
2086
+ }
2087
+ lines.push(`export type ${name} = ${enumDef.values.map((v) => `'${v}'`).join(" | ")};`);
2088
+ return lines;
2089
+ }
2090
+ generateTableTypes(table, options) {
2091
+ const lines = [];
2092
+ const pascalName = this.toPascalCase(table.name);
2093
+ if (options.includeRow) {
2094
+ if (options.includeComments && table.comment) {
2095
+ lines.push(`/** ${table.comment} */`);
2096
+ }
2097
+ lines.push(`export interface ${pascalName}Row {`);
2098
+ for (const col of table.columns) {
2099
+ const tsType = this.pgTypeToTs(col.type);
2100
+ const nullable = col.nullable ? " | null" : "";
2101
+ if (options.includeComments && col.comment) {
2102
+ lines.push(` /** ${col.comment} */`);
2103
+ }
2104
+ lines.push(` ${col.name}: ${tsType}${nullable};`);
2105
+ }
2106
+ if (options.includeRelationships) {
2107
+ for (const fk of table.foreignKeys) {
2108
+ const relatedTable = options.allTables.find(
2109
+ (t) => t.name === fk.foreignTable
2110
+ );
2111
+ if (relatedTable) {
2112
+ const relatedPascal = this.toPascalCase(fk.foreignTable);
2113
+ lines.push(` ${fk.foreignTable}?: ${relatedPascal}Row;`);
2114
+ }
2115
+ }
2116
+ }
2117
+ lines.push("}");
2118
+ lines.push("");
2119
+ }
2120
+ if (options.includeInsert) {
2121
+ lines.push(`export interface ${pascalName}Insert {`);
2122
+ for (const col of table.columns) {
2123
+ const tsType = this.pgTypeToTs(col.type);
2124
+ const optional = col.nullable || col.defaultValue !== void 0 || col.isPrimaryKey ? "?" : "";
2125
+ const nullable = col.nullable ? " | null" : "";
2126
+ lines.push(` ${col.name}${optional}: ${tsType}${nullable};`);
2127
+ }
2128
+ lines.push("}");
2129
+ lines.push("");
2130
+ }
2131
+ if (options.includeUpdate) {
2132
+ lines.push(`export interface ${pascalName}Update {`);
2133
+ for (const col of table.columns) {
2134
+ if (col.isPrimaryKey) continue;
2135
+ const tsType = this.pgTypeToTs(col.type);
2136
+ const nullable = col.nullable ? " | null" : "";
2137
+ lines.push(` ${col.name}?: ${tsType}${nullable};`);
2138
+ }
2139
+ lines.push("}");
2140
+ lines.push("");
2141
+ }
2142
+ return lines.join("\n");
2143
+ }
2144
+ generateViewTypes(view, includeComments) {
2145
+ const lines = [];
2146
+ const pascalName = this.toPascalCase(view.name);
2147
+ if (includeComments) {
2148
+ lines.push(`/** View: ${view.name} */`);
2149
+ }
2150
+ lines.push(`export interface ${pascalName}Row {`);
2151
+ for (const col of view.columns) {
2152
+ const tsType = this.pgTypeToTs(col.type);
2153
+ const nullable = col.nullable ? " | null" : "";
2154
+ lines.push(` ${col.name}: ${tsType}${nullable};`);
2155
+ }
2156
+ lines.push("}");
2157
+ lines.push("");
2158
+ return lines.join("\n");
2159
+ }
2160
+ pgTypeToTs(pgType) {
2161
+ if (pgType.endsWith("[]")) {
2162
+ const baseType = pgType.slice(0, -2);
2163
+ const tsBaseType = PG_TO_TS_TYPES[baseType.toLowerCase()] || "unknown";
2164
+ return `${tsBaseType}[]`;
2165
+ }
2166
+ const arrayMatch = pgType.match(/^(.+)\s+ARRAY$/i);
2167
+ if (arrayMatch) {
2168
+ const baseType = arrayMatch[1];
2169
+ const tsBaseType = PG_TO_TS_TYPES[baseType.toLowerCase()] || "unknown";
2170
+ return `${tsBaseType}[]`;
2171
+ }
2172
+ const precisionMatch = pgType.match(/^(\w+)\(/);
2173
+ if (precisionMatch) {
2174
+ const baseType = precisionMatch[1];
2175
+ return PG_TO_TS_TYPES[baseType.toLowerCase()] || "unknown";
2176
+ }
2177
+ return PG_TO_TS_TYPES[pgType.toLowerCase()] || "unknown";
2178
+ }
2179
+ toPascalCase(str) {
2180
+ return str.split(/[_\s-]+/).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
2181
+ }
2182
+ indent(text, spaces) {
2183
+ const pad = " ".repeat(spaces);
2184
+ return text.split("\n").map((line) => line ? pad + line : line).join("\n");
2185
+ }
2186
+ };
2187
+ function createTypeGen(client) {
2188
+ return new VaifTypeGen(client);
2189
+ }
2190
+
2191
+ // src/lib/client.ts
2192
+ var DEFAULT_API_URL = "https://api.vaif.studio";
2193
+ var DEFAULT_REALTIME_URL = "wss://realtime.vaif.studio";
2194
+ var DEFAULT_TIMEOUT = 3e4;
2195
+ var memoryStorage = {
2196
+ data: /* @__PURE__ */ new Map(),
2197
+ getItem(key) {
2198
+ return this.data.get(key) ?? null;
2199
+ },
2200
+ setItem(key, value) {
2201
+ this.data.set(key, value);
2202
+ },
2203
+ removeItem(key) {
2204
+ this.data.delete(key);
2205
+ }
2206
+ };
2207
+ var browserStorage = typeof window !== "undefined" && window.localStorage ? {
2208
+ getItem: (key) => window.localStorage.getItem(key),
2209
+ setItem: (key, value) => window.localStorage.setItem(key, value),
2210
+ removeItem: (key) => window.localStorage.removeItem(key)
2211
+ } : null;
2212
+ function createClient(options) {
2213
+ return new VaifClient(options);
2214
+ }
2215
+ var VaifClient = class {
2216
+ constructor(options) {
2217
+ this.accessToken = null;
2218
+ if (!options.projectId) {
2219
+ throw new Error("projectId is required");
2220
+ }
2221
+ if (!options.apiKey) {
2222
+ throw new Error("apiKey is required");
2223
+ }
2224
+ this.config = {
2225
+ projectId: options.projectId,
2226
+ apiKey: options.apiKey,
2227
+ apiUrl: options.apiUrl ?? DEFAULT_API_URL,
2228
+ realtimeUrl: options.realtimeUrl ?? DEFAULT_REALTIME_URL,
2229
+ timeout: options.timeout ?? DEFAULT_TIMEOUT,
2230
+ headers: options.headers ?? {},
2231
+ debug: options.debug ?? false,
2232
+ autoRefreshToken: options.autoRefreshToken ?? true,
2233
+ persistSession: options.persistSession ?? true,
2234
+ storage: options.storage ?? browserStorage ?? memoryStorage
2235
+ };
2236
+ this.db = new VaifDatabase(this);
2237
+ this.realtime = new VaifRealtime(this);
2238
+ this.storage = new VaifStorage(this);
2239
+ this.functions = new VaifFunctions(this);
2240
+ this.auth = new VaifAuth(this);
2241
+ this.ai = new VaifAI(this);
2242
+ this.typegen = new VaifTypeGen(this);
2243
+ this.restoreSession();
2244
+ }
2245
+ /**
2246
+ * Get current configuration
2247
+ */
2248
+ getConfig() {
2249
+ return this.config;
2250
+ }
2251
+ /**
2252
+ * Set access token for authenticated requests
2253
+ */
2254
+ setAccessToken(token) {
2255
+ this.accessToken = token;
2256
+ }
2257
+ /**
2258
+ * Get current access token
2259
+ */
2260
+ getAccessToken() {
2261
+ return this.accessToken;
2262
+ }
2263
+ /**
2264
+ * Make an authenticated API request
2265
+ */
2266
+ async request(path, options = {}) {
2267
+ const url = new URL(path, this.config.apiUrl);
2268
+ if (options.params) {
2269
+ Object.entries(options.params).forEach(([key, value]) => {
2270
+ if (value !== void 0) {
2271
+ url.searchParams.set(key, String(value));
2272
+ }
2273
+ });
2274
+ }
2275
+ const headers = {
2276
+ "Content-Type": "application/json",
2277
+ "X-Project-ID": this.config.projectId,
2278
+ apikey: this.config.apiKey,
2279
+ ...this.config.headers,
2280
+ ...options.headers
2281
+ };
2282
+ if (this.accessToken) {
2283
+ headers.Authorization = `Bearer ${this.accessToken}`;
2284
+ }
2285
+ const fetchOptions = {
2286
+ method: options.method ?? "GET",
2287
+ headers,
2288
+ signal: options.signal
2289
+ };
2290
+ if (options.body && options.method !== "GET") {
2291
+ fetchOptions.body = JSON.stringify(options.body);
2292
+ }
2293
+ const timeoutMs = options.timeout ?? this.config.timeout;
2294
+ const timeoutController = new AbortController();
2295
+ const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
2296
+ if (options.signal) {
2297
+ options.signal.addEventListener("abort", () => timeoutController.abort());
2298
+ }
2299
+ fetchOptions.signal = timeoutController.signal;
2300
+ try {
2301
+ if (this.config.debug) {
2302
+ console.log(`[VAIF] ${options.method ?? "GET"} ${url.toString()}`);
2303
+ }
2304
+ const response = await fetch(url.toString(), fetchOptions);
2305
+ clearTimeout(timeoutId);
2306
+ const contentType = response.headers.get("content-type");
2307
+ let data = null;
2308
+ let error = null;
2309
+ if (contentType?.includes("application/json")) {
2310
+ const json = await response.json();
2311
+ if (response.ok) {
2312
+ data = json;
2313
+ } else {
2314
+ error = {
2315
+ message: json.message ?? json.error ?? "Request failed",
2316
+ code: json.code,
2317
+ status: response.status,
2318
+ details: json.details,
2319
+ requestId: response.headers.get("x-request-id") ?? void 0
2320
+ };
2321
+ }
2322
+ } else if (!response.ok) {
2323
+ error = {
2324
+ message: `Request failed with status ${response.status}`,
2325
+ status: response.status
2326
+ };
2327
+ }
2328
+ const responseHeaders = {};
2329
+ response.headers.forEach((value, key) => {
2330
+ responseHeaders[key] = value;
2331
+ });
2332
+ return {
2333
+ data,
2334
+ error,
2335
+ status: response.status,
2336
+ headers: responseHeaders
2337
+ };
2338
+ } catch (err) {
2339
+ clearTimeout(timeoutId);
2340
+ const error = {
2341
+ message: err instanceof Error ? err.name === "AbortError" ? "Request timed out" : err.message : "Unknown error",
2342
+ code: err instanceof Error && err.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR"
2343
+ };
2344
+ return {
2345
+ data: null,
2346
+ error,
2347
+ status: 0,
2348
+ headers: {}
2349
+ };
2350
+ }
2351
+ }
2352
+ /**
2353
+ * Restore session from storage
2354
+ */
2355
+ async restoreSession() {
2356
+ if (!this.config.persistSession) return;
2357
+ try {
2358
+ const sessionStr = await this.config.storage.getItem(
2359
+ `vaif-session-${this.config.projectId}`
2360
+ );
2361
+ if (sessionStr) {
2362
+ const session = JSON.parse(sessionStr);
2363
+ if (session.accessToken && session.expiresAt > Date.now()) {
2364
+ this.accessToken = session.accessToken;
2365
+ }
2366
+ }
2367
+ } catch {
2368
+ }
2369
+ }
2370
+ /**
2371
+ * Log debug messages
2372
+ */
2373
+ debug(...args) {
2374
+ if (this.config.debug) {
2375
+ console.log("[VAIF]", ...args);
2376
+ }
2377
+ }
2378
+ };
2379
+ export {
2380
+ RealtimeChannel,
2381
+ VaifAI,
2382
+ VaifAuth,
2383
+ VaifClient,
2384
+ VaifDatabase,
2385
+ VaifFunctions,
2386
+ VaifRealtime,
2387
+ VaifStorage,
2388
+ VaifTypeGen,
2389
+ createClient,
2390
+ createTypeGen
2391
+ };