@ragable/sdk 0.5.1 → 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,7 +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,
26
28
  DEFAULT_RAGABLE_API_BASE: () => DEFAULT_RAGABLE_API_BASE,
29
+ LocalStorageAdapter: () => LocalStorageAdapter,
30
+ MemoryStorageAdapter: () => MemoryStorageAdapter,
27
31
  PostgrestDeleteReturningBuilder: () => PostgrestDeleteReturningBuilder,
28
32
  PostgrestDeleteRootBuilder: () => PostgrestDeleteRootBuilder,
29
33
  PostgrestInsertReturningBuilder: () => PostgrestInsertReturningBuilder,
@@ -35,13 +39,20 @@ __export(index_exports, {
35
39
  PostgrestUpsertReturningBuilder: () => PostgrestUpsertReturningBuilder,
36
40
  PostgrestUpsertRootBuilder: () => PostgrestUpsertRootBuilder,
37
41
  Ragable: () => Ragable,
42
+ RagableAbortError: () => RagableAbortError,
43
+ RagableAuth: () => RagableAuth,
38
44
  RagableBrowser: () => RagableBrowser,
39
45
  RagableBrowserAgentsClient: () => RagableBrowserAgentsClient,
40
46
  RagableBrowserAuthClient: () => RagableBrowserAuthClient,
41
47
  RagableBrowserDatabaseClient: () => RagableBrowserDatabaseClient,
42
48
  RagableError: () => RagableError,
49
+ RagableNetworkError: () => RagableNetworkError,
43
50
  RagableRequestClient: () => RagableRequestClient,
51
+ RagableSdkError: () => RagableSdkError,
52
+ RagableTimeoutError: () => RagableTimeoutError,
53
+ SessionStorageAdapter: () => SessionStorageAdapter,
44
54
  ShiftClient: () => ShiftClient,
55
+ Transport: () => Transport,
45
56
  asPostgrestResponse: () => asPostgrestResponse,
46
57
  bindFetch: () => bindFetch,
47
58
  createBrowserClient: () => createBrowserClient,
@@ -49,10 +60,13 @@ __export(index_exports, {
49
60
  createRagPipeline: () => createRagPipeline,
50
61
  createRagableBrowserClient: () => createRagableBrowserClient,
51
62
  createRagableServerClient: () => createRagableServerClient,
63
+ detectStorage: () => detectStorage,
52
64
  extractErrorMessage: () => extractErrorMessage,
53
65
  formatRetrievalContext: () => formatRetrievalContext,
66
+ generateIdempotencyKey: () => generateIdempotencyKey,
54
67
  normalizeBrowserApiBase: () => normalizeBrowserApiBase,
55
68
  parseSseDataLine: () => parseSseDataLine,
69
+ parseTransportResponse: () => parseTransportResponse,
56
70
  readSseStream: () => readSseStream
57
71
  });
58
72
  module.exports = __toCommonJS(index_exports);
@@ -65,14 +79,49 @@ function bindFetch(custom) {
65
79
  };
66
80
  }
67
81
  var DEFAULT_RAGABLE_API_BASE = "https://ragable-341305259977.asia-southeast1.run.app/api";
68
- var RagableError = class extends Error {
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 {
69
89
  constructor(message, status, body) {
70
90
  super(message);
91
+ __publicField(this, "__type", "RagableError");
71
92
  __publicField(this, "status");
72
93
  __publicField(this, "body");
73
- this.name = "RagableError";
94
+ __publicField(this, "code");
95
+ __publicField(this, "details");
74
96
  this.status = status;
75
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;
76
125
  }
77
126
  };
78
127
  function extractErrorMessage(payload, fallback) {
@@ -400,42 +449,191 @@ var AgentsClient = class {
400
449
  }
401
450
  };
402
451
 
403
- // src/browser-postgrest.ts
404
- function assertIdent(name, ctx) {
405
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
406
- throw new RagableError(
407
- `Invalid ${ctx} identifier "${name}" (use letters, numbers, underscore)`,
408
- 400,
409
- null
410
- );
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();
411
477
  }
478
+ _uuidCounter++;
479
+ return `idk-${Date.now()}-${_uuidCounter}-${Math.random().toString(36).slice(2, 10)}`;
412
480
  }
413
- function quoteIdent(name) {
414
- assertIdent(name, "column");
415
- return `"${name}"`;
481
+ function requestCacheKey(req) {
482
+ return `${req.method}:${req.url}`;
416
483
  }
417
- function parseConflictColumns(onConflict) {
418
- const parts = onConflict.split(",").map((s) => s.trim()).filter(Boolean);
419
- if (parts.length === 0) {
420
- throw new RagableError(
421
- "upsert requires onConflict with at least one column (e.g. 'id' or 'org_id,user_id')",
422
- 400,
423
- 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
424
574
  );
425
575
  }
426
- for (const p of parts) assertIdent(p, "onConflict");
427
- return parts;
428
- }
429
- var OP_SQL = {
430
- eq: "=",
431
- neq: "<>",
432
- gt: ">",
433
- gte: ">=",
434
- lt: "<",
435
- lte: "<=",
436
- like: "LIKE",
437
- 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
+ }
438
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
439
637
  async function asPostgrestResponse(fn) {
440
638
  try {
441
639
  const data = await fn();
@@ -445,28 +643,75 @@ async function asPostgrestResponse(fn) {
445
643
  return { data: null, error: err };
446
644
  }
447
645
  }
448
- function buildWhere(filters, params) {
449
- if (filters.length === 0) return { clause: "", nextIdx: 1 };
450
- const parts = [];
451
- let i = params.length;
452
- for (const f of filters) {
453
- assertIdent(f.column, "filter");
454
- i += 1;
455
- parts.push(`${quoteIdent(f.column)} ${OP_SQL[f.op]} $${i}`);
456
- params.push(f.value);
457
- }
458
- 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;
459
702
  }
460
703
  var PostgrestSelectBuilder = class {
461
- constructor(run, databaseInstanceId, table, columns) {
462
- this.run = run;
704
+ constructor(pgFetch, databaseInstanceId, table, columns) {
705
+ this.pgFetch = pgFetch;
463
706
  this.databaseInstanceId = databaseInstanceId;
464
707
  this.table = table;
465
708
  this.columns = columns;
466
709
  __publicField(this, "filters", []);
467
710
  __publicField(this, "_limit");
711
+ __publicField(this, "_offset");
468
712
  __publicField(this, "_order");
469
- assertIdent(table, "table");
713
+ __publicField(this, "_signal");
714
+ addFilterMethods(this);
470
715
  }
471
716
  eq(column, value) {
472
717
  this.filters.push({ op: "eq", column, value });
@@ -500,140 +745,173 @@ var PostgrestSelectBuilder = class {
500
745
  this.filters.push({ op: "ilike", column, value });
501
746
  return this;
502
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
+ }
503
766
  limit(n) {
504
767
  this._limit = n;
505
768
  return this;
506
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
+ }
507
779
  order(column, options) {
508
- 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;
509
789
  return this;
510
790
  }
511
- buildSelectCore(params, includeUserLimit) {
512
- const tbl = quoteIdent(this.table);
513
- const { clause } = buildWhere(this.filters, params);
514
- 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
+ }
515
799
  if (this._order) {
516
- 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);
804
+ }
805
+ if (this._limit != null) {
806
+ sp.set("limit", String(Math.max(0, Math.floor(this._limit))));
517
807
  }
518
- if (includeUserLimit && this._limit != null) {
519
- sql += ` LIMIT ${Math.max(0, Math.floor(this._limit))}`;
808
+ if (this._offset != null && this._offset > 0) {
809
+ sp.set("offset", String(Math.max(0, Math.floor(this._offset))));
520
810
  }
521
- return sql;
811
+ return sp;
522
812
  }
523
813
  then(onfulfilled, onrejected) {
524
814
  return this.executeMany().then(onfulfilled, onrejected);
525
815
  }
526
816
  async executeMany() {
527
817
  return asPostgrestResponse(async () => {
528
- const params = [];
529
- const sql = this.buildSelectCore(params, true);
530
- const res = await this.run({
818
+ const response = await this.pgFetch({
819
+ method: "GET",
820
+ table: this.table,
821
+ searchParams: this.buildSearchParams(),
531
822
  databaseInstanceId: this.databaseInstanceId,
532
- sql,
533
- params,
534
- readOnly: true
823
+ signal: this._signal
535
824
  });
536
- return res.rows;
825
+ return parsePostgRESTResponse(response);
537
826
  });
538
827
  }
539
828
  async single() {
540
829
  return asPostgrestResponse(async () => {
541
- const params = [];
542
- const base = this.buildSelectCore(params, false);
543
- const sql = `${base} LIMIT 2`;
544
- 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" },
545
836
  databaseInstanceId: this.databaseInstanceId,
546
- sql,
547
- params,
548
- readOnly: true
837
+ signal: this._signal
549
838
  });
550
- if (res.rows.length === 0 || res.rows.length > 1) {
551
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
552
- code: "PGRST116"
553
- });
554
- }
555
- return res.rows[0];
839
+ return parsePostgRESTResponse(response);
556
840
  });
557
841
  }
558
842
  async maybeSingle() {
559
843
  return asPostgrestResponse(async () => {
560
- const params = [];
561
- const base = this.buildSelectCore(params, false);
562
- const sql = `${base} LIMIT 2`;
563
- const res = await this.run({
844
+ const response = await this.pgFetch({
845
+ method: "GET",
846
+ table: this.table,
847
+ searchParams: this.buildSearchParams(),
564
848
  databaseInstanceId: this.databaseInstanceId,
565
- sql,
566
- params,
567
- readOnly: true
849
+ signal: this._signal
568
850
  });
569
- if (res.rows.length > 1) {
570
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
571
- code: "PGRST116"
572
- });
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
+ );
573
858
  }
574
- return res.rows[0] ?? null;
859
+ return rows[0] ?? null;
575
860
  });
576
861
  }
577
862
  };
578
863
  var PostgrestInsertRootBuilder = class {
579
- constructor(run, databaseInstanceId, table, rows) {
580
- this.run = run;
864
+ constructor(pgFetch, databaseInstanceId, table, rows) {
865
+ this.pgFetch = pgFetch;
581
866
  this.databaseInstanceId = databaseInstanceId;
582
867
  this.table = table;
583
868
  this.rows = rows;
584
- assertIdent(table, "table");
869
+ __publicField(this, "_signal");
585
870
  }
586
- /**
587
- * Return inserted rows (Supabase: chain `.select()` to get data).
588
- * @see https://supabase.com/docs/reference/javascript/insert
589
- */
590
871
  select(columns = "*") {
591
872
  return new PostgrestInsertReturningBuilder(
592
- this.run,
873
+ this.pgFetch,
593
874
  this.databaseInstanceId,
594
875
  this.table,
595
876
  this.rows,
596
- columns
877
+ columns,
878
+ this._signal
597
879
  );
598
880
  }
881
+ abortSignal(signal) {
882
+ this._signal = signal;
883
+ return this;
884
+ }
599
885
  then(onfulfilled, onrejected) {
600
886
  return this.executeNoReturn().then(onfulfilled, onrejected);
601
887
  }
602
888
  async executeNoReturn() {
603
889
  return asPostgrestResponse(async () => {
604
890
  if (this.rows.length === 0) return null;
605
- const keys = Object.keys(this.rows[0]);
606
- for (const k of keys) assertIdent(k, "column");
607
- const tbl = quoteIdent(this.table);
608
- const params = [];
609
- const valueGroups = [];
610
- for (const row of this.rows) {
611
- const placeholders = [];
612
- for (const k of keys) {
613
- params.push(row[k]);
614
- placeholders.push(`$${params.length}`);
615
- }
616
- valueGroups.push(`(${placeholders.join(", ")})`);
617
- }
618
- const cols = keys.map(quoteIdent).join(", ");
619
- const sql = `INSERT INTO ${tbl} (${cols}) VALUES ${valueGroups.join(", ")}`;
620
- 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" },
621
898
  databaseInstanceId: this.databaseInstanceId,
622
- sql,
623
- params,
624
- readOnly: false
899
+ signal: this._signal,
900
+ idempotencyKey: generateIdempotencyKey()
625
901
  });
902
+ await parsePostgRESTResponse(response);
626
903
  return null;
627
904
  });
628
905
  }
629
906
  };
