@ragable/sdk 0.3.0 → 0.4.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
@@ -338,6 +338,356 @@ var AgentsClient = class {
338
338
  }
339
339
  };
340
340
 
341
+ // src/browser-postgrest.ts
342
+ function assertIdent(name, ctx) {
343
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
344
+ throw new RagableError(
345
+ `Invalid ${ctx} identifier "${name}" (use letters, numbers, underscore)`,
346
+ 400,
347
+ null
348
+ );
349
+ }
350
+ }
351
+ function quoteIdent(name) {
352
+ assertIdent(name, "column");
353
+ return `"${name}"`;
354
+ }
355
+ var OP_SQL = {
356
+ eq: "=",
357
+ neq: "<>",
358
+ gt: ">",
359
+ gte: ">=",
360
+ lt: "<",
361
+ lte: "<=",
362
+ like: "LIKE",
363
+ ilike: "ILIKE"
364
+ };
365
+ async function asPostgrestResponse(fn) {
366
+ try {
367
+ const data = await fn();
368
+ return { data, error: null };
369
+ } catch (e) {
370
+ const err = e instanceof RagableError ? e : new RagableError(e.message, 500, null);
371
+ return { data: null, error: err };
372
+ }
373
+ }
374
+ function buildWhere(filters, params) {
375
+ if (filters.length === 0) return { clause: "", nextIdx: 1 };
376
+ const parts = [];
377
+ let i = params.length;
378
+ for (const f of filters) {
379
+ assertIdent(f.column, "filter");
380
+ i += 1;
381
+ parts.push(`${quoteIdent(f.column)} ${OP_SQL[f.op]} $${i}`);
382
+ params.push(f.value);
383
+ }
384
+ return { clause: ` WHERE ${parts.join(" AND ")}`, nextIdx: i };
385
+ }
386
+ var PostgrestSelectBuilder = class {
387
+ constructor(run, databaseInstanceId, table, columns) {
388
+ this.run = run;
389
+ this.databaseInstanceId = databaseInstanceId;
390
+ this.table = table;
391
+ this.columns = columns;
392
+ __publicField(this, "filters", []);
393
+ __publicField(this, "_limit");
394
+ __publicField(this, "_order");
395
+ assertIdent(table, "table");
396
+ }
397
+ eq(column, value) {
398
+ this.filters.push({ op: "eq", column, value });
399
+ return this;
400
+ }
401
+ neq(column, value) {
402
+ this.filters.push({ op: "neq", column, value });
403
+ return this;
404
+ }
405
+ gt(column, value) {
406
+ this.filters.push({ op: "gt", column, value });
407
+ return this;
408
+ }
409
+ gte(column, value) {
410
+ this.filters.push({ op: "gte", column, value });
411
+ return this;
412
+ }
413
+ lt(column, value) {
414
+ this.filters.push({ op: "lt", column, value });
415
+ return this;
416
+ }
417
+ lte(column, value) {
418
+ this.filters.push({ op: "lte", column, value });
419
+ return this;
420
+ }
421
+ like(column, value) {
422
+ this.filters.push({ op: "like", column, value });
423
+ return this;
424
+ }
425
+ ilike(column, value) {
426
+ this.filters.push({ op: "ilike", column, value });
427
+ return this;
428
+ }
429
+ limit(n) {
430
+ this._limit = n;
431
+ return this;
432
+ }
433
+ order(column, options) {
434
+ this._order = { column, ascending: options?.ascending !== false };
435
+ return this;
436
+ }
437
+ /** @param includeUserLimit when false, omit `.limit()` (for `.single()` / `.maybeSingle()`). */
438
+ buildSelectCore(params, includeUserLimit) {
439
+ const tbl = quoteIdent(this.table);
440
+ const { clause } = buildWhere(this.filters, params);
441
+ let sql = `SELECT ${this.columns} FROM ${tbl}${clause}`;
442
+ if (this._order) {
443
+ sql += ` ORDER BY ${quoteIdent(this._order.column)} ${this._order.ascending ? "ASC" : "DESC"}`;
444
+ }
445
+ if (includeUserLimit && this._limit != null) {
446
+ sql += ` LIMIT ${Math.max(0, Math.floor(this._limit))}`;
447
+ }
448
+ return sql;
449
+ }
450
+ then(onfulfilled, onrejected) {
451
+ return this.executeMany().then(onfulfilled, onrejected);
452
+ }
453
+ async executeMany() {
454
+ return asPostgrestResponse(async () => {
455
+ const params = [];
456
+ const sql = this.buildSelectCore(params, true);
457
+ const res = await this.run({
458
+ databaseInstanceId: this.databaseInstanceId,
459
+ sql,
460
+ params,
461
+ readOnly: true
462
+ });
463
+ return res.rows;
464
+ });
465
+ }
466
+ async single() {
467
+ return asPostgrestResponse(async () => {
468
+ const params = [];
469
+ const base = this.buildSelectCore(params, false);
470
+ const sql = `${base} LIMIT 2`;
471
+ const res = await this.run({
472
+ databaseInstanceId: this.databaseInstanceId,
473
+ sql,
474
+ params,
475
+ readOnly: true
476
+ });
477
+ if (res.rows.length === 0) {
478
+ throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
479
+ code: "PGRST116"
480
+ });
481
+ }
482
+ if (res.rows.length > 1) {
483
+ throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
484
+ code: "PGRST116"
485
+ });
486
+ }
487
+ return res.rows[0];
488
+ });
489
+ }
490
+ async maybeSingle() {
491
+ return asPostgrestResponse(async () => {
492
+ const params = [];
493
+ const base = this.buildSelectCore(params, false);
494
+ const sql = `${base} LIMIT 2`;
495
+ const res = await this.run({
496
+ databaseInstanceId: this.databaseInstanceId,
497
+ sql,
498
+ params,
499
+ readOnly: true
500
+ });
501
+ if (res.rows.length > 1) {
502
+ throw new RagableError("JSON object requested, multiple (or no) rows returned", 406, {
503
+ code: "PGRST116"
504
+ });
505
+ }
506
+ return res.rows[0] ?? null;
507
+ });
508
+ }
509
+ };
510
+ var PostgrestInsertBuilder = class {
511
+ constructor(run, databaseInstanceId, table, rows) {
512
+ this.run = run;
513
+ this.databaseInstanceId = databaseInstanceId;
514
+ this.table = table;
515
+ this.rows = rows;
516
+ __publicField(this, "returning", "*");
517
+ assertIdent(table, "table");
518
+ }
519
+ select(columns = "*") {
520
+ this.returning = columns;
521
+ return this;
522
+ }
523
+ then(onfulfilled, onrejected) {
524
+ return this.execute().then(onfulfilled, onrejected);
525
+ }
526
+ async execute() {
527
+ return asPostgrestResponse(async () => {
528
+ if (this.rows.length === 0) return [];
529
+ const keys = Object.keys(this.rows[0]);
530
+ for (const k of keys) assertIdent(k, "column");
531
+ const tbl = quoteIdent(this.table);
532
+ const params = [];
533
+ const valueGroups = [];
534
+ for (const row of this.rows) {
535
+ const placeholders = [];
536
+ for (const k of keys) {
537
+ params.push(row[k]);
538
+ placeholders.push(`$${params.length}`);
539
+ }
540
+ valueGroups.push(`(${placeholders.join(", ")})`);
541
+ }
542
+ const cols = keys.map(quoteIdent).join(", ");
543
+ const sql = `INSERT INTO ${tbl} (${cols}) VALUES ${valueGroups.join(", ")} RETURNING ${this.returning}`;
544
+ const res = await this.run({
545
+ databaseInstanceId: this.databaseInstanceId,
546
+ sql,
547
+ params,
548
+ readOnly: false
549
+ });
550
+ return res.rows;
551
+ });
552
+ }
553
+ };
554
+ var PostgrestUpdateBuilder = class {
555
+ constructor(run, databaseInstanceId, table, patch) {
556
+ this.run = run;
557
+ this.databaseInstanceId = databaseInstanceId;
558
+ this.table = table;
559
+ this.patch = patch;
560
+ __publicField(this, "filters", []);
561
+ __publicField(this, "returning", "*");
562
+ assertIdent(table, "table");
563
+ }
564
+ eq(column, value) {
565
+ this.filters.push({ op: "eq", column, value });
566
+ return this;
567
+ }
568
+ select(columns = "*") {
569
+ this.returning = columns;
570
+ return this;
571
+ }
572
+ then(onfulfilled, onrejected) {
573
+ return this.execute().then(onfulfilled, onrejected);
574
+ }
575
+ async execute() {
576
+ return asPostgrestResponse(async () => {
577
+ const keys = Object.keys(this.patch);
578
+ if (keys.length === 0) {
579
+ throw new RagableError("Empty update payload", 400, null);
580
+ }
581
+ for (const k of keys) assertIdent(k, "column");
582
+ if (this.filters.length === 0) {
583
+ throw new RagableError(
584
+ "UPDATE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped update",
585
+ 400,
586
+ null
587
+ );
588
+ }
589
+ const params = [];
590
+ const sets = [];
591
+ for (const k of keys) {
592
+ params.push(this.patch[k]);
593
+ sets.push(`${quoteIdent(k)} = $${params.length}`);
594
+ }
595
+ const { clause } = buildWhere(this.filters, params);
596
+ const tbl = quoteIdent(this.table);
597
+ const sql = `UPDATE ${tbl} SET ${sets.join(", ")}${clause} RETURNING ${this.returning}`;
598
+ const res = await this.run({
599
+ databaseInstanceId: this.databaseInstanceId,
600
+ sql,
601
+ params,
602
+ readOnly: false
603
+ });
604
+ return res.rows;
605
+ });
606
+ }
607
+ };
608
+ var PostgrestDeleteBuilder = class {
609
+ constructor(run, databaseInstanceId, table) {
610
+ this.run = run;
611
+ this.databaseInstanceId = databaseInstanceId;
612
+ this.table = table;
613
+ __publicField(this, "filters", []);
614
+ __publicField(this, "returning", "*");
615
+ assertIdent(table, "table");
616
+ }
617
+ eq(column, value) {
618
+ this.filters.push({ op: "eq", column, value });
619
+ return this;
620
+ }
621
+ select(columns = "*") {
622
+ this.returning = columns;
623
+ return this;
624
+ }
625
+ then(onfulfilled, onrejected) {
626
+ return this.execute().then(onfulfilled, onrejected);
627
+ }
628
+ async execute() {
629
+ return asPostgrestResponse(async () => {
630
+ if (this.filters.length === 0) {
631
+ throw new RagableError(
632
+ "DELETE requires a filter (e.g. .eq('id', value)) \u2014 refusing unscoped delete",
633
+ 400,
634
+ null
635
+ );
636
+ }
637
+ const params = [];
638
+ const { clause } = buildWhere(this.filters, params);
639
+ const tbl = quoteIdent(this.table);
640
+ const sql = `DELETE FROM ${tbl}${clause} RETURNING ${this.returning}`;
641
+ const res = await this.run({
642
+ databaseInstanceId: this.databaseInstanceId,
643
+ sql,
644
+ params,
645
+ readOnly: false
646
+ });
647
+ return res.rows;
648
+ });
649
+ }
650
+ };
651
+ var PostgrestTableApi = class {
652
+ constructor(run, databaseInstanceId, table) {
653
+ this.run = run;
654
+ this.databaseInstanceId = databaseInstanceId;
655
+ this.table = table;
656
+ }
657
+ select(columns = "*") {
658
+ return new PostgrestSelectBuilder(
659
+ this.run,
660
+ this.databaseInstanceId,
661
+ this.table,
662
+ columns
663
+ );
664
+ }
665
+ insert(values) {
666
+ const rows = Array.isArray(values) ? values : [values];
667
+ return new PostgrestInsertBuilder(
668
+ this.run,
669
+ this.databaseInstanceId,
670
+ this.table,
671
+ rows
672
+ );
673
+ }
674
+ update(patch) {
675
+ return new PostgrestUpdateBuilder(
676
+ this.run,
677
+ this.databaseInstanceId,
678
+ this.table,
679
+ patch
680
+ );
681
+ }
682
+ delete() {
683
+ return new PostgrestDeleteBuilder(
684
+ this.run,
685
+ this.databaseInstanceId,
686
+ this.table
687
+ );
688
+ }
689
+ };
690
+
341
691
  // src/browser.ts
