diff --git a/README.md b/README.md index d9d7cfc..15b2d67 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ The glue for TypeScript to PostgreSQL. ## Installation ```ts -import pglue from "https://git.lua.re/luaneko/pglue/raw/tag/v0.2.0/mod.ts"; +import pglue from "https://git.lua.re/luaneko/pglue/raw/tag/v0.3.0/mod.ts"; // ...or from github: -import pglue from "https://raw.githubusercontent.com/luaneko/pglue/refs/tags/v0.2.0/mod.ts"; +import pglue from "https://raw.githubusercontent.com/luaneko/pglue/refs/tags/v0.3.0/mod.ts"; ``` ## Documentation diff --git a/deno.json b/deno.json index c594288..a3a7793 100644 --- a/deno.json +++ b/deno.json @@ -1,5 +1,5 @@ { "name": "@luaneko/pglue", - "version": "0.2.0", + "version": "0.3.0", "exports": "./mod.ts" } diff --git a/mod.ts b/mod.ts index c4daac7..95d58f1 100644 --- a/mod.ts +++ b/mod.ts @@ -27,11 +27,10 @@ export { sql, is_sql, Query, - type Row, - type CommandResult, type Result, - type Results, - type ResultStream, + type Row, + type Rows, + type RowStream, } from "./query.ts"; export type Options = { diff --git a/query.ts b/query.ts index e0bde7f..15e47a6 100644 --- a/query.ts +++ b/query.ts @@ -323,22 +323,15 @@ export const sql_types: SqlTypeMap = { sql.types = sql_types; -type ReadonlyTuple = readonly [...T]; - -export interface CommandResult { +export interface Result { readonly tag: string; } -export interface Result extends CommandResult, ReadonlyTuple<[T]> { - readonly row: T; -} - -export interface Results extends CommandResult, ReadonlyArray { +export interface Rows extends Result, ReadonlyArray { readonly rows: ReadonlyArray; } -export interface ResultStream - extends AsyncIterable {} +export interface RowStream extends AsyncIterable {} export interface Row extends Iterable { [column: string]: unknown; @@ -351,12 +344,10 @@ export interface QueryOptions { readonly stdout: WritableStream | null; } -export class Query - implements PromiseLike>, ResultStream -{ +export class Query implements PromiseLike>, RowStream { readonly #f; - constructor(f: (options: Partial) => ResultStream) { + constructor(f: (options: Partial) => RowStream) { this.#f = f; } @@ -431,20 +422,18 @@ export class Query return this.#f(options); } - async first(): Promise> { - const { rows, tag } = await this.collect(1); - if (!rows.length) throw new TypeError(`expected one row, got none instead`); - const row = rows[0]; - return Object.assign([row] as const, { row: rows[0], tag }); + async first(): Promise { + const rows = await this.collect(1); + if (rows.length !== 0) return rows[0]; + else throw new TypeError(`expected one row, got none instead`); } - async first_or(value: S): Promise> { - const { rows, tag } = await this.collect(1); - const row = rows.length ? rows[0] : value; - return Object.assign([row] as const, { row: rows[0], tag }); + async first_or(value: S): Promise { + const rows = await this.collect(1); + return rows.length !== 0 ? rows[0] : value; } - async collect(count = Number.POSITIVE_INFINITY): Promise> { + async collect(count = Number.POSITIVE_INFINITY): Promise> { const iter = this[Symbol.asyncIterator](); let next; const rows = []; @@ -470,8 +459,8 @@ export class Query return n; } - then, U = never>( - f?: ((rows: Results) => S | PromiseLike) | null, + then, U = never>( + f?: ((rows: Rows) => S | PromiseLike) | null, g?: ((reason?: unknown) => U | PromiseLike) | null ) { return this.collect().then(f, g); diff --git a/test.ts b/test.ts index 8fa6fb0..428be4e 100644 --- a/test.ts +++ b/test.ts @@ -16,7 +16,7 @@ Deno.test(`integers`, async () => { await using pg = await connect(); await using _tx = await pg.begin(); - const [{ a, b, c }] = await pg.query` + const { a, b, c } = await pg.query` select ${"0x100"}::int2 as a, ${777}::int4 as b, @@ -32,7 +32,7 @@ Deno.test(`integers`, async () => { expect(b).toBe(777); expect(c).toBe(1234); - const [{ large }] = + const { large } = await pg.query`select ${"10000000000000000"}::int8 as large`.first(); expect(large).toBe(10000000000000000n); @@ -47,7 +47,7 @@ Deno.test(`boolean`, async () => { await using pg = await connect(); await using _tx = await pg.begin(); - const [{ a, b, c }] = await pg.query` + const { a, b, c } = await pg.query` select ${true}::bool as a, ${"n"}::bool as b, @@ -63,7 +63,7 @@ Deno.test(`bytea`, async () => { await using pg = await connect(); await using _tx = await pg.begin(); - const [{ string, array, buffer }] = await pg.query` + const { string, array, buffer } = await pg.query` select ${"hello, world"}::bytea as string, ${[1, 2, 3, 4, 5]}::bytea as array, @@ -93,7 +93,7 @@ Deno.test(`row`, async () => { ).tag ).toBe(`COPY 1`); - const [row] = await pg.query`select * from my_table`.first(); + const row = await pg.query`select * from my_table`.first(); { // columns by name const { a, b, c } = row; @@ -132,7 +132,7 @@ Deno.test(`sql injection`, async () => { `INSERT 0 1` ); - const [{ name }] = await pg.query<{ name: string }>` + const { name } = await pg.query<{ name: string }>` select name from users `.first(); diff --git a/wire.ts b/wire.ts index 2e65083..9cd34c9 100644 --- a/wire.ts +++ b/wire.ts @@ -35,11 +35,11 @@ import { type EncoderType, } from "./ser.ts"; import { - type CommandResult, format, is_sql, Query, - type ResultStream, + type Result, + type RowStream, type Row, sql, type SqlFragment, @@ -460,22 +460,22 @@ export type WireEvents = { close(reason?: unknown): void; }; -export interface Transaction extends CommandResult, AsyncDisposable { +export interface Transaction extends Result, AsyncDisposable { readonly open: boolean; - commit(): Promise; - rollback(): Promise; + commit(): Promise; + rollback(): Promise; } export type ChannelEvents = { notify: NotificationHandler }; export type NotificationHandler = (payload: string, process_id: number) => void; export interface Channel extends TypedEmitter, - CommandResult, + Result, AsyncDisposable { readonly name: string; readonly open: boolean; - notify(payload: string): Promise; - unlisten(): Promise; + notify(payload: string): Promise; + unlisten(): Promise; } export async function wire_connect(options: WireOptions) { @@ -546,16 +546,15 @@ export class Wire } async get(param: string) { - return ( - await this.query`select current_setting(${param}, true)` - .map(([s]) => String(s)) - .first_or(null) - )[0]; + return await this.query`select current_setting(${param}, true)` + .map(([s]) => String(s)) + .first_or(null); } async set(param: string, value: string, local = false) { - return await this - .query`select set_config(${param}, ${value}, ${local})`.execute(); + return await this.query`select set_config(${param}, ${value}, ${local})` + .map(([s]) => String(s)) + .first(); } close(reason?: unknown) { @@ -1143,7 +1142,7 @@ function wire_impl( query: string, stdin: ReadableStream | null, stdout: WritableStream | null - ): ResultStream { + ): RowStream { yield* await pipeline( () => { log("debug", { query }, `executing simple query`); @@ -1189,7 +1188,7 @@ function wire_impl( params: unknown[], stdin: ReadableStream | null, stdout: WritableStream | null - ): ResultStream { + ): RowStream { const { query, name: statement } = st; const { ser_params, Row } = await st.parse(); const param_values = ser_params(params); @@ -1238,7 +1237,7 @@ function wire_impl( chunk_size: number, stdin: ReadableStream | null, stdout: WritableStream | null - ): ResultStream { + ): RowStream { const { query, name: statement } = st; const { ser_params, Row } = await st.parse(); const param_values = ser_params(params); @@ -1326,7 +1325,7 @@ function wire_impl( return tx_stack.indexOf(this) !== -1; } - constructor(begin: CommandResult) { + constructor(begin: Result) { Object.assign(this, begin); } @@ -1384,7 +1383,7 @@ function wire_impl( return channels.get(this.#name) === this; } - constructor(name: string, listen: CommandResult) { + constructor(name: string, listen: Result) { super(); Object.assign(this, listen); this.#name = name;