630
907
  var PostgrestInsertReturningBuilder = class {
631
- constructor(run, databaseInstanceId, table, rows, returning) {
632
- this.run = run;
908
+ constructor(pgFetch, databaseInstanceId, table, rows, returning, _signal) {
909
+ this.pgFetch = pgFetch;
633
910
  this.databaseInstanceId = databaseInstanceId;
634
911
  this.table = table;
635
912
  this.rows = rows;
636
913
  this.returning = returning;
914
+ this._signal = _signal;
637
915
  }
638
916
  then(onfulfilled, onrejected) {
639
917
  return this.executeMany().then(onfulfilled, onrejected);
@@ -641,28 +919,22 @@ var PostgrestInsertReturningBuilder = class {
641
919
  async executeMany() {
642
920
  return asPostgrestResponse(async () => {
643
921
  if (this.rows.length === 0) return [];
644
- const keys = Object.keys(this.rows[0]);
645
- for (const k of keys) assertIdent(k, "column");
646
- const tbl = quoteIdent(this.table);
647
- const params = [];
648
- const valueGroups = [];
649
- for (const row of this.rows) {
650
- const placeholders = [];
651
- for (const k of keys) {
652
- params.push(row[k]);
653
- placeholders.push(`$${params.length}`);
654
- }
655
- 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);
656
926
  }
657
- const cols = keys.map(quoteIdent).join(", ");
658
- const sql = `INSERT INTO ${tbl} (${cols}) VALUES ${valueGroups.join(", ")} RETURNING ${this.returning}`;
659
- 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" },
660
933
  databaseInstanceId: this.databaseInstanceId,
661
- sql,
662
- params,
663
- readOnly: false
934
+ signal: this._signal,
935
+ idempotencyKey: generateIdempotencyKey()
664
936
  });
665
- return res.rows;
937
+ return parsePostgRESTResponse(response);
666
938
  });
667
939
  }
668
940
  async single() {
@@ -671,9 +943,11 @@ var PostgrestInsertReturningBuilder = class {
671
943
  if (error) throw error;
672
944
  const rows = data ?? [];
673
945
  if (rows.length === 0 || rows.length > 1) {
674
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
675
- code: "PGRST116"
676
- });
946
+ throw new RagableError(
947
+ "JSON object requested, multiple (or no) rows returned",
948
+ 406,
949
+ { code: "PGRST116" }
950
+ );
677
951
  }
678
952
  return rows[0];
679
953
  });