342
692
  function normalizeBrowserApiBase(baseUrl) {
343
693
  return (baseUrl ?? "http://localhost:8080/api").replace(/\/+$/, "");
@@ -372,6 +722,27 @@ async function parseJsonOrThrow(response) {
372
722
  }
373
723
  return payload;
374
724
  }
725
+ function parseExpiresInSeconds(expiresIn) {
726
+ const s = expiresIn.trim().toLowerCase();
727
+ const m = /^(\d+)([smhd])?$/.exec(s);
728
+ if (m) {
729
+ const n = Number(m[1]);
730
+ const u = m[2] ?? "s";
731
+ const mult = u === "s" ? 1 : u === "m" ? 60 : u === "h" ? 3600 : u === "d" ? 86400 : 1;
732
+ return n * mult;
733
+ }
734
+ const asNum = Number(s);
735
+ return Number.isFinite(asNum) ? asNum : 0;
736
+ }
737
+ function toSupabaseSession(s) {
738
+ return {
739
+ access_token: s.accessToken,
740
+ refresh_token: s.refreshToken,
741
+ expires_in: parseExpiresInSeconds(s.expiresIn),
742
+ token_type: "bearer",
743
+ user: s.user
744
+ };
745
+ }
375
746
  var RagableBrowserAuthClient = class {
376
747
  constructor(options) {
377
748
  this.options = options;
@@ -393,6 +764,69 @@ var RagableBrowserAuthClient = class {
393
764
  const gid = requireAuthGroupId(this.options);
394
765
  return `/auth-groups/${gid}/auth`;
395
766
  }
767
+ /** Supabase: `signUp` → `{ data: { user, session }, error }` */
768
+ async signUp(credentials) {
769
+ return asPostgrestResponse(async () => {
770
+ const name = typeof credentials.options?.data?.name === "string" ? credentials.options.data.name : void 0;
771
+ const session = await this.register({
772
+ email: credentials.email,
773
+ password: credentials.password,
774
+ name
775
+ });
776
+ return { user: session.user, session: toSupabaseSession(session) };
777
+ });
778
+ }
779
+ /** Supabase: `signInWithPassword` */
780
+ async signInWithPassword(credentials) {
781
+ return asPostgrestResponse(async () => {
782
+ const session = await this.login(credentials);
783
+ return { user: session.user, session: toSupabaseSession(session) };
784
+ });
785
+ }
786
+ /** Supabase: `refreshSession` */
787
+ async refreshSession(refreshToken) {
788
+ return asPostgrestResponse(async () => {
789
+ const tokens = await this.refresh({ refreshToken });
790
+ const me = await this.getUserFromToken(tokens.accessToken);
791
+ const session = {
792
+ access_token: tokens.accessToken,
793
+ refresh_token: tokens.refreshToken,
794
+ expires_in: parseExpiresInSeconds(tokens.expiresIn),
795
+ token_type: "bearer",
796
+ user: me.user
797
+ };
798
+ return { session, user: me.user };
799
+ });
800
+ }
801
+ /** Supabase: `getUser()` — needs `getAccessToken` on the client */
802
+ async getUser() {
803
+ return asPostgrestResponse(() => this.getMe());
804
+ }
805
+ /** Supabase: `updateUser` */
806
+ async updateUser(attributes) {
807
+ return asPostgrestResponse(
808
+ () => this.updateMe({
809
+ password: attributes.password,
810
+ name: attributes.data?.name
811
+ })
812
+ );
813
+ }
814
+ /**
815
+ * Supabase/Firebase: no server call — clear tokens in your app.
816
+ * Returns `{ error: null }` for API compatibility.
817
+ */
818
+ async signOut(_options) {
819
+ return { error: null };
820
+ }
821
+ async getUserFromToken(accessToken) {
822
+ const headers = this.baseHeaders(false);
823
+ headers.set("Authorization", `Bearer ${accessToken}`);
824
+ const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/me`, {
825
+ method: "GET",
826
+ headers
827
+ });
828
+ return parseJsonOrThrow(response);
829
+ }
396
830
  async register(body) {
397
831
  const response = await this.fetchImpl(`${this.toUrl(this.authPrefix())}/register`, {
398
832
  method: "POST",
@@ -464,6 +898,7 @@ var RagableBrowserDatabaseClient = class {
464
898
  databaseInstanceId: params.databaseInstanceId,
465
899
  sql: params.sql,
466
900
  ...params.params !== void 0 ? { params: params.params } : {},
901
+ ...params.readOnly === false ? { readOnly: false } : {},
467
902
  ...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
468
903
  ...params.rowLimit !== void 0 ? { rowLimit: params.rowLimit } : {}
469
904
  })
@@ -523,14 +958,31 @@ var RagableBrowser = class {
523
958
  __publicField(this, "agents");
524
959
  __publicField(this, "auth");
525
960
  __publicField(this, "database");
961
+ __publicField(this, "options");
962
+ this.options = options;
526
963
  this.agents = new RagableBrowserAgentsClient(options);
527
964
  this.auth = new RagableBrowserAuthClient(options);
528
965
  this.database = new RagableBrowserDatabaseClient(options);
529
966
  }
967
+ /**
968
+ * Supabase-style table API: `.from('items').select().eq('id', 1).single()`.
969
+ * Pass `databaseInstanceId` here or set `databaseInstanceId` on the client options.
970
+ */
971
+ from(table, databaseInstanceId) {
972
+ const id = databaseInstanceId?.trim() || this.options.databaseInstanceId?.trim();
973
+ if (!id) {
974
+ throw new Error(
975
+ "RagableBrowser.from() requires databaseInstanceId in client options or as the second argument"
976
+ );
977
+ }
978
+ const run = (p) => this.database.query(p);
979
+ return new PostgrestTableApi(run, id, table);
980
+ }
530
981
  };
531
982
  function createBrowserClient(options) {
532
983
  return new RagableBrowser(options);
533
984
  }
985
+ var createRagableBrowserClient = createBrowserClient;
534
986
 
535
987
  // src/rag.ts
536
988
  function formatRetrievalContext(results, options = {}) {
@@ -588,11 +1040,45 @@ var Ragable = class {
588
1040
  };
589
1041
  }
590
1042
  };
591
- function createClient(options) {
1043
+ function isServerClientOptions(o) {
1044
+ return typeof o === "object" && o !== null && "apiKey" in o && typeof o.apiKey === "string" && o.apiKey.length > 0;
1045
+ }
1046
+ function createClient(urlOrOptions, browserOptions) {
1047
+ if (typeof urlOrOptions === "string") {
1048
+ if (!browserOptions) {
1049
+ throw new Error(
1050
+ "createClient(url, options) requires options with at least organizationId"
1051
+ );
1052
+ }
1053
+ const raw = urlOrOptions.trim().replace(/\/+$/, "");
1054
+ const baseUrl = raw.endsWith("/api") ? raw : `${raw}/api`;
1055
+ return createBrowserClient({
1056
+ ...browserOptions,
1057
+ baseUrl: normalizeBrowserApiBase(baseUrl)
1058
+ });
1059
+ }
1060
+ if (isServerClientOptions(urlOrOptions)) {
1061
+ return new Ragable(urlOrOptions);
1062
+ }
1063
+ if (typeof urlOrOptions === "object" && urlOrOptions !== null && "organizationId" in urlOrOptions && typeof urlOrOptions.organizationId === "string") {
1064
+ return createBrowserClient(
1065
+ urlOrOptions
1066
+ );
1067
+ }
1068
+ throw new Error(
1069
+ "createClient(options) requires apiKey (server) or organizationId (browser)"
1070
+ );
1071
+ }
1072
+ function createRagableServerClient(options) {
592
1073
  return new Ragable(options);
593
1074
  }
594
1075
  export {
595
1076
  AgentsClient,
1077
+ PostgrestDeleteBuilder,
1078
+ PostgrestInsertBuilder,
1079
+ PostgrestSelectBuilder,
1080
+ PostgrestTableApi,
1081
+ PostgrestUpdateBuilder,
596
1082
  Ragable,
597
1083
  RagableBrowser,
598
1084
  RagableBrowserAgentsClient,
@@ -601,9 +1087,12 @@ export {
601
1087
  RagableError,
602
1088
  RagableRequestClient,
603
1089
  ShiftClient,
1090
+ asPostgrestResponse,
604
1091
  createBrowserClient,
605
1092
  createClient,
606
1093
  createRagPipeline,
1094
+ createRagableBrowserClient,
1095
+ createRagableServerClient,
607
1096
  extractErrorMessage,
608
1097
  formatRetrievalContext,
609
1098
  normalizeBrowserApiBase,