@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/dist/index.d.cts CHANGED
@@ -71,8 +71,8 @@ interface PerSQLOptions {
71
71
  * Run against a local SQLite file (via the `better-sqlite3` peer
72
72
  * dependency) instead of the HTTP API. Pass `":memory:"` for an
73
73
  * in-memory test database. When set, `token` is not required and
74
- * network features (`vectors`, `blob`, `subscribe`) throw —
75
- * they have no local equivalent.
74
+ * network features (`subscribe`) throw — they have no local
75
+ * equivalent.
76
76
  *
77
77
  * ```ts
78
78
  * const persql = new PerSQL({ local: ":memory:" });
@@ -199,10 +199,73 @@ interface TableInfo {
199
199
  name: string;
200
200
  rowCount: number;
201
201
  }
202
+ /**
203
+ * Per-statement assertion attached to a `batch` entry. Evaluated
204
+ * server-side inside the same transactionSync as the statement —
205
+ * a violation throws, which rolls a transactional batch back. For
206
+ * non-transactional batches, the rest of the batch does not run.
207
+ *
208
+ * Use this to write defensive multi-step writes without a follow-up
209
+ * read: `UPDATE … WHERE id = ?` with `expect: { rowsWritten: 1 }`
210
+ * fails fast if zero rows match.
211
+ *
212
+ * Shape mirrors `@persql/shared`'s `BatchExpect`. Inlined here so the
213
+ * published npm package has no monorepo-only deps.
214
+ */
215
+ interface BatchExpect {
216
+ /** Result has exactly this many rows. */
217
+ rows?: number;
218
+ rowsAtLeast?: number;
219
+ rowsAtMost?: number;
220
+ /** Statement wrote exactly this many rows. */
221
+ rowsWritten?: number;
222
+ rowsWrittenAtLeast?: number;
223
+ rowsWrittenAtMost?: number;
224
+ }
202
225
  interface Statement {
203
226
  sql: string;
204
227
  params?: unknown[];
228
+ expect?: BatchExpect;
229
+ }
230
+ /**
231
+ * Returned by `client.me()` — the bearer token's namespace, scope, and
232
+ * current prepaid balance. Agents call this to learn what they're
233
+ * allowed to touch and how much credit remains before the gate returns
234
+ * 402.
235
+ */
236
+ interface WhoamiInfo {
237
+ namespaceId: string;
238
+ namespaceSlug: string;
239
+ tokenId: string;
240
+ role: "admin" | "readwrite" | "readonly";
241
+ scopes: unknown[];
242
+ tableScope: string[] | null;
243
+ balanceUsdMicros: number | null;
244
+ }
245
+ /** Output of `db.sampleTable(...)`. */
246
+ interface TableSample {
247
+ table: string;
248
+ rowCount: number;
249
+ rows: Array<Record<string, unknown>>;
250
+ columns: Array<{
251
+ name: string;
252
+ type: string;
253
+ pk: boolean;
254
+ notNull: boolean;
255
+ nullCount: number;
256
+ distinctCount: number;
257
+ min: unknown;
258
+ max: unknown;
259
+ }>;
205
260
  }
261
+ /** Output of `db.validate(...)`. */
262
+ type ValidateResult = {
263
+ ok: true;
264
+ } | {
265
+ ok: false;
266
+ error: string;
267
+ errorDetail?: SqlErrorDetail;
268
+ };
206
269
  declare class PerSQLError extends Error {
207
270
  status: number;
208
271
  detail?: SqlErrorDetail;
@@ -249,6 +312,14 @@ declare class PerSQL {
249
312
  constructor(opts: PerSQLOptions);
250
313
  /** Close the local SQLite connection (no-op in HTTP mode). */
251
314
  close(): void;
315
+ /**
316
+ * Resolve the bearer token's namespace + live usage. Use this once
317
+ * per agent run so you know the slug to put in URLs and how much
318
+ * monthly budget / prepaid balance remains before a 402.
319
+ *
320
+ * Throws in local mode — no server, nothing to ask.
321
+ */
322
+ me(): Promise<WhoamiInfo>;
252
323
  /**
253
324
  * Redeem a handoff token (`phand_…`) for a regular PerSQL client
254
325
  * scoped to the handed-off (database, branch, role). Single use:
@@ -275,10 +346,42 @@ declare class PerSQL {
275
346
  database(namespace: string, slug: string): PerSQLDatabase;
276
347
  /** @internal */
277
348
  request<T>(method: "GET" | "POST" | "PUT" | "DELETE", path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
278
- /** @internal — raw upload (e.g. blob PUT) returning the ApiResponse data. */
279
- requestRaw<T>(method: "PUT" | "POST", path: string, body: ArrayBuffer | Blob | ReadableStream | string, contentType?: string): Promise<T>;
280
- /** @internal — raw fetch returning the underlying Response (used by blob GET). */
349
+ /** @internal — raw fetch returning the underlying Response. */
281
350
  fetchRaw(method: "GET", path: string): Promise<Response>;
351
+ /**
352
+ * File a support ticket directly from your SDK code. Tickets land in
353
+ * the customer console at `/support`; staff reply from the admin
354
+ * app. Useful for agents and background jobs that hit a PerSQL
355
+ * error and want a human to see it.
356
+ *
357
+ * ```ts
358
+ * try {
359
+ * await db.query("UPDATE ...");
360
+ * } catch (err) {
361
+ * await persql.support.createTicket({
362
+ * subject: "Branch merge keeps 500ing",
363
+ * body: "Repro: ...",
364
+ * error: err,
365
+ * });
366
+ * }
367
+ * ```
368
+ */
369
+ get support(): SupportClient;
370
+ }
371
+ interface CreateSupportTicketOptions {
372
+ subject: string;
373
+ body: string;
374
+ /** Optional error to attach for triage (PerSQLError or any throwable). */
375
+ error?: unknown;
376
+ /** Caller-supplied extras merged into the ticket's diagnostic context. */
377
+ errorContext?: Record<string, unknown>;
378
+ }
379
+ declare class SupportClient {
380
+ private readonly client;
381
+ constructor(client: PerSQL);
382
+ createTicket(opts: CreateSupportTicketOptions): Promise<{
383
+ id: string;
384
+ }>;
282
385
  }
283
386
  declare class PerSQLDatabase {
284
387
  private readonly client;
@@ -291,6 +394,22 @@ declare class PerSQLDatabase {
291
394
  batch<T = Record<string, unknown>>(statements: Statement[], options?: BatchOptions): Promise<QueryResult<T>[]>;
292
395
  /** Convenience for a transactional batch. */
293
396
  transaction<T = Record<string, unknown>>(statements: Statement[], options?: Omit<BatchOptions, "transaction">): Promise<QueryResult<T>[]>;
397
+ /**
398
+ * Mint a short-lived session JWT for an end user. The server (which
399
+ * holds the bearer token) calls this and hands the resulting token
400
+ * to an untrusted client (browser, mobile app, agent run). The
401
+ * client uses the JWT to call this database's published `/p/*`
402
+ * endpoints on behalf of `userId`. TTL defaults to 1 hour; max 24h.
403
+ */
404
+ createSession(opts: {
405
+ userId: string;
406
+ email?: string;
407
+ expiresIn?: number;
408
+ }): Promise<{
409
+ token: string;
410
+ expiresAt: string;
411
+ expiresIn: number;
412
+ }>;
294
413
  /**
295
414
  * Engine telemetry — recent /v1/query and /v1/batch calls issued
296
415
  * against this database. Backed by the in-DO `_persql_meta_query_log`
@@ -361,19 +480,39 @@ declare class PerSQLDatabase {
361
480
  parent: number;
362
481
  detail: string;
363
482
  }>>;
483
+ /**
484
+ * Parse + bind-check a SQL statement without executing it. Faster
485
+ * and cheaper than running the query just to catch unknown columns
486
+ * or wrong param counts.
487
+ *
488
+ * Returns `{ ok: true }` on success or `{ ok: false, error, errorDetail }`
489
+ * with the same `errorDetail` envelope as `PerSQLError.detail`.
490
+ */
491
+ validate(sql: string, params?: unknown[]): Promise<ValidateResult>;
492
+ /**
493
+ * Compact text rendering of the database's schema — one line per
494
+ * table with columns, types, PKs, and FK arrows inlined. Designed
495
+ * to stuff into an LLM prompt with minimum token overhead. See
496
+ * `db.describe()` for the structured form.
497
+ */
498
+ describeCompact(): Promise<string>;
499
+ /**
500
+ * First N rows of a table plus per-column stats (null count,
501
+ * distinct count, min, max). One call to size up an unfamiliar
502
+ * table — replaces a hand-rolled `SELECT *`, `COUNT(*)`, and
503
+ * `PRAGMA table_info` sequence.
504
+ *
505
+ * `n` defaults to 10 and is capped at 100.
506
+ */
507
+ sampleTable(table: string, opts?: {
508
+ n?: number;
509
+ }): Promise<TableSample>;
364
510
  /**
365
511
  * Introspect the database schema. Returns one entry per user table
366
512
  * with column definitions, suitable for codegen tools (the
367
513
  * `persql-codegen` CLI uses this to emit Drizzle schema files).
368
514
  */
369
515
  schema(): Promise<DatabaseSchema>;
370
- /**
371
- * Per-database semantic search via Cloudflare Vectorize. Use
372
- * `vectors.upsert` to embed and store rows; `vectors.query` to
373
- * retrieve the most similar by free text. Embeddings are computed
374
- * server-side using bge-base-en-v1.5 (768 dim, cosine distance).
375
- */
376
- get vectors(): PerSQLVectors;
377
516
  /**
378
517
  * Manage preview/PR-style branches of this database. Each branch is
379
518
  * its own DO with its own SQLite file; create-or-reset by ref is
@@ -388,6 +527,12 @@ declare class PerSQLDatabase {
388
527
  * namespace member decides; on approve, runs the original SQL.
389
528
  */
390
529
  get approvals(): PerSQLApprovals;
530
+ /**
531
+ * Manage the require_approval / deny rules for this database. Reads
532
+ * are open to any bearer with read access; create + delete require
533
+ * an admin-role bearer token.
534
+ */
535
+ get approvalRules(): PerSQLApprovalRules;
391
536
  /**
392
537
  * Pre-flight a write before running it. `propose()` validates the
393
538
  * SQL via EXPLAIN, estimates affected rows, and returns a single-use
@@ -397,13 +542,6 @@ declare class PerSQLDatabase {
397
542
  * executionToken in-process.
398
543
  */
399
544
  get proposals(): PerSQLProposals;
400
- /**
401
- * Per-database BLOB storage backed by R2. Use this for anything
402
- * larger than a SQLite cell (images, PDFs, model weights). Each
403
- * database has its own private namespace; keys may be hierarchical
404
- * (`avatars/2025/foo.jpg`) but never start with `/`.
405
- */
406
- get blob(): PerSQLBlob;
407
545
  /**
408
546
  * Subscribe to row-changes via WebSocket — the SQL equivalent of
409
547
  * Postgres `LISTEN`. The callback fires once per write that
@@ -444,50 +582,15 @@ declare class PerSQLDatabase {
444
582
  */
445
583
  driver(): SqliteProxyDriver;
446
584
  /**
447
- * Returns a tool definition for use with Anthropic / OpenAI / function-calling
448
- * agents. Pair it with `runTool` to execute the call.
585
+ * Returns a single SQL-query tool definition in every shape PerSQL
586
+ * supports (Anthropic, OpenAI Chat, Vercel AI SDK, Mastra, LangChain,
587
+ * OpenAI Agents SDK). Same input contract as `asTools()` — the only
588
+ * difference is one fat `query` tool instead of typed-per-table tools.
589
+ *
590
+ * Pair the format-specific fields with `runTool` (or use the bundled
591
+ * `execute`/`invoke` callbacks, which call `runTool` internally).
449
592
  */
450
- asTool(name?: string): {
451
- anthropic: {
452
- name: string;
453
- description: string;
454
- input_schema: {
455
- type: string;
456
- properties: {
457
- sql: {
458
- type: string;
459
- description: string;
460
- };
461
- params: {
462
- type: string;
463
- items: {};
464
- description: string;
465
- };
466
- };
467
- required: string[];
468
- };
469
- };
470
- openai: {
471
- type: "function";
472
- function: {
473
- name: string;
474
- description: string;
475
- parameters: {
476
- type: string;
477
- properties: {
478
- sql: {
479
- type: string;
480
- };
481
- params: {
482
- type: string;
483
- items: {};
484
- };
485
- };
486
- required: string[];
487
- };
488
- };
489
- };
490
- };
593
+ asTool(name?: string): SingleToolBundle;
491
594
  /**
492
595
  * Executes a tool-call payload returned from the model. Pair with `asTool`.
493
596
  */
@@ -539,6 +642,20 @@ interface OpenAiTool {
539
642
  parameters: Record<string, unknown>;
540
643
  };
541
644
  }