@@ -684,22 +958,24 @@ var PostgrestInsertReturningBuilder = class {
684
958
  if (error) throw error;
685
959
  const rows = data ?? [];
686
960
  if (rows.length > 1) {
687
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
688
- code: "PGRST116"
689
- });
961
+ throw new RagableError(
962
+ "JSON object requested, multiple (or no) rows returned",
963
+ 406,
964
+ { code: "PGRST116" }
965
+ );
690
966
  }
691
967
  return rows[0] ?? null;
692
968
  });
693
969
  }
694
970
  };
695
971
  var PostgrestUpdateRootBuilder = class {
696
- constructor(run, databaseInstanceId, table, patch) {
697
- this.run = run;
972
+ constructor(pgFetch, databaseInstanceId, table, patch) {
973
+ this.pgFetch = pgFetch;
698
974
  this.databaseInstanceId = databaseInstanceId;
699
975
  this.table = table;
700
976
  this.patch = patch;
701
977
  __publicField(this, "filters", []);
702
- assertIdent(table, "table");
978
+ __publicField(this, "_signal");
703
979
  }
704
980
  eq(column, value) {
705
981
  this.filters.push({ op: "eq", column, value });
@@ -733,64 +1009,79 @@ var PostgrestUpdateRootBuilder = class {
733
1009
  this.filters.push({ op: "ilike", column, value });
734
1010
  return this;
735
1011
  }
736
- /**
737
- * Return updated rows (Supabase: `.update().eq().select()`).
738
- * @see https://supabase.com/docs/reference/javascript/update
739
- */
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
+ }
740
1030
  select(columns = "*") {
741
1031
  return new PostgrestUpdateReturningBuilder(
742
- this.run,
1032
+ this.pgFetch,
743
1033
  this.databaseInstanceId,
744
1034
  this.table,
745
1035
  this.patch,
746
1036
  this.filters,
747
- columns
1037
+ columns,
1038
+ this._signal
748
1039
  );
749
1040
  }
1041
+ abortSignal(signal) {
1042
+ this._signal = signal;
1043
+ return this;
1044
+ }
750
1045
  then(onfulfilled, onrejected) {
751
1046
  return this.executeNoReturn().then(onfulfilled, onrejected);
752
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
+ }
753
1055
  async executeNoReturn() {
754
1056
  return asPostgrestResponse(async () => {
755
1057
  const keys = Object.keys(this.patch);
756
1058
  if (keys.length === 0) {
757
1059
  throw new RagableError("Empty update payload", 400, null);
758
1060
  }
759
- for (const k of keys) assertIdent(k, "column");
760
- if (this.filters.length === 0) {
761
- throw new RagableError(
762
- "UPDATE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped update",
763
- 400,
764
- null
765
- );
766
- }
767
- const params = [];
768
- const sets = [];
769
- for (const k of keys) {
770
- params.push(this.patch[k]);
771
- sets.push(`${quoteIdent(k)} = $${params.length}`);
772
- }
773
- const { clause } = buildWhere(this.filters, params);
774
- const tbl = quoteIdent(this.table);
775
- const sql = `UPDATE ${tbl} SET ${sets.join(", ")}${clause}`;
776
- 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" },
777
1067
  databaseInstanceId: this.databaseInstanceId,
778
- sql,
779
- params,
780
- readOnly: false
1068
+ signal: this._signal,
1069
+ idempotencyKey: generateIdempotencyKey()
781
1070
  });
1071
+ await parsePostgRESTResponse(response);
782
1072
  return null;
783
1073
  });
784
1074
  }
785
1075
  };
