@persql/sdk 1.0.0 → 1.2.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/{chunk-ZRZANHPI.js → chunk-GH75ERQ6.js} +118 -6
- package/dist/chunk-GH75ERQ6.js.map +1 -0
- package/dist/cli.cjs +116 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +118 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -1
- package/dist/index.d.ts +139 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/dist/chunk-ZRZANHPI.js.map +0 -1
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
PerSQLApprovals: () => PerSQLApprovals,
|
|
27
27
|
PerSQLBranches: () => PerSQLBranches,
|
|
28
28
|
PerSQLDatabase: () => PerSQLDatabase,
|
|
29
|
+
PerSQLDatabases: () => PerSQLDatabases,
|
|
29
30
|
PerSQLError: () => PerSQLError,
|
|
30
31
|
PerSQLProposals: () => PerSQLProposals,
|
|
31
32
|
RateLimitError: () => RateLimitError,
|
|
@@ -316,8 +317,23 @@ var PerSQL = class _PerSQL {
|
|
|
316
317
|
}
|
|
317
318
|
return new PerSQLDatabase(this, ns, slug);
|
|
318
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Provision and list databases in the token's namespace. `create`
|
|
322
|
+
* needs an unscoped admin token; `database()` then gives you a handle
|
|
323
|
+
* to query the result.
|
|
324
|
+
*/
|
|
325
|
+
get databases() {
|
|
326
|
+
return new PerSQLDatabases(this);
|
|
327
|
+
}
|
|
319
328
|
/** @internal */
|
|
320
329
|
async request(method, path, body, headers) {
|
|
330
|
+
return (await this.requestWithMeta(method, path, body, headers)).data;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* @internal — like `request`, but also surfaces the response `meta`
|
|
334
|
+
* envelope (usage + `costUsd`) that the data-only `request` drops.
|
|
335
|
+
*/
|
|
336
|
+
async requestWithMeta(method, path, body, headers) {
|
|
321
337
|
const res = await this._fetch(`${this.baseURL}${path}`, {
|
|
322
338
|
method,
|
|
323
339
|
headers: {
|
|
@@ -349,7 +365,7 @@ var PerSQL = class _PerSQL {
|
|
|
349
365
|
}
|
|
350
366
|
throw new PerSQLError(res.status, message, envelope?.errorDetail);
|
|
351
367
|
}
|
|
352
|
-
return envelope.data;
|
|
368
|
+
return { data: envelope.data, meta: envelope.meta };
|
|
353
369
|
}
|
|
354
370
|
/** @internal — raw fetch returning the underlying Response. */
|
|
355
371
|
fetchRaw(method, path) {
|
|
@@ -420,6 +436,44 @@ var SupportClient = class {
|
|
|
420
436
|
);
|
|
421
437
|
}
|
|
422
438
|
};
|
|
439
|
+
var PerSQLDatabases = class {
|
|
440
|
+
constructor(client) {
|
|
441
|
+
this.client = client;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Provision a new isolated database and return its metadata. Requires
|
|
445
|
+
* an unscoped admin token (scoped tokens can't create databases).
|
|
446
|
+
*/
|
|
447
|
+
async create(opts) {
|
|
448
|
+
if (this.client.local) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
"PerSQL: databases.create() needs a server-mode token. Pass a real token to `new PerSQL({ token })` to provision databases."
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
return this.client.request("POST", "/v1/databases", {
|
|
454
|
+
name: opts.name,
|
|
455
|
+
slug: opts.slug,
|
|
456
|
+
region: opts.region,
|
|
457
|
+
ttlDays: opts.ttlDays
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
/** List databases in the token's namespace, newest first (paginated). */
|
|
461
|
+
async list(opts = {}) {
|
|
462
|
+
if (this.client.local) {
|
|
463
|
+
throw new Error("PerSQL: databases.list() needs a server-mode token.");
|
|
464
|
+
}
|
|
465
|
+
const qs = new URLSearchParams();
|
|
466
|
+
if (opts.cursor) qs.set("cursor", opts.cursor);
|
|
467
|
+
if (opts.pageSize) qs.set("pageSize", String(opts.pageSize));
|
|
468
|
+
const tail = qs.toString() ? `?${qs.toString()}` : "";
|
|
469
|
+
const res = await this.client.fetchRaw("GET", `/v1/databases${tail}`);
|
|
470
|
+
const body = await res.json();
|
|
471
|
+
if (!res.ok || !body.success) {
|
|
472
|
+
throw new PerSQLError(res.status, body.error ?? `Request failed (${res.status})`);
|
|
473
|
+
}
|
|
474
|
+
return { data: body.data ?? [], nextCursor: body.nextCursor ?? null };
|
|
475
|
+
}
|
|
476
|
+
};
|
|
423
477
|
var PerSQLDatabase = class {
|
|
424
478
|
constructor(client, namespace, slug) {
|
|
425
479
|
this.client = client;
|
|
@@ -436,7 +490,7 @@ var PerSQLDatabase = class {
|
|
|
436
490
|
if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
437
491
|
if (options.planKey) headers["Plan-Key"] = options.planKey;
|
|
438
492
|
if (options.planStep) headers["Plan-Step"] = options.planStep;
|
|
439
|
-
const raw = await this.client.
|
|
493
|
+
const { data: raw, meta } = await this.client.requestWithMeta(
|
|
440
494
|
"POST",
|
|
441
495
|
`/v1/db/${this.namespace}/${this.slug}/query`,
|
|
442
496
|
{ sql, params },
|
|
@@ -444,7 +498,8 @@ var PerSQLDatabase = class {
|
|
|
444
498
|
);
|
|
445
499
|
return {
|
|
446
500
|
...raw,
|
|
447
|
-
data: rowsToObjects(raw.columns, raw.rows)
|
|
501
|
+
data: rowsToObjects(raw.columns, raw.rows),
|
|
502
|
+
meta
|
|
448
503
|
};
|
|
449
504
|
}
|
|
450
505
|
/** Run multiple statements in one round-trip, optionally in a transaction. */
|
|
@@ -802,6 +857,55 @@ var PerSQLDatabase = class {
|
|
|
802
857
|
}
|
|
803
858
|
};
|
|
804
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* Long-poll for row-changes on this database. Returns immediately
|
|
862
|
+
* if changes newer than `since` are already buffered; otherwise
|
|
863
|
+
* blocks server-side for up to `waitMs` (default 25s, max 25s).
|
|
864
|
+
*
|
|
865
|
+
* Most callers want `changes()` instead — an async iterator that
|
|
866
|
+
* loops `waitForChanges` forever, surfacing each batch as it
|
|
867
|
+
* arrives.
|
|
868
|
+
*/
|
|
869
|
+
async waitForChanges(opts = {}) {
|
|
870
|
+
if (this.client.local) {
|
|
871
|
+
throw new Error(
|
|
872
|
+
"PerSQL: waitForChanges requires the DO change ring \u2014 not available in local mode."
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
const qs = new URLSearchParams();
|
|
876
|
+
if (typeof opts.since === "number") qs.set("since", String(opts.since));
|
|
877
|
+
if (typeof opts.waitMs === "number") qs.set("waitMs", String(opts.waitMs));
|
|
878
|
+
if (opts.tables && opts.tables.length > 0) qs.set("tables", opts.tables.join(","));
|
|
879
|
+
const q = qs.toString();
|
|
880
|
+
return this.client.request(
|
|
881
|
+
"GET",
|
|
882
|
+
`/v1/db/${this.namespace}/${this.slug}/changes${q ? `?${q}` : ""}`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Async-iterator change feed. Loops `waitForChanges` forever and
|
|
887
|
+
* yields one batch per server response. Pass an `AbortSignal` to
|
|
888
|
+
* stop the loop cleanly.
|
|
889
|
+
*
|
|
890
|
+
* ```ts
|
|
891
|
+
* const ctl = new AbortController();
|
|
892
|
+
* for await (const batch of db.changes({ signal: ctl.signal })) {
|
|
893
|
+
* for (const c of batch.changes) console.log(c.kind, c.table);
|
|
894
|
+
* }
|
|
895
|
+
* ```
|
|
896
|
+
*/
|
|
897
|
+
async *changes(opts = {}) {
|
|
898
|
+
let cursor = opts.since ?? 0;
|
|
899
|
+
while (!opts.signal?.aborted) {
|
|
900
|
+
const batch = await this.waitForChanges({
|
|
901
|
+
since: cursor,
|
|
902
|
+
waitMs: opts.waitMs,
|
|
903
|
+
tables: opts.tables
|
|
904
|
+
});
|
|
905
|
+
cursor = batch.cursor;
|
|
906
|
+
yield batch;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
805
909
|
/**
|
|
806
910
|
* Returns the callback shape `drizzle-orm/sqlite-proxy` expects.
|
|
807
911
|
* Pair with `drizzle()` from that module to get a typed,
|
|
@@ -838,7 +942,7 @@ var PerSQLDatabase = class {
|
|
|
838
942
|
* `execute`/`invoke` callbacks, which call `runTool` internally).
|
|
839
943
|
*/
|
|
840
944
|
asTool(name = "persql_query") {
|
|
841
|
-
const description = `Run a SQL query against the PerSQL database "${this.namespace}/${this.slug}".
|
|
945
|
+
const description = `Run a SQL query against the PerSQL database "${this.namespace}/${this.slug}". A single result is capped at 100,000 rows and the query errors above that \u2014 add a LIMIT for large tables. Use parameter binding (?) to avoid injection.`;
|
|
842
946
|
const inputSchema = {
|
|
843
947
|
type: "object",
|
|
844
948
|
properties: {
|
|
@@ -849,7 +953,10 @@ var PerSQLDatabase = class {
|
|
|
849
953
|
description: "Positional parameters for the SQL statement"
|
|
850
954
|
}
|
|
851
955
|
},
|
|
852
|
-
required: ["sql"]
|
|
956
|
+
required: ["sql"],
|
|
957
|
+
// OpenAI strict function-calling rejects a schema without this; the
|
|
958
|
+
// per-table asTools() schemas already set it. Keep them consistent.
|
|
959
|
+
additionalProperties: false
|
|
853
960
|
};
|
|
854
961
|
const execute = (input) => this.runTool({
|
|
855
962
|
sql: String(input.sql ?? ""),
|
|
@@ -1808,6 +1915,11 @@ var PerSQLProposals = class {
|
|
|
1808
1915
|
* rows, and returns a single-use `executionToken` valid for 10 min
|
|
1809
1916
|
* (override with `ttlSec`, max 1 hour). Nothing is mutated until
|
|
1810
1917
|
* `apply()` is called.
|
|
1918
|
+
*
|
|
1919
|
+
* Local mode keeps the token in-process: single-use still holds, but
|
|
1920
|
+
* `estimatedAffectedRows` is always `null` (no server estimator) and a
|
|
1921
|
+
* stale/unknown token throws a plain `Error` rather than the HTTP
|
|
1922
|
+
* 404/403 of the server path.
|
|
1811
1923
|
*/
|
|
1812
1924
|
async propose(sql, opts = {}) {
|
|
1813
1925
|
if (this.client.local) {
|
|
@@ -1962,6 +2074,7 @@ function rowsToObjects(columns, rows) {
|
|
|
1962
2074
|
PerSQLApprovals,
|
|
1963
2075
|
PerSQLBranches,
|
|
1964
2076
|
PerSQLDatabase,
|
|
2077
|
+
PerSQLDatabases,
|
|
1965
2078
|
PerSQLError,
|
|
1966
2079
|
PerSQLProposals,
|
|
1967
2080
|
RateLimitError,
|