645
+ interface SingleToolBundle {
646
+ /** Anthropic shape — pass as one entry of `tools` to messages.create. */
647
+ anthropic: AnthropicTool;
648
+ /** OpenAI Chat shape — pass as one entry of `tools` to chat.completions.create. */
649
+ openai: OpenAiTool;
650
+ /** Vercel AI SDK shape — spread into `streamText({ tools: { ...db.asTool().aiSdk() } })`. */
651
+ aiSdk: () => Record<string, AiSdkTool>;
652
+ /** Mastra shape — wrap with `createTool()` if you need the Mastra envelope. */
653
+ mastra: () => Record<string, MastraTool>;
654
+ /** LangChain/LangGraph shape — pass to `DynamicStructuredTool` or use `invoke` directly. */
655
+ langchain: () => LangChainTool[];
656
+ /** OpenAI Agents SDK shape — hand to `Agent({ tools: [...] })`. */
657
+ openaiAgents: () => OpenAiAgentsTool[];
658
+ }
542
659
  interface DatabaseToolBundle {
543
660
  /** Tool definitions in Anthropic shape — pass as `tools` to messages.create. */
544
661
  anthropic: AnthropicTool[];
@@ -622,71 +739,6 @@ interface SubscribeOptions {
622
739
  onError?: (error: Error) => void;
623
740
  onClose?: () => void;
624
741
  }
625
- interface BlobListItem {
626
- key: string;
627
- sizeBytes: number;
628
- etag: string;
629
- uploadedAt: string;
630
- contentType?: string;
631
- }
632
- interface BlobListResponse {
633
- items: BlobListItem[];
634
- truncated: boolean;
635
- cursor?: string;
636
- }
637
- declare class PerSQLBlob {
638
- private readonly client;
639
- private readonly namespace;
640
- private readonly slug;
641
- constructor(client: PerSQL, namespace: string, slug: string);
642
- /** Upload a blob. Body can be ArrayBuffer, Blob, ReadableStream, or string. */
643
- put(key: string, body: ArrayBuffer | Blob | ReadableStream | string, opts?: {
644
- contentType?: string;
645
- }): Promise<{
646
- key: string;
647
- sizeBytes: number;
648
- etag: string;
649
- }>;
650
- /** Fetch a blob. Returns null if missing. */
651
- get(key: string): Promise<Response | null>;
652
- /** Delete a blob. */
653
- delete(key: string): Promise<void>;
654
- /** List blobs by prefix. */
655
- list(opts?: {
656
- prefix?: string;
657
- cursor?: string;
658
- limit?: number;
659
- }): Promise<BlobListResponse>;
660
- }
661
- interface VectorUpsertItem {
662
- id: string;
663
- text: string;
664
- metadata?: Record<string, string | number | boolean>;
665
- }
666
- interface VectorMatch {
667
- id: string;
668
- score: number;
669
- metadata: Record<string, unknown>;
670
- }
671
- declare class PerSQLVectors {
672
- private readonly client;
673
- private readonly namespace;
674
- private readonly slug;
675
- constructor(client: PerSQL, namespace: string, slug: string);
676
- /** Embed and upsert up to 100 items in one call. */
677
- upsert(items: VectorUpsertItem[]): Promise<{
678
- count: number;
679
- }>;
680
- /** Top-K nearest neighbours by free-text query. */
681
- query(text: string, opts?: {
682
- topK?: number;
683
- filter?: Record<string, string | number | boolean>;
684
- }): Promise<VectorMatch[]>;
685
- /** Delete vectors by id. Up to 1000 ids per call. */
686
- delete(ids: string[]): Promise<{
687
- count: number;
688
- }>;
689
- }
690
742
  /**
691
743
  * Database row as returned by /v1 list/upsert branch responses.
692
744
  * Mirrors the `Database` DTO from @persql/shared but kept narrow on
@@ -770,11 +822,67 @@ interface ProposeOptions {
770
822
  params?: unknown[];
771
823
  ttlSec?: number;
772
824
  }
825
+ interface ApprovalStatus {
826
+ status: "pending" | "approved" | "denied";
827
+ hits: ApprovalRuleHit[];
828
+ createdAt: string;
829
+ expiresAt: string;
830
+ decidedAt: string | null;
831
+ }
832
+ interface PollApprovalOptions {
833
+ /** Default 2000ms. */
834
+ intervalMs?: number;
835
+ /** Default 10 minutes. */
836
+ timeoutMs?: number;
837
+ /** Cancellation signal. */
838
+ signal?: AbortSignal;
839
+ }
840
+ interface SubscribeApprovalOptions {
841
+ onApprovalRequired?: (event: ApprovalEvent) => void;
842
+ onApprovalResolved?: (event: ApprovalEvent) => void;
843
+ /** Poll interval. Default 5000ms. */
844
+ intervalMs?: number;
845
+ /** Cancellation signal. */
846
+ signal?: AbortSignal;
847
+ /** Logged-by-default error sink. */
848
+ onError?: (err: Error) => void;
849
+ }
850
+ interface ApprovalEvent {
851
+ approvalToken: string;
852
+ status: "pending" | "approved" | "denied";
853
+ hits: ApprovalRuleHit[];
854
+ }
773
855
  declare class PerSQLApprovals {
774
856
  private readonly client;
775
857
  private readonly namespace;
776
858
  private readonly slug;
859
+ private readonly seen;
777
860
  constructor(client: PerSQL, namespace: string, slug: string);
861
+ /**
862
+ * Look up the status of an approval token without consuming it. The
863
+ * bearer must match the one that minted the original 403; cross-bearer
864
+ * lookup returns 403.
865
+ */
866
+ get(approvalToken: string): Promise<ApprovalStatus>;
867
+ /**
868
+ * Poll until a reviewer decides (or the token expires). Resolves with
869
+ * the final status — `approved` is your green light for `redeem()`.
870
+ */
871
+ poll(approvalToken: string, opts?: PollApprovalOptions): Promise<ApprovalStatus>;
872
+ /**
873
+ * Long-running poll subscription for new approval events on this
874
+ * database. Returns a `stop()` handle. Webhook delivery is the push
875
+ * path (events `approval_required` / `approval_resolved`); subscribe
876
+ * is the pull fallback for clients that can't host a webhook.
877
+ *
878
+ * Today this iterates over a known set of tokens supplied by the
879
+ * caller; an SDK-only client that wants discovery should pair this
880
+ * with the webhook path. Pass tokens you already hold (e.g. from a
881
+ * prior `ApprovalRequiredError`).
882
+ */
883
+ subscribe(tokens: string[], opts: SubscribeApprovalOptions): {
884
+ stop: () => void;
885
+ };
778
886
  /**
779
887
  * Redeem an approved approvalToken. The bearer must be the same one
780
888
  * that minted it, and the request must target the same database.
@@ -782,6 +890,33 @@ declare class PerSQLApprovals {
782
890
  */
783
891
  redeem<T = Record<string, unknown>>(approvalToken: string): Promise<QueryResult<T> | QueryResult<T>[]>;
784
892
  }
