@ragable/sdk 0.5.0 → 0.6.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.js CHANGED
@@ -23,6 +23,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
23
23
  var index_exports = {};
24
24
  __export(index_exports, {
25
25
  AgentsClient: () => AgentsClient,
26
+ AuthBroadcastChannel: () => AuthBroadcastChannel,
27
+ CookieStorageAdapter: () => CookieStorageAdapter,
28
+ DEFAULT_RAGABLE_API_BASE: () => DEFAULT_RAGABLE_API_BASE,
29
+ LocalStorageAdapter: () => LocalStorageAdapter,
30
+ MemoryStorageAdapter: () => MemoryStorageAdapter,
26
31
  PostgrestDeleteReturningBuilder: () => PostgrestDeleteReturningBuilder,
27
32
  PostgrestDeleteRootBuilder: () => PostgrestDeleteRootBuilder,
28
33
  PostgrestInsertReturningBuilder: () => PostgrestInsertReturningBuilder,
@@ -34,13 +39,20 @@ __export(index_exports, {
34
39
  PostgrestUpsertReturningBuilder: () => PostgrestUpsertReturningBuilder,
35
40
  PostgrestUpsertRootBuilder: () => PostgrestUpsertRootBuilder,
36
41
  Ragable: () => Ragable,
42
+ RagableAbortError: () => RagableAbortError,
43
+ RagableAuth: () => RagableAuth,
37
44
  RagableBrowser: () => RagableBrowser,
38
45
  RagableBrowserAgentsClient: () => RagableBrowserAgentsClient,
39
46
  RagableBrowserAuthClient: () => RagableBrowserAuthClient,
40
47
  RagableBrowserDatabaseClient: () => RagableBrowserDatabaseClient,
41
48
  RagableError: () => RagableError,
49
+ RagableNetworkError: () => RagableNetworkError,
42
50
  RagableRequestClient: () => RagableRequestClient,
51
+ RagableSdkError: () => RagableSdkError,
52
+ RagableTimeoutError: () => RagableTimeoutError,
53
+ SessionStorageAdapter: () => SessionStorageAdapter,
43
54
  ShiftClient: () => ShiftClient,
55
+ Transport: () => Transport,
44
56
  asPostgrestResponse: () => asPostgrestResponse,
45
57
  bindFetch: () => bindFetch,
46
58
  createBrowserClient: () => createBrowserClient,
@@ -48,10 +60,13 @@ __export(index_exports, {
48
60
  createRagPipeline: () => createRagPipeline,
49
61
  createRagableBrowserClient: () => createRagableBrowserClient,
50
62
  createRagableServerClient: () => createRagableServerClient,
63
+ detectStorage: () => detectStorage,
51
64
  extractErrorMessage: () => extractErrorMessage,
52
65
  formatRetrievalContext: () => formatRetrievalContext,
66
+ generateIdempotencyKey: () => generateIdempotencyKey,
53
67
  normalizeBrowserApiBase: () => normalizeBrowserApiBase,
54
68
  parseSseDataLine: () => parseSseDataLine,
69
+ parseTransportResponse: () => parseTransportResponse,
55
70
  readSseStream: () => readSseStream
56
71
  });
57
72
  module.exports = __toCommonJS(index_exports);
@@ -63,14 +78,50 @@ function bindFetch(custom) {
63
78
  return f.call(globalThis, input, init);
64
79
  };
65
80
  }
66
- var RagableError = class extends Error {
81
+ var DEFAULT_RAGABLE_API_BASE = "https://ragable-341305259977.asia-southeast1.run.app/api";
82
+ var RagableSdkError = class extends Error {
83
+ constructor(message) {
84
+ super(message);
85
+ this.name = this.constructor.name;
86
+ }
87
+ };
88
+ var RagableError = class extends RagableSdkError {
67
89
  constructor(message, status, body) {
68
90
  super(message);
91
+ __publicField(this, "__type", "RagableError");
69
92
  __publicField(this, "status");
70
93
  __publicField(this, "body");
71
- this.name = "RagableError";
94
+ __publicField(this, "code");
95
+ __publicField(this, "details");
72
96
  this.status = status;
73
97
  this.body = body;
98
+ if (body && typeof body === "object") {
99
+ const b = body;
100
+ this.code = typeof b.code === "string" ? b.code : void 0;
101
+ this.details = typeof b.details === "string" ? b.details : void 0;
102
+ }
103
+ }
104
+ };
105
+ var RagableNetworkError = class extends RagableSdkError {
106
+ constructor(message, cause) {
107
+ super(message);
108
+ __publicField(this, "__type", "RagableNetworkError");
109
+ __publicField(this, "cause");
110
+ this.cause = cause;
111
+ }
112
+ };
113
+ var RagableAbortError = class extends RagableSdkError {
114
+ constructor(message = "Request aborted") {
115
+ super(message);
116
+ __publicField(this, "__type", "RagableAbortError");
117
+ }
118
+ };
119
+ var RagableTimeoutError = class extends RagableSdkError {
120
+ constructor(timeoutMs) {
121
+ super(`Request timed out after ${timeoutMs}ms`);
122
+ __publicField(this, "__type", "RagableTimeoutError");
123
+ __publicField(this, "timeoutMs");
124
+ this.timeoutMs = timeoutMs;
74
125
  }
75
126
  };
76
127
  function extractErrorMessage(payload, fallback) {
@@ -94,7 +145,7 @@ var RagableRequestClient = class {
94
145
  __publicField(this, "fetchImpl");
95
146
  __publicField(this, "defaultHeaders");
96
147
  this.apiKey = options.apiKey;
97
- this.baseUrl = options.baseUrl ?? "http://localhost:8080/api";
148
+ this.baseUrl = options.baseUrl ?? DEFAULT_RAGABLE_API_BASE;
98
149
  this.fetchImpl = bindFetch(options.fetch);
99
150
  this.defaultHeaders = options.headers;
100
151
  }
@@ -398,42 +449,191 @@ var AgentsClient = class {
398
449
  }
399
450
  };
400
451
 
401
- // src/browser-postgrest.ts
402
- function assertIdent(name, ctx) {
403
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
404
- throw new RagableError(
405
- `Invalid ${ctx} identifier "${name}" (use letters, numbers, underscore)`,
406
- 400,
407
- null
408
- );
452
+ // src/transport.ts
453
+ var DEFAULT_RETRY = {
454
+ maxRetries: 3,
455
+ baseDelayMs: 200,
456
+ maxDelayMs: 5e3,
457
+ retryOn: [408, 425, 429, 502, 503, 504],
458
+ respectRetryAfter: true
459
+ };
460
+ var DEFAULT_TIMEOUT_MS = 3e4;
461
+ function jitteredDelay(base, attempt, max) {
462
+ const exp = Math.min(base * 2 ** attempt, max);
463
+ return Math.round(exp * (0.5 + Math.random() * 0.5));
464
+ }
465
+ function parseRetryAfter(header) {
466
+ if (!header) return null;
467
+ const seconds = Number(header);
468
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
469
+ const date = Date.parse(header);
470
+ if (Number.isFinite(date)) return Math.max(0, date - Date.now());
471
+ return null;
472
+ }
473
+ var _uuidCounter = 0;
474
+ function generateIdempotencyKey() {
475
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
476
+ return crypto.randomUUID();
409
477
  }
478
+ _uuidCounter++;
479
+ return `idk-${Date.now()}-${_uuidCounter}-${Math.random().toString(36).slice(2, 10)}`;
410
480
  }
411
- function quoteIdent(name) {
412
- assertIdent(name, "column");
413
- return `"${name}"`;
481
+ function requestCacheKey(req) {
482
+ return `${req.method}:${req.url}`;
414
483
  }
415
- function parseConflictColumns(onConflict) {
416
- const parts = onConflict.split(",").map((s) => s.trim()).filter(Boolean);
417
- if (parts.length === 0) {
418
- throw new RagableError(
419
- "upsert requires onConflict with at least one column (e.g. 'id' or 'org_id,user_id')",
420
- 400,
421
- null
484
+ var Transport = class {
485
+ constructor(options = {}) {
486
+ __publicField(this, "fetchImpl");
487
+ __publicField(this, "defaultHeaders");
488
+ __publicField(this, "defaultRetry");
489
+ __publicField(this, "defaultTimeoutMs");
490
+ __publicField(this, "onRequest");
491
+ __publicField(this, "onResponse");
492
+ __publicField(this, "onRetry");
493
+ __publicField(this, "inflightGets", /* @__PURE__ */ new Map());
494
+ __publicField(this, "_refreshHandler", null);
495
+ this.fetchImpl = bindFetch(options.fetch);
496
+ this.defaultHeaders = options.headers;
497
+ this.defaultRetry = { ...DEFAULT_RETRY, ...options.retry };
498
+ this.defaultTimeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
499
+ this.onRequest = options.onRequest;
500
+ this.onResponse = options.onResponse;
501
+ this.onRetry = options.onRetry;
502
+ }
503
+ setRefreshHandler(handler) {
504
+ this._refreshHandler = handler;
505
+ }
506
+ async execute(req) {
507
+ if (req.method === "GET") {
508
+ const key = requestCacheKey(req);
509
+ const existing = this.inflightGets.get(key);
510
+ if (existing) return existing;
511
+ const promise = this._executeWithRetry(req).finally(() => {
512
+ this.inflightGets.delete(key);
513
+ });
514
+ this.inflightGets.set(key, promise);
515
+ return promise;
516
+ }
517
+ return this._executeWithRetry(req);
518
+ }
519
+ async _executeWithRetry(req) {
520
+ const retryOpts = {
521
+ ...this.defaultRetry,
522
+ ...req.retry
523
+ };
524
+ const timeoutMs = req.timeoutMs ?? this.defaultTimeoutMs;
525
+ const headers = new Headers(this.defaultHeaders);
526
+ req.headers.forEach((v, k) => headers.set(k, v));
527
+ if (req.idempotencyKey) {
528
+ headers.set("Idempotency-Key", req.idempotencyKey);
529
+ }
530
+ const finalReq = { ...req, headers };
531
+ this.onRequest?.(finalReq);
532
+ let lastError;
533
+ const maxAttempts = 1 + retryOpts.maxRetries;
534
+ let did401Refresh = false;
535
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
536
+ try {
537
+ const response = await this._singleFetch(finalReq, timeoutMs);
538
+ if (response.status === 401 && this._refreshHandler && !did401Refresh) {
539
+ did401Refresh = true;
540
+ const newToken = await this._refreshHandler();
541
+ if (newToken) {
542
+ finalReq.headers.set("Authorization", `Bearer ${newToken}`);
543
+ attempt--;
544
+ continue;
545
+ }
546
+ }
547
+ if (!response.ok && retryOpts.retryOn.includes(response.status) && attempt < maxAttempts - 1) {
548
+ let delayMs = jitteredDelay(retryOpts.baseDelayMs, attempt, retryOpts.maxDelayMs);
549
+ if (retryOpts.respectRetryAfter) {
550
+ const ra = parseRetryAfter(response.headers.get("retry-after"));
551
+ if (ra !== null) delayMs = Math.min(ra, retryOpts.maxDelayMs);
552
+ }
553
+ this.onRetry?.(finalReq, attempt + 1, delayMs, `HTTP ${response.status}`);
554
+ await sleep(delayMs);
555
+ continue;
556
+ }
557
+ return response;
558
+ } catch (e) {
559
+ if (e instanceof RagableAbortError || e instanceof RagableTimeoutError) {
560
+ throw e;
561
+ }
562
+ lastError = e;
563
+ if (attempt < maxAttempts - 1) {
564
+ const delayMs = jitteredDelay(retryOpts.baseDelayMs, attempt, retryOpts.maxDelayMs);
565
+ this.onRetry?.(finalReq, attempt + 1, delayMs, e.message);
566
+ await sleep(delayMs);
567
+ continue;
568
+ }
569
+ }
570
+ }
571
+ throw lastError instanceof RagableNetworkError ? lastError : new RagableNetworkError(
572
+ lastError?.message ?? "Network request failed",
573
+ lastError
422
574
  );
423
575
  }
424
- for (const p of parts) assertIdent(p, "onConflict");
425
- return parts;
426
- }
427
- var OP_SQL = {
428
- eq: "=",
429
- neq: "<>",
430
- gt: ">",
431
- gte: ">=",
432
- lt: "<",
433
- lte: "<=",
434
- like: "LIKE",
435
- ilike: "ILIKE"
576
+ async _singleFetch(req, timeoutMs) {
577
+ const controller = new AbortController();
578
+ const signals = [controller.signal];
579
+ if (req.signal) signals.push(req.signal);
580
+ const combinedSignal = signals.length === 1 ? controller.signal : AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
581
+ if (req.signal?.aborted) {
582
+ throw new RagableAbortError();
583
+ }
584
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
585
+ const externalAbortHandler = req.signal ? () => controller.abort() : null;
586
+ if (externalAbortHandler && req.signal) {
587
+ req.signal.addEventListener("abort", externalAbortHandler, { once: true });
588
+ }
589
+ const start = Date.now();
590
+ try {
591
+ const response = await this.fetchImpl(req.url, {
592
+ method: req.method,
593
+ headers: req.headers,
594
+ body: req.body,
595
+ signal: combinedSignal
596
+ });
597
+ this.onResponse?.(req, response, Date.now() - start);
598
+ return response;
599
+ } catch (e) {
600
+ if (e.name === "AbortError") {
601
+ if (req.signal?.aborted) throw new RagableAbortError();
602
+ throw new RagableTimeoutError(timeoutMs);
603
+ }
604
+ throw new RagableNetworkError(e.message, e);
605
+ } finally {
606
+ clearTimeout(timer);
607
+ if (externalAbortHandler && req.signal) {
608
+ req.signal.removeEventListener("abort", externalAbortHandler);
609
+ }
610
+ }
611
+ }
436
612
  };
613
+ function sleep(ms) {
614
+ return new Promise((resolve) => setTimeout(resolve, ms));
615
+ }
616
+ async function parseTransportResponse(response) {
617
+ if (response.status === 204) return null;
618
+ const text = await response.text();
619
+ if (!text) return null;
620
+ let payload;
621
+ try {
622
+ payload = JSON.parse(text);
623
+ } catch {
624
+ if (!response.ok) {
625
+ throw new RagableError(text.slice(0, 200), response.status, null);
626
+ }
627
+ return text;
628
+ }
629
+ if (!response.ok) {
630
+ const message = extractErrorMessage(payload, response.statusText);
631
+ throw new RagableError(message, response.status, payload);
632
+ }
633
+ return payload;
634
+ }
635
+
636
+ // src/browser-postgrest.ts
437
637
  async function asPostgrestResponse(fn) {
438
638
  try {
439
639
  const data = await fn();
@@ -443,28 +643,75 @@ async function asPostgrestResponse(fn) {
443
643
  return { data: null, error: err };
444
644
  }
445
645
  }
446
- function buildWhere(filters, params) {
447
- if (filters.length === 0) return { clause: "", nextIdx: 1 };
448
- const parts = [];
449
- let i = params.length;
450
- for (const f of filters) {
451
- assertIdent(f.column, "filter");
452
- i += 1;
453
- parts.push(`${quoteIdent(f.column)} ${OP_SQL[f.op]} $${i}`);
454
- params.push(f.value);
455
- }
456
- return { clause: ` WHERE ${parts.join(" AND ")}`, nextIdx: i };
646
+ function encodeFilterValue(op, value) {
647
+ if (op === "is") return `is.${value}`;
648
+ if (op === "in") {
649
+ const vals = value;
650
+ return `in.(${vals.map(String).join(",")})`;
651
+ }
652
+ return `${op}.${value}`;
653
+ }
654
+ async function parsePostgRESTResponse(response) {
655
+ if (response.status === 204) return null;
656
+ const text = await response.text();
657
+ if (!text) return null;
658
+ let payload;
659
+ try {
660
+ payload = JSON.parse(text);
661
+ } catch {
662
+ throw new RagableError(
663
+ `PostgREST response parse error: ${text.slice(0, 200)}`,
664
+ response.status,
665
+ null
666
+ );
667
+ }
668
+ if (!response.ok) {
669
+ const msg = typeof payload === "object" && payload !== null ? payload.message ?? payload.error ?? response.statusText : response.statusText;
670
+ const code = typeof payload === "object" && payload !== null ? payload.code : void 0;
671
+ throw new RagableError(String(msg), response.status, { code });
672
+ }
673
+ return payload;
674
+ }
675
+ function addFilterMethods(builder) {
676
+ const b = builder;
677
+ for (const op of ["eq", "neq", "gt", "gte", "lt", "lte", "like", "ilike"]) {
678
+ b[op] = function(column, value) {
679
+ b.filters.push({ op, column, value });
680
+ return b;
681
+ };
682
+ }
683
+ b.is = function(column, value) {
684
+ b.filters.push({ op: "is", column, value });
685
+ return b;
686
+ };
687
+ b.in = function(column, values) {
688
+ b.filters.push({ op: "in", column, value: values });
689
+ return b;
690
+ };
691
+ b.match = function(query) {
692
+ for (const [col, val] of Object.entries(query)) {
693
+ if (val === null) {
694
+ b.filters.push({ op: "is", column: col, value: null });
695
+ } else {
696
+ b.filters.push({ op: "eq", column: col, value: val });
697
+ }
698
+ }
699
+ return b;
700
+ };
701
+ return b;
457
702
  }
458
703
  var PostgrestSelectBuilder = class {
459
- constructor(run, databaseInstanceId, table, columns) {
460
- this.run = run;
704
+ constructor(pgFetch, databaseInstanceId, table, columns) {
705
+ this.pgFetch = pgFetch;
461
706
  this.databaseInstanceId = databaseInstanceId;
462
707
  this.table = table;
463
708
  this.columns = columns;
464
709
  __publicField(this, "filters", []);
465
710
  __publicField(this, "_limit");
711
+ __publicField(this, "_offset");
466
712
  __publicField(this, "_order");
467
- assertIdent(table, "table");
713
+ __publicField(this, "_signal");
714
+ addFilterMethods(this);
468
715
  }
469
716
  eq(column, value) {
470
717
  this.filters.push({ op: "eq", column, value });
@@ -498,140 +745,173 @@ var PostgrestSelectBuilder = class {
498
745
  this.filters.push({ op: "ilike", column, value });
499
746
  return this;
500
747
  }
748
+ is(column, value) {
749
+ this.filters.push({ op: "is", column, value });
750
+ return this;
751
+ }
752
+ in(column, values) {
753
+ this.filters.push({ op: "in", column, value: values });
754
+ return this;
755
+ }
756
+ match(query) {
757
+ for (const [col, val] of Object.entries(query)) {
758
+ if (val === null) {
759
+ this.filters.push({ op: "is", column: col, value: null });
760
+ } else {
761
+ this.filters.push({ op: "eq", column: col, value: val });
762
+ }
763
+ }
764
+ return this;
765
+ }
501
766
  limit(n) {
502
767
  this._limit = n;
503
768
  return this;
504
769
  }
770
+ offset(n) {
771
+ this._offset = n;
772
+ return this;
773
+ }
774
+ range(from, to) {
775
+ this._offset = from;
776
+ this._limit = to - from + 1;
777
+ return this;
778
+ }
505
779
  order(column, options) {
506
- this._order = { column, ascending: options?.ascending !== false };
780
+ this._order = {
781
+ column,
782
+ ascending: options?.ascending !== false,
783
+ nullsFirst: options?.nullsFirst
784
+ };
785
+ return this;
786
+ }
787
+ abortSignal(signal) {
788
+ this._signal = signal;
507
789
  return this;
508
790
  }
509
- buildSelectCore(params, includeUserLimit) {
510
- const tbl = quoteIdent(this.table);
511
- const { clause } = buildWhere(this.filters, params);
512
- let sql = `SELECT ${this.columns} FROM ${tbl}${clause}`;
791
+ buildSearchParams() {
792
+ const sp = new URLSearchParams();
793
+ if (this.columns && this.columns !== "*") {
794
+ sp.set("select", this.columns);
795
+ }
796
+ for (const f of this.filters) {
797
+ sp.append(f.column, encodeFilterValue(f.op, f.value));
798
+ }
513
799
  if (this._order) {
514
- sql += ` ORDER BY ${quoteIdent(this._order.column)} ${this._order.ascending ? "ASC" : "DESC"}`;
800
+ let orderStr = `${this._order.column}.${this._order.ascending ? "asc" : "desc"}`;
801
+ if (this._order.nullsFirst === true) orderStr += ".nullsfirst";
802
+ else if (this._order.nullsFirst === false) orderStr += ".nullslast";
803
+ sp.set("order", orderStr);
515
804
  }
516
- if (includeUserLimit && this._limit != null) {
517
- sql += ` LIMIT ${Math.max(0, Math.floor(this._limit))}`;
805
+ if (this._limit != null) {
806
+ sp.set("limit", String(Math.max(0, Math.floor(this._limit))));
518
807
  }
519
- return sql;
808
+ if (this._offset != null && this._offset > 0) {
809
+ sp.set("offset", String(Math.max(0, Math.floor(this._offset))));
810
+ }
811
+ return sp;
520
812
  }
521
813
  then(onfulfilled, onrejected) {
522
814
  return this.executeMany().then(onfulfilled, onrejected);
523
815
  }
524
816
  async executeMany() {
525
817
  return asPostgrestResponse(async () => {
526
- const params = [];
527
- const sql = this.buildSelectCore(params, true);
528
- const res = await this.run({
818
+ const response = await this.pgFetch({
819
+ method: "GET",
820
+ table: this.table,
821
+ searchParams: this.buildSearchParams(),
529
822
  databaseInstanceId: this.databaseInstanceId,
530
- sql,
531
- params,
532
- readOnly: true
823
+ signal: this._signal
533
824
  });
534
- return res.rows;
825
+ return parsePostgRESTResponse(response);
535
826
  });
536
827
  }
537
828
  async single() {
538
829
  return asPostgrestResponse(async () => {
539
- const params = [];
540
- const base = this.buildSelectCore(params, false);
541
- const sql = `${base} LIMIT 2`;
542
- const res = await this.run({
830
+ const sp = this.buildSearchParams();
831
+ const response = await this.pgFetch({
832
+ method: "GET",
833
+ table: this.table,
834
+ searchParams: sp,
835
+ headers: { Accept: "application/vnd.pgrst.object+json" },
543
836
  databaseInstanceId: this.databaseInstanceId,
544
- sql,
545
- params,
546
- readOnly: true
837
+ signal: this._signal
547
838
  });
548
- if (res.rows.length === 0 || res.rows.length > 1) {
549
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
550
- code: "PGRST116"
551
- });
552
- }
553
- return res.rows[0];
839
+ return parsePostgRESTResponse(response);
554
840
  });
555
841
  }
556
842
  async maybeSingle() {
557
843
  return asPostgrestResponse(async () => {
558
- const params = [];
559
- const base = this.buildSelectCore(params, false);
560
- const sql = `${base} LIMIT 2`;
561
- const res = await this.run({
844
+ const response = await this.pgFetch({
845
+ method: "GET",
846
+ table: this.table,
847
+ searchParams: this.buildSearchParams(),
562
848
  databaseInstanceId: this.databaseInstanceId,
563
- sql,
564
- params,
565
- readOnly: true
849
+ signal: this._signal
566
850
  });
567
- if (res.rows.length > 1) {
568
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
569
- code: "PGRST116"
570
- });
851
+ const rows = await parsePostgRESTResponse(response);
852
+ if (rows.length > 1) {
853
+ throw new RagableError(
854
+ "JSON object requested, multiple (or no) rows returned",
855
+ 406,
856
+ { code: "PGRST116" }
857
+ );
571
858
  }
572
- return res.rows[0] ?? null;
859
+ return rows[0] ?? null;
573
860
  });
574
861
  }
575
862
  };
576
863
  var PostgrestInsertRootBuilder = class {
577
- constructor(run, databaseInstanceId, table, rows) {
578
- this.run = run;
864
+ constructor(pgFetch, databaseInstanceId, table, rows) {
865
+ this.pgFetch = pgFetch;
579
866
  this.databaseInstanceId = databaseInstanceId;
580
867
  this.table = table;
581
868
  this.rows = rows;
582
- assertIdent(table, "table");
869
+ __publicField(this, "_signal");
583
870
  }
584
- /**
585
- * Return inserted rows (Supabase: chain `.select()` to get data).
586
- * @see https://supabase.com/docs/reference/javascript/insert
587
- */
588
871
  select(columns = "*") {
589
872
  return new PostgrestInsertReturningBuilder(
590
- this.run,
873
+ this.pgFetch,
591
874
  this.databaseInstanceId,
592
875
  this.table,
593
876
  this.rows,
594
- columns
877
+ columns,
878
+ this._signal
595
879
  );
596
880
  }
881
+ abortSignal(signal) {
882
+ this._signal = signal;
883
+ return this;
884
+ }
597
885
  then(onfulfilled, onrejected) {
598
886
  return this.executeNoReturn().then(onfulfilled, onrejected);
599
887
  }
600
888
  async executeNoReturn() {
601
889
  return asPostgrestResponse(async () => {
602
890
  if (this.rows.length === 0) return null;
603
- const keys = Object.keys(this.rows[0]);
604
- for (const k of keys) assertIdent(k, "column");
605
- const tbl = quoteIdent(this.table);
606
- const params = [];
607
- const valueGroups = [];
608
- for (const row of this.rows) {
609
- const placeholders = [];
610
- for (const k of keys) {
611
- params.push(row[k]);
612
- placeholders.push(`$${params.length}`);
613
- }
614
- valueGroups.push(`(${placeholders.join(", ")})`);
615
- }
616
- const cols = keys.map(quoteIdent).join(", ");
617
- const sql = `INSERT INTO ${tbl} (${cols}) VALUES ${valueGroups.join(", ")}`;
618
- await this.run({
891
+ const body = this.rows.length === 1 ? this.rows[0] : this.rows;
892
+ const response = await this.pgFetch({
893
+ method: "POST",
894
+ table: this.table,
895
+ searchParams: new URLSearchParams(),
896
+ body,
897
+ headers: { Prefer: "return=minimal" },
619
898
  databaseInstanceId: this.databaseInstanceId,
620
- sql,
621
- params,
622
- readOnly: false
899
+ signal: this._signal,
900
+ idempotencyKey: generateIdempotencyKey()
623
901
  });
902
+ await parsePostgRESTResponse(response);
624
903
  return null;
625
904
  });
626
905
  }
627
906
  };
628
907
  var PostgrestInsertReturningBuilder = class {
629
- constructor(run, databaseInstanceId, table, rows, returning) {
630
- this.run = run;
908
+ constructor(pgFetch, databaseInstanceId, table, rows, returning, _signal) {
909
+ this.pgFetch = pgFetch;
631
910
  this.databaseInstanceId = databaseInstanceId;
632
911
  this.table = table;
633
912
  this.rows = rows;
634
913
  this.returning = returning;
914
+ this._signal = _signal;
635
915
  }
636
916
  then(onfulfilled, onrejected) {
637
917
  return this.executeMany().then(onfulfilled, onrejected);
@@ -639,28 +919,22 @@ var PostgrestInsertReturningBuilder = class {
639
919
  async executeMany() {
640
920
  return asPostgrestResponse(async () => {
641
921
  if (this.rows.length === 0) return [];
642
- const keys = Object.keys(this.rows[0]);
643
- for (const k of keys) assertIdent(k, "column");
644
- const tbl = quoteIdent(this.table);
645
- const params = [];
646
- const valueGroups = [];
647
- for (const row of this.rows) {
648
- const placeholders = [];
649
- for (const k of keys) {
650
- params.push(row[k]);
651
- placeholders.push(`$${params.length}`);
652
- }
653
- valueGroups.push(`(${placeholders.join(", ")})`);
922
+ const body = this.rows.length === 1 ? this.rows[0] : this.rows;
923
+ const sp = new URLSearchParams();
924
+ if (this.returning && this.returning !== "*") {
925
+ sp.set("select", this.returning);
654
926
  }
655
- const cols = keys.map(quoteIdent).join(", ");
656
- const sql = `INSERT INTO ${tbl} (${cols}) VALUES ${valueGroups.join(", ")} RETURNING ${this.returning}`;
657
- const res = await this.run({
927
+ const response = await this.pgFetch({
928
+ method: "POST",
929
+ table: this.table,
930
+ searchParams: sp,
931
+ body,
932
+ headers: { Prefer: "return=representation" },
658
933
  databaseInstanceId: this.databaseInstanceId,
659
- sql,
660
- params,
661
- readOnly: false
934
+ signal: this._signal,
935
+ idempotencyKey: generateIdempotencyKey()
662
936
  });
663
- return res.rows;
937
+ return parsePostgRESTResponse(response);
664
938
  });
665
939
  }
666
940
  async single() {
@@ -669,9 +943,11 @@ var PostgrestInsertReturningBuilder = class {
669
943
  if (error) throw error;
670
944
  const rows = data ?? [];
671
945
  if (rows.length === 0 || rows.length > 1) {
672
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
673
- code: "PGRST116"
674
- });
946
+ throw new RagableError(
947
+ "JSON object requested, multiple (or no) rows returned",
948
+ 406,
949
+ { code: "PGRST116" }
950
+ );
675
951
  }
676
952
  return rows[0];
677
953
  });
@@ -682,22 +958,24 @@ var PostgrestInsertReturningBuilder = class {
682
958
  if (error) throw error;
683
959
  const rows = data ?? [];
684
960
  if (rows.length > 1) {
685
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
686
- code: "PGRST116"
687
- });
961
+ throw new RagableError(
962
+ "JSON object requested, multiple (or no) rows returned",
963
+ 406,
964
+ { code: "PGRST116" }
965
+ );
688
966
  }
689
967
  return rows[0] ?? null;
690
968
  });
691
969
  }
692
970
  };
693
971
  var PostgrestUpdateRootBuilder = class {
694
- constructor(run, databaseInstanceId, table, patch) {
695
- this.run = run;
972
+ constructor(pgFetch, databaseInstanceId, table, patch) {
973
+ this.pgFetch = pgFetch;
696
974
  this.databaseInstanceId = databaseInstanceId;
697
975
  this.table = table;
698
976
  this.patch = patch;
699
977
  __publicField(this, "filters", []);
700
- assertIdent(table, "table");
978
+ __publicField(this, "_signal");
701
979
  }
702
980
  eq(column, value) {
703
981
  this.filters.push({ op: "eq", column, value });
@@ -731,64 +1009,79 @@ var PostgrestUpdateRootBuilder = class {
731
1009
  this.filters.push({ op: "ilike", column, value });
732
1010
  return this;
733
1011
  }
734
- /**
735
- * Return updated rows (Supabase: `.update().eq().select()`).
736
- * @see https://supabase.com/docs/reference/javascript/update
737
- */
1012
+ is(column, value) {
1013
+ this.filters.push({ op: "is", column, value });
1014
+ return this;
1015
+ }
1016
+ in(column, values) {
1017
+ this.filters.push({ op: "in", column, value: values });
1018
+ return this;
1019
+ }
1020
+ match(query) {
1021
+ for (const [col, val] of Object.entries(query)) {
1022
+ if (val === null) {
1023
+ this.filters.push({ op: "is", column: col, value: null });
1024
+ } else {
1025
+ this.filters.push({ op: "eq", column: col, value: val });
1026
+ }
1027
+ }
1028
+ return this;
1029
+ }
738
1030
  select(columns = "*") {
739
1031
  return new PostgrestUpdateReturningBuilder(
740
- this.run,
1032
+ this.pgFetch,
741
1033
  this.databaseInstanceId,
742
1034
  this.table,
743
1035
  this.patch,
744
1036
  this.filters,
745
- columns
1037
+ columns,
1038
+ this._signal
746
1039
  );
747
1040
  }
1041
+ abortSignal(signal) {
1042
+ this._signal = signal;
1043
+ return this;
1044
+ }
748
1045
  then(onfulfilled, onrejected) {
749
1046
  return this.executeNoReturn().then(onfulfilled, onrejected);
750
1047
  }
1048
+ buildSearchParams() {
1049
+ const sp = new URLSearchParams();
1050
+ for (const f of this.filters) {
1051
+ sp.append(f.column, encodeFilterValue(f.op, f.value));
1052
+ }
1053
+ return sp;
1054
+ }
751
1055
  async executeNoReturn() {
752
1056
  return asPostgrestResponse(async () => {
753
1057
  const keys = Object.keys(this.patch);
754
1058
  if (keys.length === 0) {
755
1059
  throw new RagableError("Empty update payload", 400, null);
756
1060
  }
757
- for (const k of keys) assertIdent(k, "column");
758
- if (this.filters.length === 0) {
759
- throw new RagableError(
760
- "UPDATE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped update",
761
- 400,
762
- null
763
- );
764
- }
765
- const params = [];
766
- const sets = [];
767
- for (const k of keys) {
768
- params.push(this.patch[k]);
769
- sets.push(`${quoteIdent(k)} = $${params.length}`);
770
- }
771
- const { clause } = buildWhere(this.filters, params);
772
- const tbl = quoteIdent(this.table);
773
- const sql = `UPDATE ${tbl} SET ${sets.join(", ")}${clause}`;
774
- await this.run({
1061
+ const response = await this.pgFetch({
1062
+ method: "PATCH",
1063
+ table: this.table,
1064
+ searchParams: this.buildSearchParams(),
1065
+ body: this.patch,
1066
+ headers: { Prefer: "return=minimal" },
775
1067
  databaseInstanceId: this.databaseInstanceId,
776
- sql,
777
- params,
778
- readOnly: false
1068
+ signal: this._signal,
1069
+ idempotencyKey: generateIdempotencyKey()
779
1070
  });
1071
+ await parsePostgRESTResponse(response);
780
1072
  return null;
781
1073
  });
782
1074
  }
783
1075
  };
784
1076
  var PostgrestUpdateReturningBuilder = class {
785
- constructor(run, databaseInstanceId, table, patch, filters, returning) {
786
- this.run = run;
1077
+ constructor(pgFetch, databaseInstanceId, table, patch, filters, returning, _signal) {
1078
+ this.pgFetch = pgFetch;
787
1079
  this.databaseInstanceId = databaseInstanceId;
788
1080
  this.table = table;
789
1081
  this.patch = patch;
790
1082
  this.filters = filters;
791
1083
  this.returning = returning;
1084
+ this._signal = _signal;
792
1085
  }
793
1086
  then(onfulfilled, onrejected) {
794
1087
  return this.executeMany().then(onfulfilled, onrejected);
@@ -799,30 +1092,24 @@ var PostgrestUpdateReturningBuilder = class {
799
1092
  if (keys.length === 0) {
800
1093
  throw new RagableError("Empty update payload", 400, null);
801
1094
  }
802
- for (const k of keys) assertIdent(k, "column");
803
- if (this.filters.length === 0) {
804
- throw new RagableError(
805
- "UPDATE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped update",
806
- 400,
807
- null
808
- );
1095
+ const sp = new URLSearchParams();
1096
+ for (const f of this.filters) {
1097
+ sp.append(f.column, encodeFilterValue(f.op, f.value));
809
1098
  }
810
- const params = [];
811
- const sets = [];
812
- for (const k of keys) {
813
- params.push(this.patch[k]);
814
- sets.push(`${quoteIdent(k)} = $${params.length}`);
1099
+ if (this.returning && this.returning !== "*") {
1100
+ sp.set("select", this.returning);
815
1101
  }
816
- const { clause } = buildWhere(this.filters, params);
817
- const tbl = quoteIdent(this.table);
818
- const sql = `UPDATE ${tbl} SET ${sets.join(", ")}${clause} RETURNING ${this.returning}`;
819
- const res = await this.run({
1102
+ const response = await this.pgFetch({
1103
+ method: "PATCH",
1104
+ table: this.table,
1105
+ searchParams: sp,
1106
+ body: this.patch,
1107
+ headers: { Prefer: "return=representation" },
820
1108
  databaseInstanceId: this.databaseInstanceId,
821
- sql,
822
- params,
823
- readOnly: false
1109
+ signal: this._signal,
1110
+ idempotencyKey: generateIdempotencyKey()
824
1111
  });
825
- return res.rows;
1112
+ return parsePostgRESTResponse(response);
826
1113
  });
827
1114
  }
828
1115
  async single() {
@@ -831,9 +1118,11 @@ var PostgrestUpdateReturningBuilder = class {
831
1118
  if (error) throw error;
832
1119
  const rows = data ?? [];
833
1120
  if (rows.length === 0 || rows.length > 1) {
834
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
835
- code: "PGRST116"
836
- });
1121
+ throw new RagableError(
1122
+ "JSON object requested, multiple (or no) rows returned",
1123
+ 406,
1124
+ { code: "PGRST116" }
1125
+ );
837
1126
  }
838
1127
  return rows[0];
839
1128
  });
@@ -844,21 +1133,23 @@ var PostgrestUpdateReturningBuilder = class {
844
1133
  if (error) throw error;
845
1134
  const rows = data ?? [];
846
1135
  if (rows.length > 1) {
847
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
848
- code: "PGRST116"
849
- });
1136
+ throw new RagableError(
1137
+ "JSON object requested, multiple (or no) rows returned",
1138
+ 406,
1139
+ { code: "PGRST116" }
1140
+ );
850
1141
  }
851
1142
  return rows[0] ?? null;
852
1143
  });
853
1144
  }
854
1145
  };
855
1146
  var PostgrestDeleteRootBuilder = class {
856
- constructor(run, databaseInstanceId, table) {
857
- this.run = run;
1147
+ constructor(pgFetch, databaseInstanceId, table) {
1148
+ this.pgFetch = pgFetch;
858
1149
  this.databaseInstanceId = databaseInstanceId;
859
1150
  this.table = table;
860
1151
  __publicField(this, "filters", []);
861
- assertIdent(table, "table");
1152
+ __publicField(this, "_signal");
862
1153
  }
863
1154
  eq(column, value) {
864
1155
  this.filters.push({ op: "eq", column, value });
@@ -892,76 +1183,92 @@ var PostgrestDeleteRootBuilder = class {
892
1183
  this.filters.push({ op: "ilike", column, value });
893
1184
  return this;
894
1185
  }
895
- /**
896
- * Return deleted rows (Supabase: `.delete().eq().select()`).
897
- * @see https://supabase.com/docs/reference/javascript/delete
898
- */
1186
+ is(column, value) {
1187
+ this.filters.push({ op: "is", column, value });
1188
+ return this;
1189
+ }
1190
+ in(column, values) {
1191
+ this.filters.push({ op: "in", column, value: values });
1192
+ return this;
1193
+ }
1194
+ match(query) {
1195
+ for (const [col, val] of Object.entries(query)) {
1196
+ if (val === null) {
1197
+ this.filters.push({ op: "is", column: col, value: null });
1198
+ } else {
1199
+ this.filters.push({ op: "eq", column: col, value: val });
1200
+ }
1201
+ }
1202
+ return this;
1203
+ }
899
1204
  select(columns = "*") {
900
1205
  return new PostgrestDeleteReturningBuilder(
901
- this.run,
1206
+ this.pgFetch,
902
1207
  this.databaseInstanceId,
903
1208
  this.table,
904
1209
  this.filters,
905
- columns
1210
+ columns,
1211
+ this._signal
906
1212
  );
907
1213
  }
1214
+ abortSignal(signal) {
1215
+ this._signal = signal;
1216
+ return this;
1217
+ }
908
1218
  then(onfulfilled, onrejected) {
909
1219
  return this.executeNoReturn().then(onfulfilled, onrejected);
910
1220
  }
911
1221
  async executeNoReturn() {
912
1222
  return asPostgrestResponse(async () => {
913
- if (this.filters.length === 0) {
914
- throw new RagableError(
915
- "DELETE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped delete",
916
- 400,
917
- null
918
- );
1223
+ const sp = new URLSearchParams();
1224
+ for (const f of this.filters) {
1225
+ sp.append(f.column, encodeFilterValue(f.op, f.value));
919
1226
  }
920
- const params = [];
921
- const { clause } = buildWhere(this.filters, params);
922
- const tbl = quoteIdent(this.table);
923
- const sql = `DELETE FROM ${tbl}${clause}`;
924
- await this.run({
1227
+ const response = await this.pgFetch({
1228
+ method: "DELETE",
1229
+ table: this.table,
1230
+ searchParams: sp,
1231
+ headers: { Prefer: "return=minimal" },
925
1232
  databaseInstanceId: this.databaseInstanceId,
926
- sql,
927
- params,
928
- readOnly: false
1233
+ signal: this._signal,
1234
+ idempotencyKey: generateIdempotencyKey()
929
1235
  });
1236
+ await parsePostgRESTResponse(response);
930
1237
  return null;
931
1238
  });
932
1239
  }
933
1240
  };
934
1241
  var PostgrestDeleteReturningBuilder = class {
935
- constructor(run, databaseInstanceId, table, filters, returning) {
936
- this.run = run;
1242
+ constructor(pgFetch, databaseInstanceId, table, filters, returning, _signal) {
1243
+ this.pgFetch = pgFetch;
937
1244
  this.databaseInstanceId = databaseInstanceId;
938
1245
  this.table = table;
939
1246
  this.filters = filters;
940
1247
  this.returning = returning;
1248
+ this._signal = _signal;
941
1249
  }
942
1250
  then(onfulfilled, onrejected) {
943
1251
  return this.executeMany().then(onfulfilled, onrejected);
944
1252
  }
945
1253
  async executeMany() {
946
1254
  return asPostgrestResponse(async () => {
947
- if (this.filters.length === 0) {
948
- throw new RagableError(
949
- "DELETE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped delete",
950
- 400,
951
- null
952
- );
1255
+ const sp = new URLSearchParams();
1256
+ for (const f of this.filters) {
1257
+ sp.append(f.column, encodeFilterValue(f.op, f.value));
1258
+ }
1259
+ if (this.returning && this.returning !== "*") {
1260
+ sp.set("select", this.returning);
953
1261
  }
954
- const params = [];
955
- const { clause } = buildWhere(this.filters, params);
956
- const tbl = quoteIdent(this.table);
957
- const sql = `DELETE FROM ${tbl}${clause} RETURNING ${this.returning}`;
958
- const res = await this.run({
1262
+ const response = await this.pgFetch({
1263
+ method: "DELETE",
1264
+ table: this.table,
1265
+ searchParams: sp,
1266
+ headers: { Prefer: "return=representation" },
959
1267
  databaseInstanceId: this.databaseInstanceId,
960
- sql,
961
- params,
962
- readOnly: false
1268
+ signal: this._signal,
1269
+ idempotencyKey: generateIdempotencyKey()
963
1270
  });
964
- return res.rows;
1271
+ return parsePostgRESTResponse(response);
965
1272
  });
966
1273
  }
967
1274
  async single() {
@@ -970,9 +1277,11 @@ var PostgrestDeleteReturningBuilder = class {
970
1277
  if (error) throw error;
971
1278
  const rows = data ?? [];
972
1279
  if (rows.length === 0 || rows.length > 1) {
973
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
974
- code: "PGRST116"
975
- });
1280
+ throw new RagableError(
1281
+ "JSON object requested, multiple (or no) rows returned",
1282
+ 406,
1283
+ { code: "PGRST116" }
1284
+ );
976
1285
  }
977
1286
  return rows[0];
978
1287
  });
@@ -983,84 +1292,66 @@ var PostgrestDeleteReturningBuilder = class {
983
1292
  if (error) throw error;
984
1293
  const rows = data ?? [];
985
1294
  if (rows.length > 1) {
986
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
987
- code: "PGRST116"
988
- });
1295
+ throw new RagableError(
1296
+ "JSON object requested, multiple (or no) rows returned",
1297
+ 406,
1298
+ { code: "PGRST116" }
1299
+ );
989
1300
  }
990
1301
  return rows[0] ?? null;
991
1302
  });
992
1303
  }
993
1304
  };
994
1305
  var PostgrestUpsertRootBuilder = class {
995
- constructor(run, databaseInstanceId, table, rows, onConflict, ignoreDuplicates) {
996
- this.run = run;
1306
+ constructor(pgFetch, databaseInstanceId, table, rows, onConflict, ignoreDuplicates) {
1307
+ this.pgFetch = pgFetch;
997
1308
  this.databaseInstanceId = databaseInstanceId;
998
1309
  this.table = table;
999
1310
  this.rows = rows;
1311
+ this.onConflict = onConflict;
1000
1312
  this.ignoreDuplicates = ignoreDuplicates;
1001
- __publicField(this, "conflictCols");
1002
- assertIdent(table, "table");
1003
- this.conflictCols = parseConflictColumns(onConflict);
1313
+ __publicField(this, "_signal");
1004
1314
  }
1005
- /**
1006
- * Return upserted rows (Supabase: `.upsert().select()`).
1007
- * @see https://supabase.com/docs/reference/javascript/upsert
1008
- */
1009
1315
  select(columns = "*") {
1010
1316
  return new PostgrestUpsertReturningBuilder(this, columns);
1011
1317
  }
1318
+ abortSignal(signal) {
1319
+ this._signal = signal;
1320
+ return this;
1321
+ }
1012
1322
  then(onfulfilled, onrejected) {
1013
1323
  return this.executeNoReturn().then(onfulfilled, onrejected);
1014
1324
  }
1015
1325
  async executeNoReturn() {
1016
1326
  return asPostgrestResponse(async () => {
1017
- await this.runUpsert(null);
1327
+ await this.runUpsert("return=minimal", null);
1018
1328
  return null;
1019
1329
  });
1020
1330
  }
1021
- async runUpsert(returning) {
1022
- if (this.rows.length === 0) {
1023
- return { command: "INSERT", rowCount: 0, truncated: false, rows: [] };
1024
- }
1025
- const keys = Object.keys(this.rows[0]);
1026
- for (const k of keys) assertIdent(k, "column");
1027
- const tbl = quoteIdent(this.table);
1028
- const conflictQuoted = this.conflictCols.map(quoteIdent).join(", ");
1029
- const params = [];
1030
- const valueGroups = [];
1031
- for (const row of this.rows) {
1032
- const placeholders = [];
1033
- for (const k of keys) {
1034
- params.push(row[k]);
1035
- placeholders.push(`$${params.length}`);
1036
- }
1037
- valueGroups.push(`(${placeholders.join(", ")})`);
1038
- }
1039
- const cols = keys.map(quoteIdent).join(", ");
1040
- let sql = `INSERT INTO ${tbl} (${cols}) VALUES ${valueGroups.join(", ")} ON CONFLICT (${conflictQuoted})`;
1041
- if (this.ignoreDuplicates) {
1042
- sql += " DO NOTHING";
1043
- } else {
1044
- const setParts = keys.filter((k) => !this.conflictCols.includes(k)).map((k) => `${quoteIdent(k)} = EXCLUDED.${quoteIdent(k)}`);
1045
- if (setParts.length === 0) {
1046
- sql += " DO NOTHING";
1047
- } else {
1048
- sql += ` DO UPDATE SET ${setParts.join(", ")}`;
1049
- }
1050
- }
1051
- if (returning) {
1052
- sql += ` RETURNING ${returning}`;
1331
+ async runUpsert(prefer, selectCols) {
1332
+ if (this.rows.length === 0) return [];
1333
+ const body = this.rows.length === 1 ? this.rows[0] : this.rows;
1334
+ const sp = new URLSearchParams();
1335
+ sp.set("on_conflict", this.onConflict);
1336
+ if (selectCols && selectCols !== "*") {
1337
+ sp.set("select", selectCols);
1053
1338
  }
1054
- return this.run({
1339
+ const resolution = this.ignoreDuplicates ? "resolution=ignore-duplicates" : "resolution=merge-duplicates";
1340
+ const response = await this.pgFetch({
1341
+ method: "POST",
1342
+ table: this.table,
1343
+ searchParams: sp,
1344
+ body,
1345
+ headers: { Prefer: `${prefer},${resolution}` },
1055
1346
  databaseInstanceId: this.databaseInstanceId,
1056
- sql,
1057
- params,
1058
- readOnly: false
1347
+ signal: this._signal,
1348
+ idempotencyKey: generateIdempotencyKey()
1059
1349
  });
1060
- }
1061
- /** Used by returning builder */
1062
- async runWithReturning(returning) {
1063
- return this.runUpsert(returning);
1350
+ if (prefer.includes("return=minimal")) {
1351
+ await parsePostgRESTResponse(response);
1352
+ return [];
1353
+ }
1354
+ return parsePostgRESTResponse(response);
1064
1355
  }
1065
1356
  };
1066
1357
  var PostgrestUpsertReturningBuilder = class {
@@ -1073,8 +1364,7 @@ var PostgrestUpsertReturningBuilder = class {
1073
1364
  }
1074
1365
  async executeMany() {
1075
1366
  return asPostgrestResponse(async () => {
1076
- const res = await this.root.runWithReturning(this.returning);
1077
- return res.rows;
1367
+ return this.root.runUpsert("return=representation", this.returning);
1078
1368
  });
1079
1369
  }
1080
1370
  async single() {
@@ -1083,9 +1373,11 @@ var PostgrestUpsertReturningBuilder = class {
1083
1373
  if (error) throw error;
1084
1374
  const rows = data ?? [];
1085
1375
  if (rows.length === 0 || rows.length > 1) {
1086
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
1087
- code: "PGRST116"
1088
- });
1376
+ throw new RagableError(
1377
+ "JSON object requested, multiple (or no) rows returned",
1378
+ 406,
1379
+ { code: "PGRST116" }
1380
+ );
1089
1381
  }
1090
1382
  return rows[0];
1091
1383
  });
@@ -1096,23 +1388,25 @@ var PostgrestUpsertReturningBuilder = class {
1096
1388
  if (error) throw error;
1097
1389
  const rows = data ?? [];
1098
1390
  if (rows.length > 1) {
1099
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
1100
- code: "PGRST116"
1101
- });
1391
+ throw new RagableError(
1392
+ "JSON object requested, multiple (or no) rows returned",
1393
+ 406,
1394
+ { code: "PGRST116" }
1395
+ );
1102
1396
  }
1103
1397
  return rows[0] ?? null;
1104
1398
  });
1105
1399
  }
1106
1400
  };
1107
1401
  var PostgrestTableApi = class {
1108
- constructor(run, databaseInstanceId, table) {
1109
- this.run = run;
1402
+ constructor(pgFetch, databaseInstanceId, table) {
1403
+ this.pgFetch = pgFetch;
1110
1404
  this.databaseInstanceId = databaseInstanceId;
1111
1405
  this.table = table;
1112
1406
  }
1113
1407
  select(columns = "*") {
1114
1408
  return new PostgrestSelectBuilder(
1115
- this.run,
1409
+ this.pgFetch,
1116
1410
  this.databaseInstanceId,
1117
1411
  this.table,
1118
1412
  columns
@@ -1121,7 +1415,7 @@ var PostgrestTableApi = class {
1121
1415
  insert(values) {
1122
1416
  const rows = Array.isArray(values) ? values : [values];
1123
1417
  return new PostgrestInsertRootBuilder(
1124
- this.run,
1418
+ this.pgFetch,
1125
1419
  this.databaseInstanceId,
1126
1420
  this.table,
1127
1421
  rows
@@ -1129,7 +1423,7 @@ var PostgrestTableApi = class {
1129
1423
  }
1130
1424
  update(patch) {
1131
1425
  return new PostgrestUpdateRootBuilder(
1132
- this.run,
1426
+ this.pgFetch,
1133
1427
  this.databaseInstanceId,
1134
1428
  this.table,
1135
1429
  patch
@@ -1137,19 +1431,15 @@ var PostgrestTableApi = class {
1137
1431
  }
1138
1432
  delete() {
1139
1433
  return new PostgrestDeleteRootBuilder(
1140
- this.run,
1434
+ this.pgFetch,
1141
1435
  this.databaseInstanceId,
1142
1436
  this.table
1143
1437
  );
1144
1438
  }
1145
- /**
1146
- * `INSERT ... ON CONFLICT ... DO UPDATE` (or `DO NOTHING` with `ignoreDuplicates`).
1147
- * @see https://supabase.com/docs/reference/javascript/upsert
1148
- */
1149
1439
  upsert(values, options) {
1150
1440
  const rows = Array.isArray(values) ? values : [values];
1151
1441
  return new PostgrestUpsertRootBuilder(
1152
- this.run,
1442
+ this.pgFetch,
1153
1443
  this.databaseInstanceId,
1154
1444
  this.table,
1155
1445
  rows,
@@ -1159,42 +1449,139 @@ var PostgrestTableApi = class {
1159
1449
  }
1160
1450
  };
1161
1451
 
1162
- // src/browser.ts
1163
- function normalizeBrowserApiBase(baseUrl) {
1164
- return (baseUrl ?? "http://localhost:8080/api").replace(/\/+$/, "");
1165
- }
1166
- function requireAuthGroupId(options) {
1167
- const id = options.authGroupId?.trim();
1168
- if (!id) {
1169
- throw new Error(
1170
- "authGroupId is required for auth and database methods on the browser client"
1171
- );
1452
+ // src/auth-storage.ts
1453
+ var LocalStorageAdapter = class {
1454
+ getItem(key) {
1455
+ try {
1456
+ return globalThis.localStorage.getItem(key);
1457
+ } catch {
1458
+ return null;
1459
+ }
1172
1460
  }
1173
- return id;
1174
- }
1175
- async function requireAccessToken(options) {
1176
- const getter = options.getAccessToken;
1177
- if (!getter) {
1178
- throw new Error(
1179
- "getAccessToken is required for this call (return the end-user access JWT)"
1180
- );
1461
+ setItem(key, value) {
1462
+ try {
1463
+ globalThis.localStorage.setItem(key, value);
1464
+ } catch {
1465
+ }
1181
1466
  }
1182
- const token = await getter();
1183
- if (!token?.trim()) {
1184
- throw new Error("getAccessToken returned no token");
1467
+ removeItem(key) {
1468
+ try {
1469
+ globalThis.localStorage.removeItem(key);
1470
+ } catch {
1471
+ }
1185
1472
  }
1186
- return token.trim();
1187
- }
1188
- async function parseJsonOrThrow(response) {
1189
- const payload = await parseMaybeJsonBody(response);
1190
- if (!response.ok) {
1191
- const message = extractErrorMessage(payload, response.statusText);
1192
- throw new RagableError(message, response.status, payload);
1473
+ };
1474
+ var SessionStorageAdapter = class {
1475
+ getItem(key) {
1476
+ try {
1477
+ return globalThis.sessionStorage.getItem(key);
1478
+ } catch {
1479
+ return null;
1480
+ }
1193
1481
  }
1194
- return payload;
1482
+ setItem(key, value) {
1483
+ try {
1484
+ globalThis.sessionStorage.setItem(key, value);
1485
+ } catch {
1486
+ }
1487
+ }
1488
+ removeItem(key) {
1489
+ try {
1490
+ globalThis.sessionStorage.removeItem(key);
1491
+ } catch {
1492
+ }
1493
+ }
1494
+ };
1495
+ var MemoryStorageAdapter = class {
1496
+ constructor() {
1497
+ __publicField(this, "store", /* @__PURE__ */ new Map());
1498
+ }
1499
+ getItem(key) {
1500
+ return this.store.get(key) ?? null;
1501
+ }
1502
+ setItem(key, value) {
1503
+ this.store.set(key, value);
1504
+ }
1505
+ removeItem(key) {
1506
+ this.store.delete(key);
1507
+ }
1508
+ };
1509
+ var CookieStorageAdapter = class {
1510
+ constructor(maxAge = 30 * 24 * 60 * 60, path = "/", sameSite = "Lax") {
1511
+ this.maxAge = maxAge;
1512
+ this.path = path;
1513
+ this.sameSite = sameSite;
1514
+ }
1515
+ getItem(key) {
1516
+ if (typeof document === "undefined") return null;
1517
+ const match = document.cookie.split("; ").find((c) => c.startsWith(`${encodeURIComponent(key)}=`));
1518
+ if (!match) return null;
1519
+ return decodeURIComponent(match.split("=").slice(1).join("="));
1520
+ }
1521
+ setItem(key, value) {
1522
+ if (typeof document === "undefined") return;
1523
+ const parts = [
1524
+ `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
1525
+ `path=${this.path}`,
1526
+ `max-age=${this.maxAge}`,
1527
+ `SameSite=${this.sameSite}`
1528
+ ];
1529
+ if (this.sameSite === "None") parts.push("Secure");
1530
+ document.cookie = parts.join("; ");
1531
+ }
1532
+ removeItem(key) {
1533
+ if (typeof document === "undefined") return;
1534
+ document.cookie = `${encodeURIComponent(key)}=; path=${this.path}; max-age=0`;
1535
+ }
1536
+ };
1537
+ function detectStorage() {
1538
+ if (typeof globalThis !== "undefined" && typeof globalThis.localStorage !== "undefined") {
1539
+ try {
1540
+ const testKey = "__ragable_test__";
1541
+ globalThis.localStorage.setItem(testKey, "1");
1542
+ globalThis.localStorage.removeItem(testKey);
1543
+ return new LocalStorageAdapter();
1544
+ } catch {
1545
+ }
1546
+ }
1547
+ return new MemoryStorageAdapter();
1195
1548
  }
1196
- function parseExpiresInSeconds(expiresIn) {
1197
- const s = expiresIn.trim().toLowerCase();
1549
+ var AuthBroadcastChannel = class {
1550
+ constructor(channelName) {
1551
+ __publicField(this, "channel", null);
1552
+ if (typeof BroadcastChannel !== "undefined") {
1553
+ try {
1554
+ this.channel = new BroadcastChannel(channelName);
1555
+ } catch {
1556
+ }
1557
+ }
1558
+ }
1559
+ onMessage(cb) {
1560
+ if (this.channel) {
1561
+ this.channel.onmessage = (e) => {
1562
+ const data = e.data;
1563
+ if (data && typeof data.type === "string") {
1564
+ cb(data);
1565
+ }
1566
+ };
1567
+ }
1568
+ }
1569
+ postSessionUpdated(serialized) {
1570
+ this.channel?.postMessage({ type: "SESSION_UPDATED", payload: serialized });
1571
+ }
1572
+ postSessionRemoved() {
1573
+ this.channel?.postMessage({ type: "SESSION_REMOVED" });
1574
+ }
1575
+ close() {
1576
+ this.channel?.close();
1577
+ this.channel = null;
1578
+ }
1579
+ };
1580
+
1581
+ // src/auth.ts
1582
+ function parseExpiresInSeconds(raw) {
1583
+ if (typeof raw === "number") return raw;
1584
+ const s = raw.trim().toLowerCase();
1198
1585
  const m = /^(\d+)([smhd])?$/.exec(s);
1199
1586
  if (m) {
1200
1587
  const n = Number(m[1]);
@@ -1205,147 +1592,541 @@ function parseExpiresInSeconds(expiresIn) {
1205
1592
  const asNum = Number(s);
1206
1593
  return Number.isFinite(asNum) ? asNum : 0;
1207
1594
  }
1208
- function toSupabaseSession(s) {
1209
- return {
1210
- access_token: s.accessToken,
1211
- refresh_token: s.refreshToken,
1212
- expires_in: parseExpiresInSeconds(s.expiresIn),
1213
- token_type: "bearer",
1214
- user: s.user
1215
- };
1216
- }
1217
- var RagableBrowserAuthClient = class {
1218
- constructor(options) {
1219
- this.options = options;
1220
- __publicField(this, "fetchImpl");
1221
- this.fetchImpl = bindFetch(options.fetch);
1595
+ async function parseJsonOrThrow(response) {
1596
+ const text = await response.text();
1597
+ let payload;
1598
+ try {
1599
+ payload = text ? JSON.parse(text) : null;
1600
+ } catch {
1601
+ throw new RagableError(`Response parse error: ${text.slice(0, 200)}`, response.status, null);
1222
1602
  }
1223
- toUrl(path) {
1224
- return `${normalizeBrowserApiBase(this.options.baseUrl)}${path.startsWith("/") ? path : `/${path}`}`;
1603
+ if (!response.ok) {
1604
+ const message = extractErrorMessage(payload, response.statusText);
1605
+ throw new RagableError(message, response.status, payload);
1225
1606
  }
1226
- baseHeaders(json) {
1227
- const h = new Headers(this.options.headers);
1228
- if (json) {
1229
- h.set("Content-Type", "application/json");
1607
+ return payload;
1608
+ }
1609
+ var _subCounter = 0;
1610
+ var RagableAuth = class {
1611
+ constructor(config) {
1612
+ __publicField(this, "fetchImpl");
1613
+ __publicField(this, "baseUrl");
1614
+ __publicField(this, "authGroupId");
1615
+ __publicField(this, "defaultHeaders");
1616
+ __publicField(this, "persistSession");
1617
+ __publicField(this, "autoRefreshToken");
1618
+ __publicField(this, "storage");
1619
+ __publicField(this, "storageKey");
1620
+ __publicField(this, "refreshSkewSeconds");
1621
+ __publicField(this, "debug");
1622
+ __publicField(this, "currentSession", null);
1623
+ __publicField(this, "refreshTimer", null);
1624
+ __publicField(this, "refreshPromise", null);
1625
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
1626
+ __publicField(this, "broadcast", null);
1627
+ __publicField(this, "visibilityHandler", null);
1628
+ __publicField(this, "initialized", false);
1629
+ this.baseUrl = config.baseUrl;
1630
+ this.authGroupId = config.authGroupId;
1631
+ this.fetchImpl = bindFetch(config.fetch);
1632
+ this.defaultHeaders = config.headers;
1633
+ const auth = config.auth ?? {};
1634
+ this.persistSession = auth.persistSession !== false;
1635
+ this.autoRefreshToken = auth.autoRefreshToken !== false;
1636
+ this.storage = auth.storage ?? detectStorage();
1637
+ this.storageKey = auth.storageKey ?? `ragable.session.${this.authGroupId}`;
1638
+ this.refreshSkewSeconds = auth.refreshSkewSeconds ?? 60;
1639
+ this.debug = auth.debug ?? false;
1640
+ this.broadcast = new AuthBroadcastChannel(`ragable-auth-${this.authGroupId}`);
1641
+ this.broadcast.onMessage((msg) => {
1642
+ if (msg.type === "SESSION_UPDATED") {
1643
+ try {
1644
+ const session = JSON.parse(msg.payload);
1645
+ this.currentSession = session;
1646
+ this.scheduleRefresh(session);
1647
+ this.emit("TOKEN_REFRESHED", session);
1648
+ } catch {
1649
+ }
1650
+ } else if (msg.type === "SESSION_REMOVED") {
1651
+ this.currentSession = null;
1652
+ this.clearRefreshTimer();
1653
+ this.emit("SIGNED_OUT", null);
1654
+ }
1655
+ });
1656
+ this.setupVisibilityListener();
1657
+ }
1658
+ log(...args) {
1659
+ if (this.debug) console.debug("[RagableAuth]", ...args);
1660
+ }
1661
+ // ── Lifecycle ──────────────────────────────────────────────────────────────
1662
+ async initialize() {
1663
+ if (this.initialized) return this.currentSession;
1664
+ this.initialized = true;
1665
+ if (this.persistSession) {
1666
+ try {
1667
+ const raw = await this.storage.getItem(this.storageKey);
1668
+ if (raw) {
1669
+ const session = JSON.parse(raw);
1670
+ if (session.expires_at && session.expires_at > nowSeconds()) {
1671
+ this.currentSession = session;
1672
+ this.scheduleRefresh(session);
1673
+ this.log("Restored session from storage");
1674
+ } else if (session.refresh_token) {
1675
+ this.log("Stored session expired, attempting refresh");
1676
+ const refreshed = await this._doRefresh(session.refresh_token);
1677
+ if (refreshed) {
1678
+ this.currentSession = refreshed;
1679
+ }
1680
+ }
1681
+ }
1682
+ } catch (e) {
1683
+ this.log("Failed to restore session", e);
1684
+ }
1230
1685
  }
1231
- return h;
1232
- }
1233
- authPrefix() {
1234
- const gid = requireAuthGroupId(this.options);
1235
- return `/auth-groups/${gid}/auth`;
1686
+ this.emit("INITIAL_SESSION", this.currentSession);
1687
+ return this.currentSession;
1236
1688
  }
1237
- /** Supabase: `signUp` `{ data: { user, session }, error }` */
1689
+ // ── Auth methods ───────────────────────────────────────────────────────────
1238
1690
  async signUp(credentials) {
1239
1691
  return asPostgrestResponse(async () => {
1240
1692
  const name = typeof credentials.options?.data?.name === "string" ? credentials.options.data.name : void 0;
1241
- const session = await this.register({
1693
+ const raw = await this.fetchAuth("/register", "POST", {
1242
1694
  email: credentials.email,
1243
1695
  password: credentials.password,
1244
- name
1696
+ ...name !== void 0 ? { name } : {}
1245
1697
  });
1246
- return { user: session.user, session: toSupabaseSession(session) };
1698
+ const session = this.rawToSession(raw);
1699
+ await this.setSessionInternal(session, "SIGNED_IN");
1700
+ return { user: session.user, session };
1247
1701
  });
1248
1702
  }
1249
- /** Supabase: `signInWithPassword` */
1250
1703
  async signInWithPassword(credentials) {
1251
1704
  return asPostgrestResponse(async () => {
1252
- const session = await this.login(credentials);
1253
- return { user: session.user, session: toSupabaseSession(session) };
1705
+ const raw = await this.fetchAuth("/login", "POST", {
1706
+ email: credentials.email,
1707
+ password: credentials.password
1708
+ });
1709
+ const session = this.rawToSession(raw);
1710
+ await this.setSessionInternal(session, "SIGNED_IN");
1711
+ return { user: session.user, session };
1254
1712
  });
1255
1713
  }
1256
- /** Supabase: `refreshSession` */
1714
+ async signOut(_options) {
1715
+ this.currentSession = null;
1716
+ this.clearRefreshTimer();
1717
+ if (this.persistSession) {
1718
+ await this.storage.removeItem(this.storageKey);
1719
+ }
1720
+ this.broadcast?.postSessionRemoved();
1721
+ this.emit("SIGNED_OUT", null);
1722
+ return { error: null };
1723
+ }
1257
1724
  async refreshSession(refreshToken) {
1258
1725
  return asPostgrestResponse(async () => {
1259
- const tokens = await this.refresh({ refreshToken });
1260
- const me = await this.getUserFromToken(tokens.accessToken);
1726
+ const token = refreshToken ?? this.currentSession?.refresh_token;
1727
+ if (!token) throw new RagableError("No refresh token available", 401, null);
1728
+ const session = await this.singleFlightRefresh(token);
1729
+ if (!session) throw new RagableError("Refresh failed", 401, null);
1730
+ return { session, user: session.user };
1731
+ });
1732
+ }
1733
+ async getSession() {
1734
+ if (!this.initialized) await this.initialize();
1735
+ return { data: { session: this.currentSession }, error: null };
1736
+ }
1737
+ async getUser() {
1738
+ return asPostgrestResponse(async () => {
1739
+ const token = this.currentSession?.access_token;
1740
+ if (!token) throw new RagableError("Not authenticated", 401, null);
1741
+ return this.fetchAuthWithBearer("/me", "GET", token);
1742
+ });
1743
+ }
1744
+ async setSession(tokens) {
1745
+ return asPostgrestResponse(async () => {
1746
+ const me = await this.fetchAuthWithBearer("/me", "GET", tokens.access_token);
1747
+ const decoded = decodeJwtExpiry(tokens.access_token);
1748
+ const expiresIn = decoded ? decoded - nowSeconds() : 3600;
1261
1749
  const session = {
1262
- access_token: tokens.accessToken,
1263
- refresh_token: tokens.refreshToken,
1264
- expires_in: parseExpiresInSeconds(tokens.expiresIn),
1750
+ access_token: tokens.access_token,
1751
+ refresh_token: tokens.refresh_token,
1752
+ expires_in: expiresIn,
1753
+ expires_at: nowSeconds() + expiresIn,
1265
1754
  token_type: "bearer",
1266
1755
  user: me.user
1267
1756
  };
1757
+ await this.setSessionInternal(session, "SIGNED_IN");
1268
1758
  return { session, user: me.user };
1269
1759
  });
1270
1760
  }
1271
- /** Supabase: `getUser()` — needs `getAccessToken` on the client */
1272
- async getUser() {
1273
- return asPostgrestResponse(() => this.getMe());
1274
- }
1275
- /** Supabase: `updateUser` */
1276
1761
  async updateUser(attributes) {
1277
- return asPostgrestResponse(
1278
- () => this.updateMe({
1279
- password: attributes.password,
1280
- name: attributes.data?.name
1281
- })
1282
- );
1762
+ return asPostgrestResponse(async () => {
1763
+ const token = this.currentSession?.access_token;
1764
+ if (!token) throw new RagableError("Not authenticated", 401, null);
1765
+ const result = await this.fetchAuthWithBearer("/me", "PATCH", token, {
1766
+ ...attributes.password !== void 0 ? { password: attributes.password } : {},
1767
+ ...attributes.data?.name !== void 0 ? { name: attributes.data.name } : {}
1768
+ });
1769
+ if (this.currentSession) {
1770
+ this.currentSession = { ...this.currentSession, user: result.user };
1771
+ await this.persistCurrentSession();
1772
+ }
1773
+ this.emit("USER_UPDATED", this.currentSession);
1774
+ return result;
1775
+ });
1283
1776
  }
1284
- /**
1285
- * Supabase/Firebase: no server call — clear tokens in your app.
1286
- * Returns `{ error: null }` for API compatibility.
1287
- */
1288
- async signOut(_options) {
1289
- return { error: null };
1777
+ // ── Event subscription ─────────────────────────────────────────────────────
1778
+ onAuthStateChange(callback) {
1779
+ _subCounter++;
1780
+ const id = `sub-${_subCounter}`;
1781
+ this.listeners.set(id, callback);
1782
+ const unsubscribe = () => {
1783
+ this.listeners.delete(id);
1784
+ };
1785
+ return { data: { subscription: { id, unsubscribe } } };
1290
1786
  }
1291
- async getUserFromToken(accessToken) {
1292
- const headers = this.baseHeaders(false);
1293
- headers.set("Authorization", `Bearer ${accessToken}`);
1294
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/me`, {
1295
- method: "GET",
1296
- headers
1297
- });
1298
- return parseJsonOrThrow(response);
1787
+ // ── Accessors ──────────────────────────────────────────────────────────────
1788
+ getAccessToken() {
1789
+ return this.currentSession?.access_token ?? null;
1790
+ }
1791
+ getCurrentSession() {
1792
+ return this.currentSession;
1299
1793
  }
1794
+ // ── Back-compat: raw Ragable auth methods ──────────────────────────────────
1300
1795
  async register(body) {
1301
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/register`, {
1302
- method: "POST",
1303
- headers: this.baseHeaders(true),
1304
- body: JSON.stringify(body)
1305
- });
1306
- return parseJsonOrThrow(response);
1796
+ const raw = await this.fetchAuth("/register", "POST", body);
1797
+ const session = this.rawToSession(raw);
1798
+ await this.setSessionInternal(session, "SIGNED_IN");
1799
+ return raw;
1307
1800
  }
1308
1801
  async login(body) {
1309
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/login`, {
1310
- method: "POST",
1311
- headers: this.baseHeaders(true),
1312
- body: JSON.stringify(body)
1313
- });
1314
- return parseJsonOrThrow(response);
1802
+ const raw = await this.fetchAuth("/login", "POST", body);
1803
+ const session = this.rawToSession(raw);
1804
+ await this.setSessionInternal(session, "SIGNED_IN");
1805
+ return raw;
1315
1806
  }
1316
1807
  async refresh(body) {
1317
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/refresh`, {
1318
- method: "POST",
1319
- headers: this.baseHeaders(true),
1320
- body: JSON.stringify(body)
1321
- });
1322
- return parseJsonOrThrow(response);
1808
+ return this.fetchAuth("/refresh", "POST", body);
1323
1809
  }
1324
1810
  async getMe() {
1325
- const token = await requireAccessToken(this.options);
1326
- const headers = this.baseHeaders(false);
1327
- headers.set("Authorization", `Bearer ${token}`);
1328
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/me`, {
1329
- method: "GET",
1330
- headers
1811
+ const token = this.currentSession?.access_token;
1812
+ if (!token) throw new RagableError("Not authenticated", 401, null);
1813
+ return this.fetchAuthWithBearer("/me", "GET", token);
1814
+ }
1815
+ async updateMe(body) {
1816
+ const token = this.currentSession?.access_token;
1817
+ if (!token) throw new RagableError("Not authenticated", 401, null);
1818
+ const result = await this.fetchAuthWithBearer("/me", "PATCH", token, body);
1819
+ if (this.currentSession) {
1820
+ this.currentSession = { ...this.currentSession, user: result.user };
1821
+ await this.persistCurrentSession();
1822
+ }
1823
+ this.emit("USER_UPDATED", this.currentSession);
1824
+ return result;
1825
+ }
1826
+ // ── Cleanup ────────────────────────────────────────────────────────────────
1827
+ destroy() {
1828
+ this.clearRefreshTimer();
1829
+ this.broadcast?.close();
1830
+ this.listeners.clear();
1831
+ if (this.visibilityHandler && typeof document !== "undefined") {
1832
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
1833
+ }
1834
+ }
1835
+ // ─── Internal ──────────────────────────────────────────────────────────────
1836
+ authPrefix() {
1837
+ return `/auth-groups/${this.authGroupId}/auth`;
1838
+ }
1839
+ toUrl(path) {
1840
+ return `${this.baseUrl}${this.authPrefix()}${path}`;
1841
+ }
1842
+ baseHeaders(json) {
1843
+ const h = new Headers(this.defaultHeaders);
1844
+ if (json) h.set("Content-Type", "application/json");
1845
+ return h;
1846
+ }
1847
+ async fetchAuth(path, method, body) {
1848
+ const headers = this.baseHeaders(body !== void 0);
1849
+ const response = await this.fetchImpl(this.toUrl(path), {
1850
+ method,
1851
+ headers,
1852
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1331
1853
  });
1332
1854
  return parseJsonOrThrow(response);
1333
1855
  }
1334
- async updateMe(body) {
1335
- const token = await requireAccessToken(this.options);
1336
- const headers = this.baseHeaders(true);
1856
+ async fetchAuthWithBearer(path, method, token, body) {
1857
+ const headers = this.baseHeaders(body !== void 0);
1337
1858
  headers.set("Authorization", `Bearer ${token}`);
1338
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/me`, {
1339
- method: "PATCH",
1859
+ const response = await this.fetchImpl(this.toUrl(path), {
1860
+ method,
1340
1861
  headers,
1341
- body: JSON.stringify(body)
1862
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1342
1863
  });
1343
1864
  return parseJsonOrThrow(response);
1344
1865
  }
1866
+ rawToSession(raw) {
1867
+ const expiresIn = parseExpiresInSeconds(raw.expiresIn);
1868
+ return {
1869
+ access_token: raw.accessToken,
1870
+ refresh_token: raw.refreshToken,
1871
+ expires_in: expiresIn,
1872
+ expires_at: nowSeconds() + expiresIn,
1873
+ token_type: "bearer",
1874
+ user: raw.user
1875
+ };
1876
+ }
1877
+ async setSessionInternal(session, event) {
1878
+ this.currentSession = session;
1879
+ await this.persistCurrentSession();
1880
+ this.broadcast?.postSessionUpdated(JSON.stringify(session));
1881
+ this.scheduleRefresh(session);
1882
+ this.emit(event, session);
1883
+ }
1884
+ async persistCurrentSession() {
1885
+ if (!this.persistSession || !this.currentSession) return;
1886
+ try {
1887
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.currentSession));
1888
+ } catch (e) {
1889
+ this.log("Failed to persist session", e);
1890
+ }
1891
+ }
1892
+ emit(event, session) {
1893
+ this.log(event, session?.user);
1894
+ for (const cb of this.listeners.values()) {
1895
+ try {
1896
+ cb(event, session);
1897
+ } catch (e) {
1898
+ this.log("Listener threw", e);
1899
+ }
1900
+ }
1901
+ }
1902
+ // ─── Refresh scheduling ────────────────────────────────────────────────────
1903
+ scheduleRefresh(session) {
1904
+ this.clearRefreshTimer();
1905
+ if (!this.autoRefreshToken) return;
1906
+ const secondsUntilExpiry = session.expires_at - nowSeconds();
1907
+ const refreshIn = Math.max(0, secondsUntilExpiry - this.refreshSkewSeconds);
1908
+ this.log(`Scheduling refresh in ${refreshIn}s`);
1909
+ this.refreshTimer = setTimeout(() => {
1910
+ this.singleFlightRefresh(session.refresh_token).catch((e) => {
1911
+ this.log("Scheduled refresh failed", e);
1912
+ });
1913
+ }, refreshIn * 1e3);
1914
+ }
1915
+ clearRefreshTimer() {
1916
+ if (this.refreshTimer !== null) {
1917
+ clearTimeout(this.refreshTimer);
1918
+ this.refreshTimer = null;
1919
+ }
1920
+ }
1921
+ async singleFlightRefresh(refreshToken) {
1922
+ if (this.refreshPromise) return this.refreshPromise;
1923
+ this.refreshPromise = this._doRefresh(refreshToken).finally(() => {
1924
+ this.refreshPromise = null;
1925
+ });
1926
+ return this.refreshPromise;
1927
+ }
1928
+ async _doRefresh(refreshToken) {
1929
+ try {
1930
+ const raw = await this.fetchAuth("/refresh", "POST", { refreshToken });
1931
+ const me = await this.fetchAuthWithBearer("/me", "GET", raw.accessToken);
1932
+ const expiresIn = parseExpiresInSeconds(raw.expiresIn);
1933
+ const session = {
1934
+ access_token: raw.accessToken,
1935
+ refresh_token: raw.refreshToken,
1936
+ expires_in: expiresIn,
1937
+ expires_at: nowSeconds() + expiresIn,
1938
+ token_type: "bearer",
1939
+ user: me.user
1940
+ };
1941
+ await this.setSessionInternal(session, "TOKEN_REFRESHED");
1942
+ return session;
1943
+ } catch (e) {
1944
+ this.log("Refresh failed", e);
1945
+ return null;
1946
+ }
1947
+ }
1948
+ // ─── Visibility listener ───────────────────────────────────────────────────
1949
+ setupVisibilityListener() {
1950
+ if (typeof document === "undefined") return;
1951
+ this.visibilityHandler = () => {
1952
+ if (document.visibilityState === "visible" && this.currentSession) {
1953
+ const secondsUntilExpiry = this.currentSession.expires_at - nowSeconds();
1954
+ if (secondsUntilExpiry <= this.refreshSkewSeconds) {
1955
+ this.singleFlightRefresh(this.currentSession.refresh_token).catch(() => {
1956
+ });
1957
+ } else {
1958
+ this.scheduleRefresh(this.currentSession);
1959
+ }
1960
+ } else {
1961
+ this.clearRefreshTimer();
1962
+ }
1963
+ };
1964
+ document.addEventListener("visibilitychange", this.visibilityHandler);
1965
+ }
1966
+ };
1967
+ function nowSeconds() {
1968
+ return Math.floor(Date.now() / 1e3);
1969
+ }
1970
+ function decodeJwtExpiry(jwt) {
1971
+ try {
1972
+ const parts = jwt.split(".");
1973
+ if (parts.length !== 3) return null;
1974
+ const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/")));
1975
+ return typeof payload.exp === "number" ? payload.exp : null;
1976
+ } catch {
1977
+ return null;
1978
+ }
1979
+ }
1980
+
1981
+ // src/browser.ts
1982
+ function normalizeBrowserApiBase(baseUrl) {
1983
+ const trimmed = (baseUrl ?? DEFAULT_RAGABLE_API_BASE).trim().replace(/\/+$/, "");
1984
+ return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
1985
+ }
1986
+ function requireAuthGroupId(options) {
1987
+ const id = options.authGroupId?.trim();
1988
+ if (!id) {
1989
+ throw new Error(
1990
+ "authGroupId is required for auth and database methods on the browser client"
1991
+ );
1992
+ }
1993
+ return id;
1994
+ }
1995
+ async function requireAccessToken(options, ragableAuth) {
1996
+ if (ragableAuth) {
1997
+ const token = ragableAuth.getAccessToken();
1998
+ if (token) return token;
1999
+ }
2000
+ const getter = options.getAccessToken;
2001
+ if (getter) {
2002
+ const token = await getter();
2003
+ if (token?.trim()) return token.trim();
2004
+ }
2005
+ throw new Error(
2006
+ "No access token available (sign in or provide getAccessToken)"
2007
+ );
2008
+ }
2009
+ async function resolveDatabaseAuthBearer(options, ragableAuth) {
2010
+ const mode = options.dataAuth ?? "user";
2011
+ if (mode === "user") {
2012
+ return requireAccessToken(options, ragableAuth);
2013
+ }
2014
+ const fromGetter = options.getDataStaticKey ? await options.getDataStaticKey() : null;
2015
+ const key = (fromGetter?.trim() || options.dataStaticKey?.trim()) ?? "";
2016
+ if (!key) {
2017
+ throw new Error(
2018
+ mode === "publicAnon" ? "dataAuth publicAnon requires getDataStaticKey or dataStaticKey" : "dataAuth admin requires getDataStaticKey or dataStaticKey"
2019
+ );
2020
+ }
2021
+ return key;
2022
+ }
2023
+ async function parseJsonOrThrow2(response) {
2024
+ const payload = await parseMaybeJsonBody(response);
2025
+ if (!response.ok) {
2026
+ const message = extractErrorMessage(payload, response.statusText);
2027
+ throw new RagableError(message, response.status, payload);
2028
+ }
2029
+ return payload;
2030
+ }
2031
+ var RagableBrowserAuthClient = class {
2032
+ constructor(_options, ragableAuth = null) {
2033
+ this.ragableAuth = ragableAuth;
2034
+ }
2035
+ get auth() {
2036
+ if (!this.ragableAuth) {
2037
+ throw new Error("Auth not initialized \u2014 provide authGroupId to enable auth");
2038
+ }
2039
+ return this.ragableAuth;
2040
+ }
2041
+ async signUp(credentials) {
2042
+ const result = await this.auth.signUp(credentials);
2043
+ if (result.error) return { data: null, error: result.error };
2044
+ const session = result.data.session;
2045
+ return {
2046
+ data: {
2047
+ user: session.user,
2048
+ session: {
2049
+ access_token: session.access_token,
2050
+ refresh_token: session.refresh_token,
2051
+ expires_in: session.expires_in,
2052
+ token_type: "bearer",
2053
+ user: session.user
2054
+ }
2055
+ },
2056
+ error: null
2057
+ };
2058
+ }
2059
+ async signInWithPassword(credentials) {
2060
+ const result = await this.auth.signInWithPassword(credentials);
2061
+ if (result.error) return { data: null, error: result.error };
2062
+ const session = result.data.session;
2063
+ return {
2064
+ data: {
2065
+ user: session.user,
2066
+ session: {
2067
+ access_token: session.access_token,
2068
+ refresh_token: session.refresh_token,
2069
+ expires_in: session.expires_in,
2070
+ token_type: "bearer",
2071
+ user: session.user
2072
+ }
2073
+ },
2074
+ error: null
2075
+ };
2076
+ }
2077
+ async refreshSession(refreshToken) {
2078
+ const result = await this.auth.refreshSession(refreshToken);
2079
+ if (result.error) return { data: null, error: result.error };
2080
+ const session = result.data.session;
2081
+ return {
2082
+ data: {
2083
+ user: session.user,
2084
+ session: {
2085
+ access_token: session.access_token,
2086
+ refresh_token: session.refresh_token,
2087
+ expires_in: session.expires_in,
2088
+ token_type: "bearer",
2089
+ user: session.user
2090
+ }
2091
+ },
2092
+ error: null
2093
+ };
2094
+ }
2095
+ async getUser() {
2096
+ return this.auth.getUser();
2097
+ }
2098
+ async updateUser(attributes) {
2099
+ return this.auth.updateUser(attributes);
2100
+ }
2101
+ async signOut(_options) {
2102
+ return this.auth.signOut(_options);
2103
+ }
2104
+ async register(body) {
2105
+ return this.auth.register(body);
2106
+ }
2107
+ async login(body) {
2108
+ return this.auth.login(body);
2109
+ }
2110
+ async refresh(body) {
2111
+ return this.auth.refresh(body);
2112
+ }
2113
+ async getMe() {
2114
+ return this.auth.getMe();
2115
+ }
2116
+ async updateMe(body) {
2117
+ return this.auth.updateMe(body);
2118
+ }
2119
+ onAuthStateChange(callback) {
2120
+ return this.auth.onAuthStateChange(callback);
2121
+ }
2122
+ getSession() {
2123
+ return this.auth.getSession();
2124
+ }
1345
2125
  };
1346
2126
  var RagableBrowserDatabaseClient = class {
1347
- constructor(options) {
2127
+ constructor(options, ragableAuth = null) {
1348
2128
  this.options = options;
2129
+ this.ragableAuth = ragableAuth;
1349
2130
  __publicField(this, "fetchImpl");
1350
2131
  this.fetchImpl = bindFetch(options.fetch);
1351
2132
  }
@@ -1354,7 +2135,7 @@ var RagableBrowserDatabaseClient = class {
1354
2135
  }
1355
2136
  async query(params) {
1356
2137
  const gid = requireAuthGroupId(this.options);
1357
- const token = await requireAccessToken(this.options);
2138
+ const token = await resolveDatabaseAuthBearer(this.options, this.ragableAuth);
1358
2139
  const databaseInstanceId = params.databaseInstanceId?.trim() || this.options.databaseInstanceId?.trim();
1359
2140
  if (!databaseInstanceId) {
1360
2141
  throw new Error(
@@ -1364,6 +2145,7 @@ var RagableBrowserDatabaseClient = class {
1364
2145
  const headers = this.baseHeaders();
1365
2146
  headers.set("Authorization", `Bearer ${token}`);
1366
2147
  headers.set("Content-Type", "application/json");
2148
+ const readOnly = (this.options.dataAuth ?? "user") === "publicAnon" ? true : params.readOnly !== false;
1367
2149
  const response = await this.fetchImpl(
1368
2150
  this.toUrl(`/auth-groups/${gid}/data/query`),
1369
2151
  {
@@ -1373,13 +2155,13 @@ var RagableBrowserDatabaseClient = class {
1373
2155
  databaseInstanceId,
1374
2156
  sql: params.sql,
1375
2157
  ...params.params !== void 0 ? { params: params.params } : {},
1376
- ...params.readOnly === false ? { readOnly: false } : {},
2158
+ readOnly,
1377
2159
  ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
1378
2160
  ...params.rowLimit !== void 0 ? { rowLimit: params.rowLimit } : {}
1379
2161
  })
1380
2162
  }
1381
2163
  );
1382
- return parseJsonOrThrow(response);
2164
+ return parseJsonOrThrow2(response);
1383
2165
  }
1384
2166
  baseHeaders() {
1385
2167
  return new Headers(this.options.headers);
@@ -1394,9 +2176,6 @@ var RagableBrowserAgentsClient = class {
1394
2176
  toUrl(path) {
1395
2177
  return `${normalizeBrowserApiBase(this.options.baseUrl)}${path.startsWith("/") ? path : `/${path}`}`;
1396
2178
  }
1397
- /**
1398
- * Stream agent execution as SSE (`POST /public/organizations/:orgId/agents/:agentId/chat/stream`).
1399
- */
1400
2179
  async *chatStream(agentId, params) {
1401
2180
  const orgId = this.options.organizationId;
1402
2181
  const body = {
@@ -1432,16 +2211,40 @@ var RagableBrowser = class {
1432
2211
  __publicField(this, "agents");
1433
2212
  __publicField(this, "auth");
1434
2213
  __publicField(this, "database");
2214
+ __publicField(this, "transport");
1435
2215
  __publicField(this, "options");
2216
+ __publicField(this, "_ragableAuth");
1436
2217
  this.options = options;
2218
+ this.transport = new Transport({
2219
+ fetch: options.fetch,
2220
+ headers: options.headers,
2221
+ ...options.transport
2222
+ });
2223
+ if (options.authGroupId) {
2224
+ this._ragableAuth = new RagableAuth({
2225
+ baseUrl: normalizeBrowserApiBase(options.baseUrl),
2226
+ authGroupId: options.authGroupId,
2227
+ fetch: options.fetch,
2228
+ headers: options.headers,
2229
+ auth: options.auth
2230
+ });
2231
+ this.transport.setRefreshHandler(async () => {
2232
+ const session = await this._ragableAuth.singleFlightRefresh(
2233
+ this._ragableAuth.getCurrentSession()?.refresh_token ?? ""
2234
+ );
2235
+ return session?.access_token ?? null;
2236
+ });
2237
+ if (!options.getAccessToken) {
2238
+ this._ragableAuth.initialize().catch(() => {
2239
+ });
2240
+ }
2241
+ } else {
2242
+ this._ragableAuth = null;
2243
+ }
1437
2244
  this.agents = new RagableBrowserAgentsClient(options);
1438
- this.auth = new RagableBrowserAuthClient(options);
1439
- this.database = new RagableBrowserDatabaseClient(options);
2245
+ this.auth = new RagableBrowserAuthClient(options, this._ragableAuth);
2246
+ this.database = new RagableBrowserDatabaseClient(options, this._ragableAuth);
1440
2247
  }
1441
- /**
1442
- * Supabase-style table API: `.from('items').select().eq('id', 1).single()`.
1443
- * Pass `databaseInstanceId` here or set `databaseInstanceId` on the client options.
1444
- */
1445
2248
  from(table, databaseInstanceId) {
1446
2249
  const id = databaseInstanceId?.trim() || this.options.databaseInstanceId?.trim();
1447
2250
  if (!id) {
@@ -1449,8 +2252,40 @@ var RagableBrowser = class {
1449
2252
  "RagableBrowser.from() requires databaseInstanceId in client options or as the second argument"
1450
2253
  );
1451
2254
  }
1452
- const run = (p) => this.database.query(p);
1453
- return new PostgrestTableApi(run, id, table);
2255
+ const gid = requireAuthGroupId(this.options);
2256
+ const ragableAuth = this._ragableAuth;
2257
+ const opts = this.options;
2258
+ const pgFetch = async (params) => {
2259
+ const token = await resolveDatabaseAuthBearer(opts, ragableAuth);
2260
+ const baseUrl = normalizeBrowserApiBase(opts.baseUrl);
2261
+ const qs = params.searchParams.toString();
2262
+ const url = `${baseUrl}/auth-groups/${gid}/data/rest/${params.table}${qs ? `?${qs}` : ""}`;
2263
+ const headers = new Headers(opts.headers);
2264
+ headers.set("Authorization", `Bearer ${token}`);
2265
+ headers.set("X-Database-Instance-Id", params.databaseInstanceId);
2266
+ if (params.body !== void 0) {
2267
+ headers.set("Content-Type", "application/json");
2268
+ }
2269
+ if (params.headers) {
2270
+ for (const [k, v] of Object.entries(params.headers)) {
2271
+ headers.set(k, v);
2272
+ }
2273
+ }
2274
+ if (params.idempotencyKey) {
2275
+ headers.set("Idempotency-Key", params.idempotencyKey);
2276
+ }
2277
+ const fetchImpl = bindFetch(opts.fetch);
2278
+ return fetchImpl(url, {
2279
+ method: params.method,
2280
+ headers,
2281
+ body: params.body !== void 0 ? JSON.stringify(params.body) : void 0,
2282
+ signal: params.signal
2283
+ });
2284
+ };
2285
+ return new PostgrestTableApi(pgFetch, id, table);
2286
+ }
2287
+ destroy() {
2288
+ this._ragableAuth?.destroy();
1454
2289
  }
1455
2290
  };
1456
2291
  function createBrowserClient(options) {
@@ -1549,6 +2384,11 @@ function createRagableServerClient(options) {
1549
2384
  // Annotate the CommonJS export names for ESM import in node:
1550
2385
  0 && (module.exports = {
1551
2386
  AgentsClient,
2387
+ AuthBroadcastChannel,
2388
+ CookieStorageAdapter,
2389
+ DEFAULT_RAGABLE_API_BASE,
2390
+ LocalStorageAdapter,
2391
+ MemoryStorageAdapter,
1552
2392
  PostgrestDeleteReturningBuilder,
1553
2393
  PostgrestDeleteRootBuilder,
1554
2394
  PostgrestInsertReturningBuilder,
@@ -1560,13 +2400,20 @@ function createRagableServerClient(options) {
1560
2400
  PostgrestUpsertReturningBuilder,
1561
2401
  PostgrestUpsertRootBuilder,
1562
2402
  Ragable,
2403
+ RagableAbortError,
2404
+ RagableAuth,
1563
2405
  RagableBrowser,
1564
2406
  RagableBrowserAgentsClient,
1565
2407
  RagableBrowserAuthClient,
1566
2408
  RagableBrowserDatabaseClient,
1567
2409
  RagableError,
2410
+ RagableNetworkError,
1568
2411
  RagableRequestClient,
2412
+ RagableSdkError,
2413
+ RagableTimeoutError,
2414
+ SessionStorageAdapter,
1569
2415
  ShiftClient,
2416
+ Transport,
1570
2417
  asPostgrestResponse,
1571
2418
  bindFetch,
1572
2419
  createBrowserClient,
@@ -1574,10 +2421,13 @@ function createRagableServerClient(options) {
1574
2421
  createRagPipeline,
1575
2422
  createRagableBrowserClient,
1576
2423
  createRagableServerClient,
2424
+ detectStorage,
1577
2425
  extractErrorMessage,
1578
2426
  formatRetrievalContext,
2427
+ generateIdempotencyKey,
1579
2428
  normalizeBrowserApiBase,
1580
2429
  parseSseDataLine,
2430
+ parseTransportResponse,
1581
2431
  readSseStream
1582
2432
  });
1583
2433
  //# sourceMappingURL=index.js.map