@persql/sdk 0.1.0 → 1.0.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/README.md CHANGED
@@ -107,9 +107,9 @@ const { data } = await db.query("SELECT * FROM customers");
107
107
 
108
108
  `query / batch / transaction / tables / explain / schema` route
109
109
  through `better-sqlite3` (lazy-loaded — never required for live use).
110
- `vectors`, `blob`, and `subscribe` throwthey have no local
111
- equivalent. The same `db.driver()` works in local mode, so a
112
- Drizzle-based data layer needs zero changes between tests and prod.
110
+ `subscribe` throwsit has no local equivalent. The same
111
+ `db.driver()` works in local mode, so a Drizzle-based data layer
112
+ needs zero changes between tests and prod.
113
113
 
114
114
  Call `persql.close()` to release the file handle when done.
115
115
 
@@ -1,4 +1,26 @@
1
1
  // src/local.ts
2
+ function checkLocalExpect(e, r) {
3
+ const rows = r.rows.length;
4
+ if (e.rows != null && rows !== e.rows) {
5
+ return `expected rows=${e.rows}, got ${rows}`;
6
+ }
7
+ if (e.rowsAtLeast != null && rows < e.rowsAtLeast) {
8
+ return `expected rows>=${e.rowsAtLeast}, got ${rows}`;
9
+ }
10
+ if (e.rowsAtMost != null && rows > e.rowsAtMost) {
11
+ return `expected rows<=${e.rowsAtMost}, got ${rows}`;
12
+ }
13
+ if (e.rowsWritten != null && r.rowsWritten !== e.rowsWritten) {
14
+ return `expected rowsWritten=${e.rowsWritten}, got ${r.rowsWritten}`;
15
+ }
16
+ if (e.rowsWrittenAtLeast != null && r.rowsWritten < e.rowsWrittenAtLeast) {
17
+ return `expected rowsWritten>=${e.rowsWrittenAtLeast}, got ${r.rowsWritten}`;
18
+ }
19
+ if (e.rowsWrittenAtMost != null && r.rowsWritten > e.rowsWrittenAtMost) {
20
+ return `expected rowsWritten<=${e.rowsWrittenAtMost}, got ${r.rowsWritten}`;
21
+ }
22
+ return null;
23
+ }
2
24
  var PROPOSAL_TTL_DEFAULT_SEC = 600;
3
25
  var PROPOSAL_TTL_MAX_SEC = 3600;