893
+ interface ApprovalRule {
894
+ id: string;
895
+ databaseId: string;
896
+ tableGlob: string;
897
+ action: "require_approval" | "deny";
898
+ note: string | null;
899
+ createdById: string;
900
+ createdAt: string;
901
+ }
902
+ interface CreateApprovalRuleInput {
903
+ tableGlob: string;
904
+ action: "require_approval" | "deny";
905
+ note?: string;
906
+ }
907
+ declare class PerSQLApprovalRules {
908
+ private readonly client;
909
+ private readonly namespace;
910
+ private readonly slug;
911
+ constructor(client: PerSQL, namespace: string, slug: string);
912
+ list(): Promise<ApprovalRule[]>;
913
+ /** Requires an admin-role bearer token. */
914
+ create(input: CreateApprovalRuleInput): Promise<ApprovalRule>;
915
+ /** Requires an admin-role bearer token. */
916
+ delete(ruleId: string): Promise<{
917
+ deleted: true;
918
+ }>;
919
+ }
785
920
  declare class PerSQLProposals {
786
921
  private readonly client;
787
922
  private readonly namespace;
@@ -923,4 +1058,4 @@ type SqliteProxyDriver = (sql: string, params: unknown[], method: "all" | "run"
923
1058
  rows: unknown;
924
1059
  }>;
925
1060
 
926
- export { type AiSdkTool, type AnthropicTool, ApprovalRequiredError, type ApprovalRuleHit, type BatchOptions, type BlobListItem, type BlobListResponse, type BranchInfo, type BranchMergeMode, type BranchMergeOptions, type BranchMergePlanStep, type BranchMergeResult, type BranchUpsertOptions, type ClaimBranchOptions, type ClaimedBranch, type DatabaseSchema, type DatabaseToolBundle, type DescribeBundle, type HandoffClaim, type LangChainTool, type MastraTool, type OpenAiAgentsTool, type OpenAiTool, PerSQL, PerSQLApprovals, PerSQLBlob, PerSQLBranches, PerSQLDatabase, PerSQLError, type PerSQLOptions, PerSQLProposals, PerSQLVectors, type ProposalPlan, type ProposeOptions, type QueryLogEntry, type QueryOptions, type QueryResult, RateLimitError, type RawQueryResult, type SchemaDoctorReport, type SchemaSearchResponse, type SqlErrorDetail, type SqlErrorKind, type SqliteProxyDriver, type Statement, type SubscribeChange, type SubscribeChangeKind, type SubscribeOptions, type TableInfo, type VectorMatch, type VectorUpsertItem };
1061
+ export { type AiSdkTool, type AnthropicTool, type ApprovalEvent, ApprovalRequiredError, type ApprovalRule, type ApprovalRuleHit, type ApprovalStatus, type BatchExpect, type BatchOptions, type BranchInfo, type BranchMergeMode, type BranchMergeOptions, type BranchMergePlanStep, type BranchMergeResult, type BranchUpsertOptions, type ClaimBranchOptions, type ClaimedBranch, type CreateApprovalRuleInput, type CreateSupportTicketOptions, type DatabaseSchema, type DatabaseToolBundle, type DescribeBundle, type HandoffClaim, type LangChainTool, type MastraTool, type OpenAiAgentsTool, type OpenAiTool, PerSQL, PerSQLApprovalRules, PerSQLApprovals, PerSQLBranches, PerSQLDatabase, PerSQLError, type PerSQLOptions, PerSQLProposals, type PollApprovalOptions, type ProposalPlan, type ProposeOptions, type QueryLogEntry, type QueryOptions, type QueryResult, RateLimitError, type RawQueryResult, type SchemaDoctorReport, type SchemaSearchResponse, type SingleToolBundle, type SqlErrorDetail, type SqlErrorKind, type SqliteProxyDriver, type Statement, type SubscribeApprovalOptions, type SubscribeChange, type SubscribeChangeKind, type SubscribeOptions, SupportClient, type TableInfo, type TableSample, type ValidateResult, type WhoamiInfo };