786
1076
  var PostgrestUpdateReturningBuilder = class {
787
- constructor(run, databaseInstanceId, table, patch, filters, returning) {
788
- this.run = run;
1077
+ constructor(pgFetch, databaseInstanceId, table, patch, filters, returning, _signal) {
1078
+ this.pgFetch = pgFetch;
789
1079
  this.databaseInstanceId = databaseInstanceId;
790
1080
  this.table = table;
791
1081
  this.patch = patch;
792
1082
  this.filters = filters;
793
1083
  this.returning = returning;
1084
+ this._signal = _signal;
794
1085
  }
795
1086
  then(onfulfilled, onrejected) {
796
1087
  return this.executeMany().then(onfulfilled, onrejected);
@@ -801,30 +1092,24 @@ var PostgrestUpdateReturningBuilder = class {
801
1092
  if (keys.length === 0) {
802
1093
  throw new RagableError("Empty update payload", 400, null);
803
1094
  }
804
- for (const k of keys) assertIdent(k, "column");
805
- if (this.filters.length === 0) {
806
- throw new RagableError(
807
- "UPDATE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped update",
808
- 400,
809
- null
810
- );
1095
+ const sp = new URLSearchParams();
1096
+ for (const f of this.filters) {
1097
+ sp.append(f.column, encodeFilterValue(f.op, f.value));
811
1098
  }
812
- const params = [];
813
- const sets = [];
814
- for (const k of keys) {
815
- params.push(this.patch[k]);
816
- sets.push(`${quoteIdent(k)} = $${params.length}`);
1099
+ if (this.returning && this.returning !== "*") {
1100
+ sp.set("select", this.returning);
817
1101
  }
818
- const { clause } = buildWhere(this.filters, params);
819
- const tbl = quoteIdent(this.table);
820
- const sql = `UPDATE ${tbl} SET ${sets.join(", ")}${clause} RETURNING ${this.returning}`;
821
- 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" },
822
1108
  databaseInstanceId: this.databaseInstanceId,
823
- sql,
824
- params,
825
- readOnly: false
1109
+ signal: this._signal,
1110
+ idempotencyKey: generateIdempotencyKey()
826
1111
  });
827
- return res.rows;
1112
+ return parsePostgRESTResponse(response);
828
1113
  });
829
1114
  }
830
1115
  async single() {
@@ -833,9 +1118,11 @@ var PostgrestUpdateReturningBuilder = class {
833
1118
  if (error) throw error;
834
1119
  const rows = data ?? [];
835
1120
  if (rows.length === 0 || rows.length > 1) {
836
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
837
- code: "PGRST116"
838
- });
1121
+ throw new RagableError(
1122
+ "JSON object requested, multiple (or no) rows returned",
1123
+ 406,
1124
+ { code: "PGRST116" }
1125
+ );
839
1126
  }
840
1127
  return rows[0];
841
1128
  });
@@ -846,21 +1133,23 @@ var PostgrestUpdateReturningBuilder = class {
846
1133
  if (error) throw error;
847
1134
  const rows = data ?? [];
848
1135
  if (rows.length > 1) {
849
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
850
- code: "PGRST116"
851
- });
1136
+ throw new RagableError(
1137
+ "JSON object requested, multiple (or no) rows returned",
1138
+ 406,
1139
+ { code: "PGRST116" }
1140
+ );
852
1141
  }
853
1142
  return rows[0] ?? null;
854
1143
  });
855
1144
  }
856
1145
  };
857
1146
  var PostgrestDeleteRootBuilder = class {
858
- constructor(run, databaseInstanceId, table) {
859
- this.run = run;
1147
+ constructor(pgFetch, databaseInstanceId, table) {
1148
+ this.pgFetch = pgFetch;
860
1149
  this.databaseInstanceId = databaseInstanceId;
861
1150
  this.table = table;
862
1151
  __publicField(this, "filters", []);
863
- assertIdent(table, "table");
1152
+ __publicField(this, "_signal");
864
1153
  }
865
1154
  eq(column, value) {
866
1155
  this.filters.push({ op: "eq", column, value });
@@ -894,76 +1183,92 @@ var PostgrestDeleteRootBuilder = class {
894
1183
  this.filters.push({ op: "ilike", column, value });
895
1184
  return this;
896
1185
  }
897
- /**
898
- * Return deleted rows (Supabase: `.delete().eq().select()`).
899
- * @see https://supabase.com/docs/reference/javascript/delete
900
- */
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
+ }
901
1204
  select(columns = "*") {
902
1205
  return new PostgrestDeleteReturningBuilder(
903
- this.run,
1206
+ this.pgFetch,
904
1207
  this.databaseInstanceId,
905
1208
  this.table,
906
1209
  this.filters,
907
- columns
1210
+ columns,
1211
+ this._signal
908
1212
  );
909
1213
  }
1214
+ abortSignal(signal) {
1215
+ this._signal = signal;
1216
+ return this;
1217
+ }
910
1218
  then(onfulfilled, onrejected) {
911
1219
  return this.executeNoReturn().then(onfulfilled, onrejected);
912
1220
  }
913
1221
  async executeNoReturn() {
914
1222
  return asPostgrestResponse(async () => {
915
- if (this.filters.length === 0) {
916
- throw new RagableError(
917
- "DELETE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped delete",
918
- 400,
919
- null
920
- );
1223
+ const sp = new URLSearchParams();
1224
+ for (const f of this.filters) {
1225
+ sp.append(f.column, encodeFilterValue(f.op, f.value));
921
1226
  }
922
- const params = [];
923
- const { clause } = buildWhere(this.filters, params);
924
- const tbl = quoteIdent(this.table);
925
- const sql = `DELETE FROM ${tbl}${clause}`;
926
- await this.run({
1227
+ const response = await this.pgFetch({
1228
+ method: "DELETE",
1229
+ table: this.table,
1230
+ searchParams: sp,
1231
+ headers: { Prefer: "return=minimal" },
927
1232
  databaseInstanceId: this.databaseInstanceId,
928
- sql,
929
- params,
930
- readOnly: false
1233
+ signal: this._signal,
1234
+ idempotencyKey: generateIdempotencyKey()
931
1235
  });
1236
+ await parsePostgRESTResponse(response);
932
1237
  return null;
933
1238
  });
934
1239
  }
935
1240
  };
936
1241
  var PostgrestDeleteReturningBuilder = class {
937
- constructor(run, databaseInstanceId, table, filters, returning) {
938
- this.run = run;
1242
+ constructor(pgFetch, databaseInstanceId, table, filters, returning, _signal) {
1243
+ this.pgFetch = pgFetch;
939
1244
  this.databaseInstanceId = databaseInstanceId;
940
1245
  this.table = table;
941
1246
  this.filters = filters;
942
1247
  this.returning = returning;
1248
+ this._signal = _signal;
943
1249
  }
944
1250
  then(onfulfilled, onrejected) {
945
1251
  return this.executeMany().then(onfulfilled, onrejected);
946
1252
  }
947
1253
  async executeMany() {
948
1254
  return asPostgrestResponse(async () => {
949
- if (this.filters.length === 0) {
950
- throw new RagableError(
951
- "DELETE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped delete",
952
- 400,
953
- null
954
- );
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);
955
1261
  }
956
- const params = [];
957
- const { clause } = buildWhere(this.filters, params);
958
- const tbl = quoteIdent(this.table);
959
- const sql = `DELETE FROM ${tbl}${clause} RETURNING ${this.returning}`;
960
- 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" },
961
1267
  databaseInstanceId: this.databaseInstanceId,
962
- sql,
963
- params,
964
- readOnly: false
1268
+ signal: this._signal,
1269
+ idempotencyKey: generateIdempotencyKey()
965
1270
  });
966
- return res.rows;
1271
+ return parsePostgRESTResponse(response);
967
1272
  });
968
1273
  }
969
1274
  async single() {
@@ -972,9 +1277,11 @@ var PostgrestDeleteReturningBuilder = class {
972
1277
  if (error) throw error;
973
1278
  const rows = data ?? [];
974
1279
  if (rows.length === 0 || rows.length > 1) {
975
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
976
- code: "PGRST116"
977
- });
1280
+ throw new RagableError(
1281
+ "JSON object requested, multiple (or no) rows returned",
1282
+ 406,
1283
+ { code: "PGRST116" }
1284
+ );
978
1285
  }
979
1286
  return rows[0];
980
1287
  });
@@ -985,84 +1292,66 @@ var PostgrestDeleteReturningBuilder = class {
985
1292
  if (error) throw error;
986
1293
  const rows = data ?? [];
987
1294
  if (rows.length > 1) {
988
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
989
- code: "PGRST116"
990
- });
1295
+ throw new RagableError(
1296
+ "JSON object requested, multiple (or no) rows returned",
1297
+ 406,
1298
+ { code: "PGRST116" }
1299
+ );
991
1300
  }
