@ragable/sdk 0.5.1 → 0.6.1

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