@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.cjs
CHANGED
|
@@ -287,8 +287,23 @@ var PerSQL = class _PerSQL {
|
|
|
287
287
|
}
|
|
288
288
|
return new PerSQLDatabase(this, ns, slug);
|
|
289
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Provision and list databases in the token's namespace. `create`
|
|
292
|
+
* needs an unscoped admin token; `database()` then gives you a handle
|
|
293
|
+
* to query the result.
|
|
294
|
+
*/
|
|
295
|
+
get databases() {
|
|
296
|
+
return new PerSQLDatabases(this);
|
|
297
|
+
}
|
|
290
298
|
/** @internal */
|
|
291
299
|
async request(method, path, body, headers) {
|
|
300
|
+
return (await this.requestWithMeta(method, path, body, headers)).data;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* @internal — like `request`, but also surfaces the response `meta`
|
|
304
|
+
* envelope (usage + `costUsd`) that the data-only `request` drops.
|
|
305
|
+
*/
|
|
306
|
+
async requestWithMeta(method, path, body, headers) {
|
|
292
307
|
const res = await this._fetch(`${this.baseURL}${path}`, {
|
|
293
308
|
method,
|
|
294
309
|
headers: {
|
|
@@ -320,7 +335,7 @@ var PerSQL = class _PerSQL {
|
|
|
320
335
|
}
|
|
321
336
|
throw new PerSQLError(res.status, message, envelope?.errorDetail);
|
|
322
337
|
}
|
|
323
|
-
return envelope.data;
|
|
338
|
+
return { data: envelope.data, meta: envelope.meta };
|
|
324
339
|
}
|
|
325
340
|
/** @internal — raw fetch returning the underlying Response. */
|
|
326
341
|
fetchRaw(method, path) {
|
|
@@ -391,6 +406,44 @@ var SupportClient = class {
|
|
|
391
406
|
);
|
|
392
407
|
}
|
|
393
408
|
};
|
|
409
|
+
var PerSQLDatabases = class {
|
|
410
|
+
constructor(client) {
|
|
411
|
+
this.client = client;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Provision a new isolated database and return its metadata. Requires
|
|
415
|
+
* an unscoped admin token (scoped tokens can't create databases).
|
|
416
|
+
*/
|
|
417
|
+
async create(opts) {
|
|
418
|
+
if (this.client.local) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
"PerSQL: databases.create() needs a server-mode token. Pass a real token to `new PerSQL({ token })` to provision databases."
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return this.client.request("POST", "/v1/databases", {
|
|
424
|
+
name: opts.name,
|
|
425
|
+
slug: opts.slug,
|
|
426
|
+
region: opts.region,
|
|
427
|
+
ttlDays: opts.ttlDays
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/** List databases in the token's namespace, newest first (paginated). */
|
|
431
|
+
async list(opts = {}) {
|
|
432
|
+
if (this.client.local) {
|
|
433
|
+
throw new Error("PerSQL: databases.list() needs a server-mode token.");
|
|
434
|
+
}
|
|
435
|
+
const qs = new URLSearchParams();
|
|
436
|
+
if (opts.cursor) qs.set("cursor", opts.cursor);
|
|
437
|
+
if (opts.pageSize) qs.set("pageSize", String(opts.pageSize));
|
|
438
|
+
const tail = qs.toString() ? `?${qs.toString()}` : "";
|
|
439
|
+
const res = await this.client.fetchRaw("GET", `/v1/databases${tail}`);
|
|
440
|
+
const body = await res.json();
|
|
441
|
+
if (!res.ok || !body.success) {
|
|
442
|
+
throw new PerSQLError(res.status, body.error ?? `Request failed (${res.status})`);
|
|
443
|
+
}
|
|
444
|
+
return { data: body.data ?? [], nextCursor: body.nextCursor ?? null };
|
|
445
|
+
}
|
|
446
|
+
};
|
|
394
447
|
var PerSQLDatabase = class {
|
|
395
448
|
constructor(client, namespace, slug) {
|
|
396
449
|
this.client = client;
|
|
@@ -407,7 +460,7 @@ var PerSQLDatabase = class {
|
|
|
407
460
|
if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
408
461
|
if (options.planKey) headers["Plan-Key"] = options.planKey;
|
|
409
462
|
if (options.planStep) headers["Plan-Step"] = options.planStep;
|
|
410
|
-
const raw = await this.client.
|
|
463
|
+
const { data: raw, meta } = await this.client.requestWithMeta(
|
|
411
464
|
"POST",
|
|
412
465
|
`/v1/db/${this.namespace}/${this.slug}/query`,
|
|
413
466
|
{ sql, params },
|
|
@@ -415,7 +468,8 @@ var PerSQLDatabase = class {
|
|
|
415
468
|
);
|
|
416
469
|
return {
|
|
417
470
|
...raw,
|
|
418
|
-
data: rowsToObjects(raw.columns, raw.rows)
|
|
471
|
+
data: rowsToObjects(raw.columns, raw.rows),
|
|
472
|
+
meta
|
|
419
473
|
};
|
|
420
474
|
}
|
|
421
475
|
/** Run multiple statements in one round-trip, optionally in a transaction. */
|
|
@@ -773,6 +827,55 @@ var PerSQLDatabase = class {
|
|
|
773
827
|
}
|
|
774
828
|
};
|
|
775
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* Long-poll for row-changes on this database. Returns immediately
|
|
832
|
+
* if changes newer than `since` are already buffered; otherwise
|
|
833
|
+
* blocks server-side for up to `waitMs` (default 25s, max 25s).
|
|
834
|
+
*
|
|
835
|
+
* Most callers want `changes()` instead — an async iterator that
|
|
836
|
+
* loops `waitForChanges` forever, surfacing each batch as it
|
|
837
|
+
* arrives.
|
|
838
|
+
*/
|
|
839
|
+
async waitForChanges(opts = {}) {
|
|
840
|
+
if (this.client.local) {
|
|
841
|
+
throw new Error(
|
|
842
|
+
"PerSQL: waitForChanges requires the DO change ring \u2014 not available in local mode."
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const qs = new URLSearchParams();
|
|
846
|
+
if (typeof opts.since === "number") qs.set("since", String(opts.since));
|
|
847
|
+
if (typeof opts.waitMs === "number") qs.set("waitMs", String(opts.waitMs));
|
|
848
|
+
if (opts.tables && opts.tables.length > 0) qs.set("tables", opts.tables.join(","));
|
|
849
|
+
const q = qs.toString();
|
|
850
|
+
return this.client.request(
|
|
851
|
+
"GET",
|
|
852
|
+
`/v1/db/${this.namespace}/${this.slug}/changes${q ? `?${q}` : ""}`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Async-iterator change feed. Loops `waitForChanges` forever and
|
|
857
|
+
* yields one batch per server response. Pass an `AbortSignal` to
|
|
858
|
+
* stop the loop cleanly.
|
|
859
|
+
*
|
|
860
|
+
* ```ts
|
|
861
|
+
* const ctl = new AbortController();
|
|
862
|
+
* for await (const batch of db.changes({ signal: ctl.signal })) {
|
|
863
|
+
* for (const c of batch.changes) console.log(c.kind, c.table);
|
|
864
|
+
* }
|
|
865
|
+
* ```
|
|
866
|
+
*/
|
|
867
|
+
async *changes(opts = {}) {
|
|
868
|
+
let cursor = opts.since ?? 0;
|
|
869
|
+
while (!opts.signal?.aborted) {
|
|
870
|
+
const batch = await this.waitForChanges({
|
|
871
|
+
since: cursor,
|
|
872
|
+
waitMs: opts.waitMs,
|
|
873
|
+
tables: opts.tables
|
|
874
|
+
});
|
|
875
|
+
cursor = batch.cursor;
|
|
876
|
+
yield batch;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
776
879
|
/**
|
|
777
880
|
* Returns the callback shape `drizzle-orm/sqlite-proxy` expects.
|
|
778
881
|
* Pair with `drizzle()` from that module to get a typed,
|
|
@@ -809,7 +912,7 @@ var PerSQLDatabase = class {
|
|
|
809
912
|
* `execute`/`invoke` callbacks, which call `runTool` internally).
|
|
810
913
|
*/
|
|
811
914
|
asTool(name = "persql_query") {
|
|
812
|
-
const description = `Run a SQL query against the PerSQL database "${this.namespace}/${this.slug}".
|
|
915
|
+
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.`;
|
|
813
916
|
const inputSchema = {
|
|
814
917
|
type: "object",
|
|
815
918
|
properties: {
|
|
@@ -820,7 +923,10 @@ var PerSQLDatabase = class {
|
|
|
820
923
|
description: "Positional parameters for the SQL statement"
|
|
821
924
|
}
|
|
822
925
|
},
|
|
823
|
-
required: ["sql"]
|
|
926
|
+
required: ["sql"],
|
|
927
|
+
// OpenAI strict function-calling rejects a schema without this; the
|
|
928
|
+
// per-table asTools() schemas already set it. Keep them consistent.
|
|
929
|
+
additionalProperties: false
|
|
824
930
|
};
|
|
825
931
|
const execute = (input) => this.runTool({
|
|
826
932
|
sql: String(input.sql ?? ""),
|
|
@@ -1779,6 +1885,11 @@ var PerSQLProposals = class {
|
|
|
1779
1885
|
* rows, and returns a single-use `executionToken` valid for 10 min
|
|
1780
1886
|
* (override with `ttlSec`, max 1 hour). Nothing is mutated until
|
|
1781
1887
|
* `apply()` is called.
|
|
1888
|
+
*
|
|
1889
|
+
* Local mode keeps the token in-process: single-use still holds, but
|
|
1890
|
+
* `estimatedAffectedRows` is always `null` (no server estimator) and a
|
|
1891
|
+
* stale/unknown token throws a plain `Error` rather than the HTTP
|
|
1892
|
+
* 404/403 of the server path.
|
|
1782
1893
|
*/
|
|
1783
1894
|
async propose(sql, opts = {}) {
|
|
1784
1895
|
if (this.client.local) {
|