@ragable/sdk 0.5.0 → 0.6.0

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