992
1301
  return rows[0] ?? null;
993
1302
  });
994
1303
  }
995
1304
  };
996
1305
  var PostgrestUpsertRootBuilder = class {
997
- constructor(run, databaseInstanceId, table, rows, onConflict, ignoreDuplicates) {
998
- this.run = run;
1306
+ constructor(pgFetch, databaseInstanceId, table, rows, onConflict, ignoreDuplicates) {
1307
+ this.pgFetch = pgFetch;
999
1308
  this.databaseInstanceId = databaseInstanceId;
1000
1309
  this.table = table;
1001
1310
  this.rows = rows;
1311
+ this.onConflict = onConflict;
1002
1312
  this.ignoreDuplicates = ignoreDuplicates;
1003
- __publicField(this, "conflictCols");
1004
- assertIdent(table, "table");
1005
- this.conflictCols = parseConflictColumns(onConflict);
1313
+ __publicField(this, "_signal");
1006
1314
  }
1007
- /**
1008
- * Return upserted rows (Supabase: `.upsert().select()`).
1009
- * @see https://supabase.com/docs/reference/javascript/upsert
1010
- */
1011
1315
  select(columns = "*") {
1012
1316
  return new PostgrestUpsertReturningBuilder(this, columns);
1013
1317
  }
1318
+ abortSignal(signal) {
1319
+ this._signal = signal;
1320
+ return this;
1321
+ }
1014
1322
  then(onfulfilled, onrejected) {
1015
1323
  return this.executeNoReturn().then(onfulfilled, onrejected);
1016
1324
  }
1017
1325
  async executeNoReturn() {
1018
1326
  return asPostgrestResponse(async () => {
1019
- await this.runUpsert(null);
1327
+ await this.runUpsert("return=minimal", null);
1020
1328
  return null;
1021
1329
  });
1022
1330
  }
1023
- async runUpsert(returning) {
1024
- if (this.rows.length === 0) {
1025
- return { command: "INSERT", rowCount: 0, truncated: false, rows: [] };
1026
- }
1027
- const keys = Object.keys(this.rows[0]);
1028
- for (const k of keys) assertIdent(k, "column");
1029
- const tbl = quoteIdent(this.table);
1030
- const conflictQuoted = this.conflictCols.map(quoteIdent).join(", ");
1031
- const params = [];
1032
- const valueGroups = [];
1033
- for (const row of this.rows) {
1034
- const placeholders = [];
1035
- for (const k of keys) {
1036
- params.push(row[k]);
1037
- placeholders.push(`$${params.length}`);
1038
- }
1039
- valueGroups.push(`(${placeholders.join(", ")})`);
1040
- }
1041
- const cols = keys.map(quoteIdent).join(", ");
1042
- let sql = `INSERT INTO ${tbl} (${cols}) VALUES ${valueGroups.join(", ")} ON CONFLICT (${conflictQuoted})`;
1043
- if (this.ignoreDuplicates) {
1044
- sql += " DO NOTHING";
1045
- } else {
1046
- const setParts = keys.filter((k) => !this.conflictCols.includes(k)).map((k) => `${quoteIdent(k)} = EXCLUDED.${quoteIdent(k)}`);
1047
- if (setParts.length === 0) {
1048
- sql += " DO NOTHING";
1049
- } else {
1050
- sql += ` DO UPDATE SET ${setParts.join(", ")}`;
1051
- }
1052
- }
1053
- if (returning) {
1054
- 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);
1055
1338
  }
1056
- 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}` },
1057
1346
  databaseInstanceId: this.databaseInstanceId,
1058
- sql,
1059
- params,
1060
- readOnly: false
1347
+ signal: this._signal,
1348
+ idempotencyKey: generateIdempotencyKey()
1061
1349
  });
1062
- }
1063
- /** Used by returning builder */
1064
- async runWithReturning(returning) {
1065
- return this.runUpsert(returning);
1350
+ if (prefer.includes("return=minimal")) {
1351
+ await parsePostgRESTResponse(response);
1352
+ return [];
1353
+ }
1354
+ return parsePostgRESTResponse(response);
1066
1355
  }
1067
1356
  };
1068
1357
  var PostgrestUpsertReturningBuilder = class {
@@ -1075,8 +1364,7 @@ var PostgrestUpsertReturningBuilder = class {
1075
1364
  }
1076
1365
  async executeMany() {
1077
1366
  return asPostgrestResponse(async () => {
1078
- const res = await this.root.runWithReturning(this.returning);
1079
- return res.rows;
1367
+ return this.root.runUpsert("return=representation", this.returning);
1080
1368
  });
1081
1369
  }
1082
1370
  async single() {
@@ -1085,9 +1373,11 @@ var PostgrestUpsertReturningBuilder = class {
1085
1373
  if (error) throw error;
1086
1374
  const rows = data ?? [];
1087
1375
  if (rows.length === 0 || rows.length > 1) {
1088
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
1089
- code: "PGRST116"
1090
- });
1376
+ throw new RagableError(
1377
+ "JSON object requested, multiple (or no) rows returned",
1378
+ 406,
1379
+ { code: "PGRST116" }
1380
+ );
1091
1381
  }
1092
1382
  return rows[0];
1093
1383
  });
@@ -1098,23 +1388,25 @@ var PostgrestUpsertReturningBuilder = class {
1098
1388
  if (error) throw error;
1099
1389
  const rows = data ?? [];
1100
1390
  if (rows.length > 1) {
1101
- throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
1102
- code: "PGRST116"
1103
- });
1391
+ throw new RagableError(
1392
+ "JSON object requested, multiple (or no) rows returned",
1393
+ 406,
1394
+ { code: "PGRST116" }
1395
+ );
1104
1396
  }
1105
1397
  return rows[0] ?? null;
1106
1398
  });
1107
1399
  }
1108
1400
  };
1109
1401
  var PostgrestTableApi = class {
1110
- constructor(run, databaseInstanceId, table) {
1111
- this.run = run;
1402
+ constructor(pgFetch, databaseInstanceId, table) {
1403
+ this.pgFetch = pgFetch;
1112
1404
  this.databaseInstanceId = databaseInstanceId;
1113
1405
  this.table = table;
1114
1406
  }
1115
1407
  select(columns = "*") {
1116
1408
  return new PostgrestSelectBuilder(
1117
- this.run,
1409
+ this.pgFetch,
1118
1410
  this.databaseInstanceId,
1119
1411
  this.table,
1120
1412
  columns
@@ -1123,7 +1415,7 @@ var PostgrestTableApi = class {
1123
1415
  insert(values) {
1124
1416
  const rows = Array.isArray(values) ? values : [values];
1125
1417
  return new PostgrestInsertRootBuilder(
1126
- this.run,
1418
+ this.pgFetch,
1127
1419
  this.databaseInstanceId,
1128
1420
  this.table,
1129
1421
  rows
@@ -1131,7 +1423,7 @@ var PostgrestTableApi = class {
1131
1423
  }
1132
1424
  update(patch) {
1133
1425
  return new PostgrestUpdateRootBuilder(
1134
- this.run,
1426
+ this.pgFetch,
1135
1427
  this.databaseInstanceId,
1136
1428
  this.table,
1137
1429
  patch
@@ -1139,19 +1431,15 @@ var PostgrestTableApi = class {
1139
1431
  }
1140
1432
  delete() {
1141
1433
  return new PostgrestDeleteRootBuilder(
1142
- this.run,
1434
+ this.pgFetch,
1143
1435
  this.databaseInstanceId,
1144
1436
  this.table
1145
1437
  );
1146
1438
  }
1147
- /**
1148
- * `INSERT ... ON CONFLICT ... DO UPDATE` (or `DO NOTHING` with `ignoreDuplicates`).
1149
- * @see https://supabase.com/docs/reference/javascript/upsert
1150
- */
1151
1439
  upsert(values, options) {
1152
1440
  const rows = Array.isArray(values) ? values : [values];
1153
1441
  return new PostgrestUpsertRootBuilder(
1154
- this.run,
1442
+ this.pgFetch,
1155
1443
  this.databaseInstanceId,
1156
1444
  this.table,
1157
1445
  rows,
@@ -1161,57 +1449,139 @@ var PostgrestTableApi = class {
1161
1449
  }
1162
1450
  };
1163
1451
 
1164
- // src/browser.ts
1165
- function normalizeBrowserApiBase(baseUrl) {
1166
- const trimmed = (baseUrl ?? DEFAULT_RAGABLE_API_BASE).trim().replace(/\/+$/, "");
1167
- return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
1168
- }
1169
- function requireAuthGroupId(options) {
1170
- const id = options.authGroupId?.trim();
1171
- if (!id) {
1172
- throw new Error(
1173
- "authGroupId is required for auth and database methods on the browser client"
1174
- );
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
+ }
1175
1460
  }
1176
- return id;
1177
- }
1178
- async function requireAccessToken(options) {
1179
- const getter = options.getAccessToken;
1180
- if (!getter) {
1181
- throw new Error(
1182
- "getAccessToken is required for this call (return the end-user access JWT)"
1183
- );
1461
+ setItem(key, value) {
1462
+ try {
1463
+ globalThis.localStorage.setItem(key, value);
1464
+ } catch {
1465
+ }
1184
1466
  }
1185
- const token = await getter();
1186
- if (!token?.trim()) {
1187
- throw new Error("getAccessToken returned no token");
1467
+ removeItem(key) {
1468
+ try {
1469
+ globalThis.localStorage.removeItem(key);
1470
+ } catch {
1471
+ }
1188
1472
  }
1189
- return token.trim();
1190
- }
1191
- async function resolveDatabaseAuthBearer(options) {
1192
- const mode = options.dataAuth ?? "user";
1193
- if (mode === "user") {
1194
- return requireAccessToken(options);
1473
+ };
1474
+ var SessionStorageAdapter = class {
1475
+ getItem(key) {
1476
+ try {
1477
+ return globalThis.sessionStorage.getItem(key);
1478
+ } catch {
1479
+ return null;
1480
+ }
1195
1481
  }
1196
- const fromGetter = options.getDataStaticKey ? await options.getDataStaticKey() : null;
1197
- const key = (fromGetter?.trim() || options.dataStaticKey?.trim()) ?? "";
1198
- if (!key) {
1199
- throw new Error(
1200
- mode === "publicAnon" ? "dataAuth publicAnon requires getDataStaticKey or dataStaticKey (rotate key in dashboard)" : "dataAuth admin requires getDataStaticKey or dataStaticKey (server-side only; rotate in dashboard)"
1201
- );
1482
+ setItem(key, value) {
1483
+ try {
1484
+ globalThis.sessionStorage.setItem(key, value);
1485
+ } catch {
1486
+ }
1202
1487
  }
1203
- return key;
1204
- }
1205
- async function parseJsonOrThrow(response) {
1206
- const payload = await parseMaybeJsonBody(response);
1207
- if (!response.ok) {
1208
- const message = extractErrorMessage(payload, response.statusText);
1209
- throw new RagableError(message, response.status, payload);
1488
+ removeItem(key) {
1489
+ try {
1490
+ globalThis.sessionStorage.removeItem(key);
1491
+ } catch {
1492
+ }
1210
1493
  }
1211
- return payload;
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();
1212
1548
  }
1213
- function parseExpiresInSeconds(expiresIn) {
1214
- 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();
1215
1585
  const m = /^(\d+)([smhd])?$/.exec(s);
1216
1586
  if (m) {
1217
1587
  const n = Number(m[1]);
@@ -1222,147 +1592,541 @@ function parseExpiresInSeconds(expiresIn) {
1222
1592
  const asNum = Number(s);
1223
1593
  return Number.isFinite(asNum) ? asNum : 0;
1224
1594
  }
1225
- function toSupabaseSession(s) {
1226
- return {
1227
- access_token: s.accessToken,
1228
- refresh_token: s.refreshToken,
1229
- expires_in: parseExpiresInSeconds(s.expiresIn),
1230
- token_type: "bearer",
1231
- user: s.user
1232
- };
1233
- }
1234
- var RagableBrowserAuthClient = class {
1235
- constructor(options) {
1236
- this.options = options;
1237
- __publicField(this, "fetchImpl");
1238
- 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);
1239
1602
  }
1240
- toUrl(path) {
1241
- 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);
1242
1606
  }
1243
- baseHeaders(json) {
1244
- const h = new Headers(this.options.headers);
1245
- if (json) {
1246
- 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
+ }
1247
1685
  }
1248
- return h;
1686
+ this.emit("INITIAL_SESSION", this.currentSession);
1687
+ return this.currentSession;
1249
1688
  }
1250
- authPrefix() {
1251
- const gid = requireAuthGroupId(this.options);
1252
- return `/auth-groups/${gid}/auth`;
1253
- }
1254
- /** Supabase: `signUp` → `{ data: { user, session }, error }` */
1689
+ // ── Auth methods ───────────────────────────────────────────────────────────
1255
1690
  async signUp(credentials) {
1256
1691
  return asPostgrestResponse(async () => {
1257
1692
  const name = typeof credentials.options?.data?.name === "string" ? credentials.options.data.name : void 0;
1258
- const session = await this.register({
1693
+ const raw = await this.fetchAuth("/register", "POST", {
1259
1694
  email: credentials.email,
1260
1695
  password: credentials.password,
1261
- name
1696
+ ...name !== void 0 ? { name } : {}
1262
1697
  });
1263
- 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 };
1264
1701
  });
1265
1702
  }
1266
- /** Supabase: `signInWithPassword` */
1267
1703
  async signInWithPassword(credentials) {
1268
1704
  return asPostgrestResponse(async () => {
1269
- const session = await this.login(credentials);
1270
- 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 };
1271
1712
  });
1272
1713
  }
1273
- /** 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
+ }
1274
1724
  async refreshSession(refreshToken) {
1275
1725
  return asPostgrestResponse(async () => {
1276
- const tokens = await this.refresh({ refreshToken });
1277
- 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;
1278
1749
  const session = {
1279
- access_token: tokens.accessToken,
1280
- refresh_token: tokens.refreshToken,
1281
- 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,
1282
1754
  token_type: "bearer",
1283
1755
  user: me.user
1284
1756
  };
1757
+ await this.setSessionInternal(session, "SIGNED_IN");
1285
1758
  return { session, user: me.user };
1286
1759
  });
1287
1760
  }
1288
- /** Supabase: `getUser()` — needs `getAccessToken` on the client */
1289
- async getUser() {
1290
- return asPostgrestResponse(() => this.getMe());
1291
- }
1292
- /** Supabase: `updateUser` */
1293
1761
  async updateUser(attributes) {
1294
- return asPostgrestResponse(
1295
- () => this.updateMe({
1296
- password: attributes.password,
1297
- name: attributes.data?.name
1298
- })
1299
- );
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
+ });
1300
1776
  }
1301
- /**
1302
- * Supabase/Firebase: no server call — clear tokens in your app.
1303
- * Returns `{ error: null }` for API compatibility.
1304
- */
1305
- async signOut(_options) {
1306
- 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 } } };
1307
1786
  }
1308
- async getUserFromToken(accessToken) {
1309
- const headers = this.baseHeaders(false);
1310
- headers.set("Authorization", `Bearer ${accessToken}`);
1311
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/me`, {
1312
- method: "GET",
1313
- headers
1314
- });
1315
- return parseJsonOrThrow(response);
1787
+ // ── Accessors ──────────────────────────────────────────────────────────────
1788
+ getAccessToken() {
1789
+ return this.currentSession?.access_token ?? null;
1316
1790
  }