4
26
  function newLocalProposalId() {
@@ -37,7 +59,16 @@ var LocalDriver = class {
37
59
  }
38
60
  async batch(statements, transaction) {
39
61
  const db = await this.open();
40
- const exec = () => statements.map((s) => runOne(db, s.sql, s.params ?? []));
62
+ const exec = () => statements.map((s, i) => {
63
+ const r = runOne(db, s.sql, s.params ?? []);
64
+ if (s.expect) {
65
+ const violation = checkLocalExpect(s.expect, r);
66
+ if (violation) {
67
+ throw new Error(`Assertion failed on statement ${i}: ${violation}`);
68
+ }
69
+ }
70
+ return r;
71
+ });
41
72
  if (transaction) return db.transaction(exec)();
42
73
  return exec();
43
74
  }
@@ -199,6 +230,19 @@ var PerSQL = class _PerSQL {
199
230
  close() {
200
231
  this.local?.close();
201
232
  }
233
+ /**
234
+ * Resolve the bearer token's namespace + live usage. Use this once
235
+ * per agent run so you know the slug to put in URLs and how much
236
+ * monthly budget / prepaid balance remains before a 402.
237
+ *
238
+ * Throws in local mode — no server, nothing to ask.
239
+ */
240
+ async me() {
241
+ if (this.local) {
242
+ throw new Error("PerSQL: me() is not available in local mode.");
243
+ }
244
+ return this.request("GET", "/v1/me");
245
+ }
202
246
  /**
203
247
  * Redeem a handoff token (`phand_…`) for a regular PerSQL client
204
248
  * scoped to the handed-off (database, branch, role). Single use:
@@ -272,40 +316,74 @@ var PerSQL = class _PerSQL {
272
316
  }
273
317
  return envelope.data;
274
318
  }
275
- /** @internal — raw upload (e.g. blob PUT) returning the ApiResponse data. */
276
- async requestRaw(method, path, body, contentType) {
277
- const res = await this._fetch(`${this.baseURL}${path}`, {
278
- method,
279
- headers: {
280
- Authorization: `Bearer ${this.token}`,
281
- "Content-Type": contentType ?? "application/octet-stream"
282
- },
283
- body,
284
- // @ts-expect-error — duplex is required by Node fetch for streaming bodies
285
- duplex: "half"
286
- });
287
- if (res.status === 429) {
288
- const retryAfter = Number(res.headers.get("retry-after") ?? "60");
289
- throw new RateLimitError(retryAfter);
290
- }
291
- let envelope = null;
292
- try {
293
- envelope = await res.json();
294
- } catch {
295
- }
296
- if (!res.ok || !envelope?.success) {
297
- const message = envelope?.error ?? `Request failed (${res.status})`;
298
- throw new PerSQLError(res.status, message, envelope?.errorDetail);
299
- }
300
- return envelope.data;
301
- }
302
- /** @internal — raw fetch returning the underlying Response (used by blob GET). */
319
+ /** @internal — raw fetch returning the underlying Response. */
303
320
  fetchRaw(method, path) {
304
321
  return this._fetch(`${this.baseURL}${path}`, {
305
322
  method,
306
323
  headers: { Authorization: `Bearer ${this.token}` }
307
324
  });
308
325
  }
326
+ /**
327
+ * File a support ticket directly from your SDK code. Tickets land in
328
+ * the customer console at `/support`; staff reply from the admin
329
+ * app. Useful for agents and background jobs that hit a PerSQL
330
+ * error and want a human to see it.
331
+ *
332
+ * ```ts
333
+ * try {
334
+ * await db.query("UPDATE ...");
335
+ * } catch (err) {
336
+ * await persql.support.createTicket({
337
+ * subject: "Branch merge keeps 500ing",
338
+ * body: "Repro: ...",
339
+ * error: err,
340
+ * });
341
+ * }
342
+ * ```
343
+ */
344
+ get support() {
345
+ return new SupportClient(this);
346
+ }
347
+ };
348
+ function errorContextFor(err) {
349
+ if (err instanceof PerSQLError) {
350
+ return {
351
+ errorName: err.name,
352
+ errorMessage: err.message,
353
+ status: err.status,
354
+ detail: err.detail ?? null
355
+ };
356
+ }
357
+ if (err instanceof Error) {
358
+ return { errorName: err.name, errorMessage: err.message };
359
+ }
360
+ if (err !== void 0 && err !== null) {
361
+ return { errorMessage: String(err) };
362
+ }
363
+ return {};
364
+ }
365
+ var SupportClient = class {
366
+ constructor(client) {
367
+ this.client = client;
368
+ }
369
+ async createTicket(opts) {
370
+ if (this.client.local) {
371
+ throw new Error("support.createTicket is not supported in local mode");
372
+ }
373
+ const errorContext = {
374
+ ...errorContextFor(opts.error),
375
+ ...opts.errorContext ?? {}
376
+ };
377
+ return this.client.request(
378
+ "POST",
379
+ "/v1/support/tickets",
380
+ {
381
+ subject: opts.subject,
382
+ body: opts.body,
383
+ errorContext: Object.keys(errorContext).length > 0 ? errorContext : void 0
384
+ }
385
+ );
386
+ }
309
387
  };
310
388
  var PerSQLDatabase = class {
311
389
  constructor(client, namespace, slug) {
@@ -359,6 +437,25 @@ var PerSQLDatabase = class {
359
437
  transaction(statements, options = {}) {
360
438
  return this.batch(statements, { ...options, transaction: true });
361
439
  }
440
+ /**
441
+ * Mint a short-lived session JWT for an end user. The server (which
442
+ * holds the bearer token) calls this and hands the resulting token
443
+ * to an untrusted client (browser, mobile app, agent run). The
444
+ * client uses the JWT to call this database's published `/p/*`
445
+ * endpoints on behalf of `userId`. TTL defaults to 1 hour; max 24h.
446
+ */
447
+ async createSession(opts) {
448
+ if (this.client.local) {
449
+ throw new Error("createSession is not supported in local mode");
450
+ }
451
+ return this.client.request("POST", "/v1/sessions", {
452
+ namespaceSlug: this.namespace,
453
+ databaseSlug: this.slug,
454
+ userId: opts.userId,
455
+ email: opts.email,
456
+ expiresIn: opts.expiresIn
457
+ });
458
+ }
362
459
  /**
363
460
  * Engine telemetry — recent /v1/query and /v1/batch calls issued
364
461
  * against this database. Backed by the in-DO `_persql_meta_query_log`
@@ -480,6 +577,61 @@ var PerSQLDatabase = class {
480
577
  if (this.client.local) return this.client.local.explain(sql, params);
481
578
  return this.client.request("POST", `/v1/db/${this.namespace}/${this.slug}/explain`, { sql, params });
482
579
  }
580
+ /**
581
+ * Parse + bind-check a SQL statement without executing it. Faster
582
+ * and cheaper than running the query just to catch unknown columns
583
+ * or wrong param counts.
584
+ *
585
+ * Returns `{ ok: true }` on success or `{ ok: false, error, errorDetail }`
586
+ * with the same `errorDetail` envelope as `PerSQLError.detail`.
587
+ */
588
+ async validate(sql, params = []) {
589
+ if (this.client.local) {
590
+ throw new Error("PerSQL: validate is not available in local mode.");
591
+ }
592
+ const data = await this.client.request(
593
+ "POST",
594
+ `/v1/db/${this.namespace}/${this.slug}/validate`,
595
+ { sql, params }
596
+ );
597
+ return data;
598
+ }
599
+ /**
600
+ * Compact text rendering of the database's schema — one line per
601
+ * table with columns, types, PKs, and FK arrows inlined. Designed
602
+ * to stuff into an LLM prompt with minimum token overhead. See
603
+ * `db.describe()` for the structured form.
604
+ */
605
+ async describeCompact() {
606
+ if (this.client.local) {
607
+ throw new Error("PerSQL: describeCompact is not available in local mode.");
608
+ }
609
+ const data = await this.client.request(
610
+ "GET",
611
+ `/v1/db/${this.namespace}/${this.slug}/describe?format=compact`
612
+ );
613
+ return data.text;
614
+ }
615
+ /**
616
+ * First N rows of a table plus per-column stats (null count,
617
+ * distinct count, min, max). One call to size up an unfamiliar
618
+ * table — replaces a hand-rolled `SELECT *`, `COUNT(*)`, and
619
+ * `PRAGMA table_info` sequence.
620
+ *
621
+ * `n` defaults to 10 and is capped at 100.
622
+ */
623
+ async sampleTable(table, opts = {}) {
624
+ if (this.client.local) {
625
+ throw new Error("PerSQL: sampleTable is not available in local mode.");
626
+ }
627
+ const qs = opts.n ? `?n=${Math.max(1, Math.min(100, opts.n | 0))}` : "";
628
+ return this.client.request(
629
+ "GET",
630
+ `/v1/db/${this.namespace}/${this.slug}/tables/${encodeURIComponent(
631
+ table
632
+ )}/sample${qs}`
633
+ );
634
+ }
483
635
  /**
484
636
  * Introspect the database schema. Returns one entry per user table
485
637
  * with column definitions, suitable for codegen tools (the
@@ -503,20 +655,6 @@ var PerSQLDatabase = class {
503
655
  }
504
656
  return out;
505
657
  }
506
- /**
507
- * Per-database semantic search via Cloudflare Vectorize. Use
508
- * `vectors.upsert` to embed and store rows; `vectors.query` to
509
- * retrieve the most similar by free text. Embeddings are computed
510
- * server-side using bge-base-en-v1.5 (768 dim, cosine distance).
511
- */
512
- get vectors() {
513
- if (this.client.local) {
514
- throw new Error(
515
- "PerSQL: vectors require Cloudflare Vectorize and Workers AI \u2014 not available in local mode. Use a server-mode token (psql_live_/psql_test_) to call vectors.upsert/query/delete."
516
- );
517
- }
518
- return new PerSQLVectors(this.client, this.namespace, this.slug);
519
- }
520
658
  /**
521
659
  * Manage preview/PR-style branches of this database. Each branch is
522
660
  * its own DO with its own SQLite file; create-or-reset by ref is
@@ -545,6 +683,19 @@ var PerSQLDatabase = class {
545
683
  }
546
684
  return new PerSQLApprovals(this.client, this.namespace, this.slug);
547
685
  }
686
+ /**
687
+ * Manage the require_approval / deny rules for this database. Reads
688
+ * are open to any bearer with read access; create + delete require
689
+ * an admin-role bearer token.
690
+ */
691
+ get approvalRules() {
692
+ if (this.client.local) {
693
+ throw new Error(
694
+ "PerSQL: approval rules live on the server; local mode does not enforce them."
695
+ );
696
+ }
697
+ return new PerSQLApprovalRules(this.client, this.namespace, this.slug);
698
+ }
548
699
  /**
549
700
  * Pre-flight a write before running it. `propose()` validates the
550
701
  * SQL via EXPLAIN, estimates affected rows, and returns a single-use
@@ -556,20 +707,6 @@ var PerSQLDatabase = class {
556
707
  get proposals() {
557
708
  return new PerSQLProposals(this.client, this.namespace, this.slug);
558
709
  }
559
- /**
560
- * Per-database BLOB storage backed by R2. Use this for anything
561
- * larger than a SQLite cell (images, PDFs, model weights). Each
562
- * database has its own private namespace; keys may be hierarchical
563
- * (`avatars/2025/foo.jpg`) but never start with `/`.
564
- */
565
- get blob() {
566
- if (this.client.local) {
567
- throw new Error(
568
- "PerSQL: blob storage is backed by R2 \u2014 not available in local mode. Use a server-mode token, or store blobs in your test fixtures directly."
569
- );
570
- }
571
- return new PerSQLBlob(this.client, this.namespace, this.slug);
572
- }
573
710
  /**
574
711
  * Subscribe to row-changes via WebSocket — the SQL equivalent of
575
712
  * Postgres `LISTEN`. The callback fires once per write that
@@ -657,42 +794,61 @@ var PerSQLDatabase = class {
657
794
  return callback;
658
795
  }
659
796
  /**
660
- * Returns a tool definition for use with Anthropic / OpenAI / function-calling
661
- * agents. Pair it with `runTool` to execute the call.
797
+ * Returns a single SQL-query tool definition in every shape PerSQL
798
+ * supports (Anthropic, OpenAI Chat, Vercel AI SDK, Mastra, LangChain,
799
+ * OpenAI Agents SDK). Same input contract as `asTools()` — the only
800
+ * difference is one fat `query` tool instead of typed-per-table tools.
801
+ *
802
+ * Pair the format-specific fields with `runTool` (or use the bundled
803
+ * `execute`/`invoke` callbacks, which call `runTool` internally).
662
804
  */
663
805
  asTool(name = "persql_query") {
664
- return {
665
- anthropic: {
666
- name,
667
- description: `Run a SQL query against the PerSQL database "${this.namespace}/${this.slug}". Returns up to 1000 rows. Use parameter binding (?) to avoid injection.`,
668
- input_schema: {
669
- type: "object",
670
- properties: {
671
- sql: { type: "string", description: "SQLite SQL statement" },
672
- params: {
673
- type: "array",
674
- items: {},
675
- description: "Positional parameters for the SQL statement"
676
- }
677
- },
678
- required: ["sql"]
806
+ const description = `Run a SQL query against the PerSQL database "${this.namespace}/${this.slug}". Returns up to 1000 rows. Use parameter binding (?) to avoid injection.`;
807
+ const inputSchema = {
808
+ type: "object",
809
+ properties: {
810
+ sql: { type: "string", description: "SQLite SQL statement" },
811
+ params: {
812
+ type: "array",
813
+ items: {},
814
+ description: "Positional parameters for the SQL statement"
679
815
  }
680
816
  },
817
+ required: ["sql"]
818
+ };
819
+ const execute = (input) => this.runTool({
820
+ sql: String(input.sql ?? ""),
821
+ params: Array.isArray(input.params) ? input.params : []
822
+ });
823
+ return {
824
+ anthropic: { name, description, input_schema: inputSchema },
681
825
  openai: {
682
826
  type: "function",
683
- function: {
827
+ function: { name, description, parameters: inputSchema }
828
+ },
829
+ aiSdk: () => ({
830
+ [name]: { description, inputSchema, execute }
831
+ }),
832
+ mastra: () => ({
833
+ [name]: {
834
+ id: name,
835
+ description,
836
+ inputSchema,
837
+ execute: ({ context }) => execute(context)
838
+ }
839
+ }),
840
+ langchain: () => [
841
+ { name, description, schema: inputSchema, invoke: execute }
842
+ ],
843
+ openaiAgents: () => [
844
+ {
845
+ type: "function",
684
846
  name,
685
- description: `Run a SQL query against the PerSQL database "${this.namespace}/${this.slug}".`,
686
- parameters: {
687
- type: "object",
688
- properties: {
689
- sql: { type: "string" },
690
- params: { type: "array", items: {} }
691
- },
692
- required: ["sql"]
693
- }
847
+ description,
848
+ parameters: inputSchema,
849
+ invoke: execute
694
850
  }
695
- }
851
+ ]
696
852
  };
697
853
  }
698
854
  /**
@@ -1464,106 +1620,148 @@ function sanitizeToolPart(s) {
1464
1620
  function quoteIdent(s) {
1465
1621
  return `"${s.replace(/"/g, '""')}"`;
1466
1622
  }
1467
- var PerSQLBlob = class {
1623
+ var PerSQLApprovals = class {
1468
1624
  constructor(client, namespace, slug) {
1469
1625
  this.client = client;
1470
1626
  this.namespace = namespace;
1471
1627
  this.slug = slug;
1472
1628
  }
1473
- /** Upload a blob. Body can be ArrayBuffer, Blob, ReadableStream, or string. */
1474
- async put(key, body, opts = {}) {
1475
- const path = `/v1/db/${this.namespace}/${this.slug}/blobs/${encodeBlobKey(key)}`;
1476
- return this.client.requestRaw(
1477
- "PUT",
1478
- path,
1479
- body,
1480
- opts.contentType
1629
+ // Tokens this client has seen via subscribe used to dedupe poll
1630
+ // results so onApprovalRequired fires once per token regardless of
1631
+ // how many subscribe ticks observe it.
1632
+ seen = /* @__PURE__ */ new Map();
1633
+ /**
1634
+ * Look up the status of an approval token without consuming it. The
1635
+ * bearer must match the one that minted the original 403; cross-bearer
1636
+ * lookup returns 403.
1637
+ */
1638
+ async get(approvalToken) {
1639
+ return this.client.request(
1640
+ "GET",
1641
+ `/v1/db/${this.namespace}/${this.slug}/approval/${encodeURIComponent(
1642
+ approvalToken
1643
+ )}`
1481
1644
  );
1482
1645
  }
1483
- /** Fetch a blob. Returns null if missing. */
1484
- async get(key) {
1485
- const path = `/v1/db/${this.namespace}/${this.slug}/blobs/${encodeBlobKey(key)}`;
1486
- const res = await this.client.fetchRaw("GET", path);
1487
- if (res.status === 404) return null;
1488
- if (!res.ok) {
1489
- throw new PerSQLError(res.status, `Blob fetch failed (${res.status})`);
1646
+ /**
1647
+ * Poll until a reviewer decides (or the token expires). Resolves with
1648
+ * the final status — `approved` is your green light for `redeem()`.
1649
+ */
1650
+ async poll(approvalToken, opts = {}) {
1651
+ const intervalMs = Math.max(250, opts.intervalMs ?? 2e3);
1652
+ const deadline = Date.now() + (opts.timeoutMs ?? 10 * 60 * 1e3);
1653
+ while (true) {
1654
+ if (opts.signal?.aborted) {
1655
+ throw new Error("approval poll aborted");
1656
+ }
1657
+ const status = await this.get(approvalToken);
1658
+ if (status.status !== "pending") return status;
1659
+ if (Date.now() > deadline) return status;
1660
+ await new Promise((r) => setTimeout(r, intervalMs));
1490
1661
  }
1491
- return res;
1492
1662
  }
1493
- /** Delete a blob. */
1494
- async delete(key) {
1495
- const path = `/v1/db/${this.namespace}/${this.slug}/blobs/${encodeBlobKey(key)}`;
1496
- await this.client.request("DELETE", path);
1663
+ /**
1664
+ * Long-running poll subscription for new approval events on this
1665
+ * database. Returns a `stop()` handle. Webhook delivery is the push
1666
+ * path (events `approval_required` / `approval_resolved`); subscribe
1667
+ * is the pull fallback for clients that can't host a webhook.
1668
+ *
1669
+ * Today this iterates over a known set of tokens supplied by the
1670
+ * caller; an SDK-only client that wants discovery should pair this
1671
+ * with the webhook path. Pass tokens you already hold (e.g. from a
1672
+ * prior `ApprovalRequiredError`).
1673
+ */
1674
+ subscribe(tokens, opts) {
1675
+ const intervalMs = Math.max(1e3, opts.intervalMs ?? 5e3);
1676
+ let cancelled = false;
1677
+ const onAbort = () => {
1678
+ cancelled = true;
1679
+ };
1680
+ opts.signal?.addEventListener("abort", onAbort);
1681
+ const tick = async () => {
1682
+ for (const token of tokens) {
1683
+ if (cancelled) return;
1684
+ try {
1685
+ const status = await this.get(token);
1686
+ const prev = this.seen.get(token);
1687
+ if (status.status !== prev) {
1688
+ this.seen.set(token, status.status);
1689
+ const event = {
1690
+ approvalToken: token,
1691
+ status: status.status,
1692
+ hits: status.hits
1693
+ };
1694
+ if (status.status === "pending") {
1695
+ opts.onApprovalRequired?.(event);
1696
+ } else {
1697
+ opts.onApprovalResolved?.(event);
1698
+ }
1699
+ }
1700
+ } catch (e) {
1701
+ opts.onError?.(e instanceof Error ? e : new Error(String(e)));
1702
+ }
1703
+ }
1704
+ };
1705
+ const id = setInterval(() => {
1706
+ void tick();
1707
+ }, intervalMs);
1708
+ void tick();
1709
+ return {
1710
+ stop: () => {
1711
+ cancelled = true;
1712
+ clearInterval(id);
1713
+ opts.signal?.removeEventListener("abort", onAbort);
1714
+ }
1715
+ };
1497
1716
  }
1498
- /** List blobs by prefix. */
1499
- async list(opts = {}) {
1500
- const params = new URLSearchParams();
1501
- if (opts.prefix) params.set("prefix", opts.prefix);
1502
- if (opts.cursor) params.set("cursor", opts.cursor);
1503
- if (opts.limit) params.set("limit", String(opts.limit));
1504
- const qs = params.toString();
1505
- const path = `/v1/db/${this.namespace}/${this.slug}/blobs` + (qs ? `?${qs}` : "");
1506
- return this.client.request("GET", path);
1717
+ /**
1718
+ * Redeem an approved approvalToken. The bearer must be the same one
1719
+ * that minted it, and the request must target the same database.
1720
+ * Returns the result of the originally-blocked query or batch.
1721
+ */
1722
+ async redeem(approvalToken) {
1723
+ const raw = await this.client.request("POST", `/v1/db/${this.namespace}/${this.slug}/redeem_approval`, {
1724
+ approvalToken
1725
+ });
1726
+ if (Array.isArray(raw)) {
1727
+ return raw.map((r) => ({
1728
+ ...r,
1729
+ data: rowsToObjects(r.columns, r.rows)
1730
+ }));
1731
+ }
1732
+ return { ...raw, data: rowsToObjects(raw.columns, raw.rows) };
1507
1733
  }
1508
1734
  };
1509
- function encodeBlobKey(key) {
1510
- return key.split("/").map(encodeURIComponent).join("/");
1511
- }
1512
- var PerSQLVectors = class {
1735
+ var PerSQLApprovalRules = class {
1513
1736
  constructor(client, namespace, slug) {
1514
1737
  this.client = client;
1515
1738
  this.namespace = namespace;
1516
1739
  this.slug = slug;
1517
1740
  }
1518
- /** Embed and upsert up to 100 items in one call. */
1519
- async upsert(items) {
1741
+ async list() {
1520
1742
  return this.client.request(
1521
- "POST",
1522
- `/v1/db/${this.namespace}/${this.slug}/vectors`,
1523
- { items }
1743
+ "GET",
1744
+ `/v1/db/${this.namespace}/${this.slug}/approval-rules`
1524
1745
  );
1525
1746
  }
1526
- /** Top-K nearest neighbours by free-text query. */
1527
- async query(text, opts = {}) {
1747
+ /** Requires an admin-role bearer token. */
1748
+ async create(input) {
1528
1749
  return this.client.request(
1529
1750
  "POST",
1530
- `/v1/db/${this.namespace}/${this.slug}/vectors/query`,
1531
- { query: text, topK: opts.topK, filter: opts.filter }
1751
+ `/v1/db/${this.namespace}/${this.slug}/approval-rules`,
1752
+ input
1532
1753
  );
1533
1754
  }
1534
- /** Delete vectors by id. Up to 1000 ids per call. */
1535
- async delete(ids) {
1755
+ /** Requires an admin-role bearer token. */
1756
+ async delete(ruleId) {
1536
1757
  return this.client.request(
1537
1758
  "DELETE",
1538
- `/v1/db/${this.namespace}/${this.slug}/vectors`,
1539
- { ids }
1759
+ `/v1/db/${this.namespace}/${this.slug}/approval-rules/${encodeURIComponent(
1760
+ ruleId
1761
+ )}`
1540
1762
  );
1541
1763
  }
1542
1764
  };
1543
- var PerSQLApprovals = class {
1544
- constructor(client, namespace, slug) {
1545
- this.client = client;
1546
- this.namespace = namespace;
1547
- this.slug = slug;
1548
- }
1549
- /**
1550
- * Redeem an approved approvalToken. The bearer must be the same one
1551
- * that minted it, and the request must target the same database.
1552
- * Returns the result of the originally-blocked query or batch.
1553
- */
1554
- async redeem(approvalToken) {
1555
- const raw = await this.client.request("POST", `/v1/db/${this.namespace}/${this.slug}/redeem_approval`, {
1556
- approvalToken
1557
- });
1558
- if (Array.isArray(raw)) {
1559
- return raw.map((r) => ({
1560
- ...r,
1561
- data: rowsToObjects(r.columns, r.rows)
1562
- }));
1563
- }
1564
- return { ...raw, data: rowsToObjects(raw.columns, raw.rows) };
1565
- }
1566
- };
1567
1765
  var PerSQLProposals = class {
1568
1766
  constructor(client, namespace, slug) {
1569
1767
  this.client = client;
@@ -1727,11 +1925,11 @@ export {
1727
1925
  RateLimitError,
1728
1926
  ApprovalRequiredError,
1729
1927
  PerSQL,
1928
+ SupportClient,
1730
1929
  PerSQLDatabase,
1731
- PerSQLBlob,
1732
- PerSQLVectors,
1733
1930
  PerSQLApprovals,
1931
+ PerSQLApprovalRules,
1734
1932
  PerSQLProposals,
1735
1933
  PerSQLBranches
1736
1934
  };
1737
- //# sourceMappingURL=chunk-CDNTQOBK.js.map
1935
+ //# sourceMappingURL=chunk-ZRZANHPI.js.map