1791
+ getCurrentSession() {
1792
+ return this.currentSession;
1793
+ }
1794
+ // ── Back-compat: raw Ragable auth methods ──────────────────────────────────
1317
1795
  async register(body) {
1318
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/register`, {
1319
- method: "POST",
1320
- headers: this.baseHeaders(true),
1321
- body: JSON.stringify(body)
1322
- });
1323
- 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;
1324
1800
  }
1325
1801
  async login(body) {
1326
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/login`, {
1327
- method: "POST",
1328
- headers: this.baseHeaders(true),
1329
- body: JSON.stringify(body)
1330
- });
1331
- 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;
1332
1806
  }
1333
1807
  async refresh(body) {
1334
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/refresh`, {
1335
- method: "POST",
1336
- headers: this.baseHeaders(true),
1337
- body: JSON.stringify(body)
1338
- });
1339
- return parseJsonOrThrow(response);
1808
+ return this.fetchAuth("/refresh", "POST", body);
1340
1809
  }
1341
1810
  async getMe() {
1342
- const token = await requireAccessToken(this.options);
1343
- const headers = this.baseHeaders(false);
1344
- headers.set("Authorization", `Bearer ${token}`);
1345
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/me`, {
1346
- method: "GET",
1347
- 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
1348
1853
  });
1349
1854
  return parseJsonOrThrow(response);
1350
1855
  }
1351
- async updateMe(body) {
1352
- const token = await requireAccessToken(this.options);
1353
- const headers = this.baseHeaders(true);
1856
+ async fetchAuthWithBearer(path, method, token, body) {
1857
+ const headers = this.baseHeaders(body !== void 0);
1354
1858
  headers.set("Authorization", `Bearer ${token}`);
1355
- const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/me`, {
1356
- method: "PATCH",
1859
+ const response = await this.fetchImpl(this.toUrl(path), {
1860
+ method,
1357
1861
  headers,
1358
- body: JSON.stringify(body)
1862
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1359
1863
  });
1360
1864
  return parseJsonOrThrow(response);
1361
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
+ }
1362
2125
  };
1363
2126
  var RagableBrowserDatabaseClient = class {
1364
- constructor(options) {
2127
+ constructor(options, ragableAuth = null) {
1365
2128
  this.options = options;
2129
+ this.ragableAuth = ragableAuth;
1366
2130
  __publicField(this, "fetchImpl");
1367
2131
  this.fetchImpl = bindFetch(options.fetch);
1368
2132
  }
@@ -1371,7 +2135,7 @@ var RagableBrowserDatabaseClient = class {
1371
2135
  }
1372
2136
  async query(params) {
1373
2137
  const gid = requireAuthGroupId(this.options);
1374
- const token = await resolveDatabaseAuthBearer(this.options);
2138
+ const token = await resolveDatabaseAuthBearer(this.options, this.ragableAuth);
1375
2139
  const databaseInstanceId = params.databaseInstanceId?.trim() || this.options.databaseInstanceId?.trim();
1376
2140
  if (!databaseInstanceId) {
1377
2141
  throw new Error(
@@ -1397,7 +2161,7 @@ var RagableBrowserDatabaseClient = class {
1397
2161
  })
1398
2162
  }
1399
2163
  );
1400
- return parseJsonOrThrow(response);
2164
+ return parseJsonOrThrow2(response);
1401
2165
  }
1402
2166
  baseHeaders() {
1403
2167
  return new Headers(this.options.headers);
@@ -1412,9 +2176,6 @@ var RagableBrowserAgentsClient = class {
1412
2176
  toUrl(path) {
1413
2177
  return `${normalizeBrowserApiBase(this.options.baseUrl)}${path.startsWith("/") ? path : `/${path}`}`;
1414
2178
  }
1415
- /**
1416
- * Stream agent execution as SSE (`POST /public/organizations/:orgId/agents/:agentId/chat/stream`).
1417
- */
1418
2179
  async *chatStream(agentId, params) {
1419
2180
  const orgId = this.options.organizationId;
1420
2181
  const body = {
@@ -1450,16 +2211,40 @@ var RagableBrowser = class {
1450
2211
  __publicField(this, "agents");
1451
2212
  __publicField(this, "auth");
1452
2213
  __publicField(this, "database");
2214
+ __publicField(this, "transport");
1453
2215
  __publicField(this, "options");
2216
+ __publicField(this, "_ragableAuth");
1454
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
+ }
1455
2244
  this.agents = new RagableBrowserAgentsClient(options);
1456
- this.auth = new RagableBrowserAuthClient(options);
1457
- this.database = new RagableBrowserDatabaseClient(options);
2245
+ this.auth = new RagableBrowserAuthClient(options, this._ragableAuth);
2246
+ this.database = new RagableBrowserDatabaseClient(options, this._ragableAuth);
1458
2247
  }
1459
- /**
1460
- * Supabase-style table API: `.from('items').select().eq('id', 1).single()`.
1461
- * Pass `databaseInstanceId` here or set `databaseInstanceId` on the client options.
1462
- */
1463
2248
  from(table, databaseInstanceId) {
1464
2249
  const id = databaseInstanceId?.trim() || this.options.databaseInstanceId?.trim();
1465
2250
  if (!id) {
@@ -1467,8 +2252,40 @@ var RagableBrowser = class {
1467
2252
  "RagableBrowser.from() requires databaseInstanceId in client options or as the second argument"
1468
2253
  );
1469
2254
  }
1470
- const run = (p) => this.database.query(p);
1471
- 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();
1472
2289
  }
1473
2290
  };
1474
2291
  function createBrowserClient(options) {
@@ -1567,7 +2384,11 @@ function createRagableServerClient(options) {
1567
2384
  // Annotate the CommonJS export names for ESM import in node:
1568
2385
  0 && (module.exports = {
1569
2386
  AgentsClient,
2387
+ AuthBroadcastChannel,
2388
+ CookieStorageAdapter,
1570
2389
  DEFAULT_RAGABLE_API_BASE,
2390
+ LocalStorageAdapter,
2391
+ MemoryStorageAdapter,
1571
2392
  PostgrestDeleteReturningBuilder,
1572
2393
  PostgrestDeleteRootBuilder,
1573
2394
  PostgrestInsertReturningBuilder,
@@ -1579,13 +2400,20 @@ function createRagableServerClient(options) {
1579
2400
  PostgrestUpsertReturningBuilder,
1580
2401
  PostgrestUpsertRootBuilder,
1581
2402
  Ragable,
2403
+ RagableAbortError,
2404
+ RagableAuth,
1582
2405
  RagableBrowser,
1583
2406
  RagableBrowserAgentsClient,
1584
2407
  RagableBrowserAuthClient,
1585
2408
  RagableBrowserDatabaseClient,
1586
2409
  RagableError,
2410
+ RagableNetworkError,
1587
2411
  RagableRequestClient,
2412
+ RagableSdkError,
2413
+ RagableTimeoutError,
2414
+ SessionStorageAdapter,
1588
2415
  ShiftClient,
2416
+ Transport,
1589
2417
  asPostgrestResponse,
1590
2418
  bindFetch,
1591
2419
  createBrowserClient,
@@ -1593,10 +2421,13 @@ function createRagableServerClient(options) {
1593
2421
  createRagPipeline,
1594
2422
  createRagableBrowserClient,
1595
2423
  createRagableServerClient,
2424
+ detectStorage,
1596
2425
  extractErrorMessage,
1597
2426
  formatRetrievalContext,
2427
+ generateIdempotencyKey,
1598
2428
  normalizeBrowserApiBase,
1599
2429
  parseSseDataLine,
2430
+ parseTransportResponse,
1600
2431
  readSseStream
1601
2432
  });
1602
2433
  //# sourceMappingURL=index.js.map