@typesql/pg 0.0.4 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @typesql/pg
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Queries run from the `sql` bound to your client: `createSql<DB>(client)` then `sql("…").execute()` (or `executeTakeFirst`, `executeTakeFirstOrThrow`, `executeCount`), and `sql.transaction(async (tx) => …)` for transactions, nested with savepoints; the `query`, `one`, `maybeOne`, `many`, `execute`, `transaction` and `RowCountError` exports of `@typesql/pg` and `@typesql/postgres`, `query` and `execute` of `@typesql/kysely`, and `queryRaw` and `executeRaw` of `@typesql/prisma` are removed.
8
+
9
+ ### Patch Changes
10
+
11
+ - The README links to the type map tables on the new node-postgres and postgres.js adapter pages of the docs.
12
+
13
+ - Updated dependencies:
14
+ - @typesql/core@0.1.0
15
+
3
16
  ## 0.0.4
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [node-postgres](https://node-postgres.com) (`pg` 8.x) adapter for
4
4
  [`@typesql/core`](https://www.npmjs.com/package/@typesql/core): a `createSql` bound to the type
5
- map of `pg`'s default parsers, and helpers that run a query and return rows typed from it.
5
+ map of `pg`'s default parsers, whose queries run on your pool and return rows typed from the SQL.
6
6
 
7
7
  ```sh
8
8
  pnpm add @typesql/core @typesql/pg pg
@@ -12,21 +12,20 @@ pnpm add @typesql/core @typesql/pg pg
12
12
 
13
13
  ```ts
14
14
  import { Pool } from "pg";
15
- import { createSql, execute, many, maybeOne, one, query, transaction } from "@typesql/pg";
15
+ import { createSql } from "@typesql/pg";
16
16
  import type { DB } from "./schema";
17
17
 
18
18
  const pool = new Pool();
19
- const sql = createSql<DB>();
19
+ const sql = createSql<DB>(pool);
20
20
 
21
- const users = await query(pool, sql("SELECT id, email FROM users")); // { id: number; email: string }[]
22
- const user = await one(pool, sql("SELECT id, email FROM users WHERE id = $1", 7)); // exactly one row, or throws
23
- const maybe = await maybeOne(pool, sql("SELECT id FROM users WHERE email = $1", "x")); // row | null
24
- const some = await many(pool, sql("SELECT id FROM posts")); // at least one row, or throws
25
- const changed = await execute(pool, sql("UPDATE users SET display_name = NULL")); // row count
21
+ const users = await sql("SELECT id, email FROM users").execute(); // { id: number; email: string }[]
22
+ const maybe = await sql("SELECT id FROM users WHERE email = $1", "x").executeTakeFirst(); // row | undefined
23
+ const user = await sql("SELECT id, email FROM users WHERE id = $1", 7).executeTakeFirstOrThrow(); // row, or NoResultError
24
+ const changed = await sql("UPDATE users SET display_name = NULL").executeCount(); // row count
26
25
 
27
- const id = await transaction(pool, async (client) => {
28
- const { id } = await one(client, sql("INSERT INTO users (email) VALUES ($1) RETURNING id", "a@b.c"));
29
- await execute(client, sql("INSERT INTO posts (author_id, title) VALUES ($1, $2)", id, "Hello"));
26
+ const id = await sql.transaction(async (tx) => {
27
+ const { id } = await tx("INSERT INTO users (email) VALUES ($1) RETURNING id", "a@b.c").executeTakeFirstOrThrow();
28
+ await tx("INSERT INTO posts (author_id, title) VALUES ($1, $2)", id, "Hello").execute();
30
29
  return id;
31
30
  });
32
31
  ```
@@ -35,20 +34,18 @@ const id = await transaction(pool, async (client) => {
35
34
 
36
35
  | Export | |
37
36
  |---|---|
38
- | `createSql<DB>(pool?, options?)` | core's `createSql<DB, PgTypeMap>()`; `{ rowKeys: "camelCase" }` camelCases the row keys |
39
- | `query(client, q)` | `Promise<Row[]>` |
40
- | `one(client, q)` | `Promise<Row>`; `RowCountError` unless exactly one row |
41
- | `maybeOne(client, q)` | `Promise<Row \| null>`; `RowCountError` on more than one row |
42
- | `many(client, q)` | `Promise<[Row, ...Row[]]>`; `RowCountError` on zero rows |
43
- | `execute(client, q)` | `Promise<number>`, the affected row count |
44
- | `executor(client)` | an `Executor` for `q.execute()` and `q.executeCount()` (reports `rowCount`); `createSql<DB>(pool)` binds one |
45
- | `transaction(pool, fn)` | `BEGIN`, `fn(client)`, `COMMIT`; `ROLLBACK` and rethrow on error |
37
+ | `createSql<DB>(client, options?)` | core's `createSql<DB, PgTypeMap>()` with the queries running on `client` (a `Pool`, `Client` or `PoolClient`); `{ rowKeys: "camelCase" }` camelCases the row keys |
38
+ | `q.execute()`, `q.executeTakeFirst()`, `q.executeTakeFirstOrThrow()`, `q.executeCount()` | `Promise<Row[]>`, `Promise<Row \| undefined>`, `Promise<Row>` (`NoResultError` on 0 rows), `Promise<number>` (`rowCount`) |
39
+ | `sql.transaction(fn)` | on a `Pool`: checks out a client, `BEGIN`, `fn(tx)`, `COMMIT`, releases it; `ROLLBACK` and rethrow on error. On a `Client` or `PoolClient`: the same on that connection. `tx` is `sql` bound to the transaction; `tx.transaction(fn)` uses a savepoint |
40
+ | `executor(client)` | an `Executor` for `q.execute(executor)` and core's `createSql(executor)` (reports `rowCount`) |
46
41
  | `migrate(pool, m)` | runs `m.statements` (e.g. from core's `migrations()`) in one transaction |
42
+ | `PgSql<DB, Keys>` | the type `createSql` returns |
47
43
  | `PgTypeMap`, `PgParamTypeMap`, `PgInterval` | the type map (rows), what parameters also accept, and the `postgres-interval` object type |
48
- | `RowCountError`, `PgQueryable`, `PgPoolLike`, `PgTransactionClient`, `PgResult`, `MigrationSource` | errors and parameter types |
44
+ | `NoResultError`, `PgQueryable`, `PgPoolLike`, `PgTransactionClient`, `PgResult`, `MigrationSource` | errors and parameter types |
49
45
 
50
- `client` is anything with `query(text, values)`: a `Pool`, `Client` or `PoolClient`. The
51
- parameters are structural, so fakes work in tests.
46
+ `client` is anything with `query(text, values)`. The parameters are structural, so fakes work
47
+ in tests. If the `ROLLBACK` of a pool transaction fails, the client is released with the error
48
+ so the pool discards the connection.
52
49
 
53
50
  ## `typesql introspect`
54
51
 
@@ -83,7 +80,7 @@ What `pg`'s default parsers return:
83
80
  | text-like and other types | `string` |
84
81
 
85
82
  Arrays map element-wise. The full table, including the array types `pg` leaves as raw text, is
86
- in the [Adapters guide](https://github.com/ismailkarsli/typesql/blob/main/packages/docs/guide/adapters.md).
83
+ on the [node-postgres adapter page](https://github.com/ismailkarsli/typesql/blob/main/packages/docs/adapters/pg.md).
87
84
 
88
85
  Parameters accept the row type and what `pg` serializes for the type (`PgParamTypeMap`):
89
86
  `number | bigint | string` for `int8`, `numeric` and `money`, `Date | string` for dates and
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type Executor, type Query, type RowType } from "@typesql/core";
2
- /** The subset of a `pg` query result the helpers rely on. */
1
+ import { type Executor, type SqlDriver } from "@typesql/core";
2
+ /** The subset of a `pg` query result the adapter relies on. */
3
3
  export interface PgResult {
4
4
  readonly rows: unknown[];
5
5
  readonly rowCount: number | null;
@@ -19,37 +19,13 @@ export interface PgTransactionClient extends PgQueryable {
19
19
  export interface PgPoolLike<C extends PgTransactionClient = PgTransactionClient> {
20
20
  connect(): Promise<C>;
21
21
  }
22
- /** Thrown by `one`, `maybeOne` and `many` when the row count does not match. */
23
- export declare class RowCountError extends Error {
24
- readonly code: "NO_ROWS" | "TOO_MANY_ROWS";
25
- readonly expected: string;
26
- readonly actual: number;
27
- readonly text: string;
28
- readonly name = "RowCountError";
29
- constructor(code: "NO_ROWS" | "TOO_MANY_ROWS", expected: string, actual: number, text: string);
30
- }
31
- type AnyQuery = Query<unknown, readonly unknown[]>;
32
22
  /**
33
23
  * An `Executor` for core's `q.execute()`: runs queries on `client` and resolves to
34
24
  * `{ rows, rowCount }` (pg's `rowCount`, so `q.executeCount()` works).
35
25
  */
36
26
  export declare function executor(client: PgQueryable): Executor;
37
- /** Run a query and return all rows, typed from the query. */
38
- export declare function query<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<RowType<Q>[]>;
39
- /** Exactly one row, otherwise throws `RowCountError`. */
40
- export declare function one<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<RowType<Q>>;
41
- /** Zero or one row (`null` for zero); throws `RowCountError` on more than one. */
42
- export declare function maybeOne<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<RowType<Q> | null>;
43
- /** At least one row, otherwise throws `RowCountError`. */
44
- export declare function many<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<[RowType<Q>, ...RowType<Q>[]]>;
45
- /** Run a statement for its side effect and return the affected row count (`rowCount ?? 0`). */
46
- export declare function execute(client: PgQueryable, q: AnyQuery): Promise<number>;
47
- /**
48
- * Check out a client, run `fn` inside BEGIN/COMMIT, ROLLBACK on error, and release the
49
- * client. If ROLLBACK itself fails the client is released with the error so the pool
50
- * discards the connection instead of reusing it.
51
- */
52
- export declare function transaction<C extends PgTransactionClient, T>(pool: PgPoolLike<C>, fn: (client: C) => Promise<T>): Promise<T>;
27
+ /** How `createSql(client)` runs queries and transactions. */
28
+ export declare const driver: SqlDriver<PgQueryable>;
53
29
  /** Anything carrying an ordered list of DDL statements, e.g. a `migrations()` builder from core. */
54
30
  export interface MigrationSource {
55
31
  readonly statements: readonly string[];
@@ -60,5 +36,4 @@ export interface MigrationSource {
60
36
  * with `$$`-quoted function bodies work.
61
37
  */
62
38
  export declare function migrate<C extends PgTransactionClient>(pool: PgPoolLike<C>, m: MigrationSource): Promise<void>;
63
- export {};
64
39
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAEjF,6DAA6D;AAC7D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC5D;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;CACtC;AAED,6CAA6C;AAC7C,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,mBAAmB,GAAG,mBAAmB;IAC7E,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;CACvB;AAED,gFAAgF;AAChF,qBAAa,aAAc,SAAQ,KAAK;IAGpC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;IALvB,SAAkB,IAAI,mBAAmB;IACzC,YACW,IAAI,EAAE,SAAS,GAAG,eAAe,EACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAGtB;CACF;AAED,KAAK,QAAQ,GAAG,KAAK,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC;AAOnD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAKtD;AAED,6DAA6D;AAC7D,wBAAsB,KAAK,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAGhG;AAED,yDAAyD;AACzD,wBAAsB,GAAG,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAM5F;AAED,kFAAkF;AAClF,wBAAsB,QAAQ,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAIxG;AAED,0DAA0D;AAC1D,wBAAsB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAIhH;AAED,+FAA+F;AAC/F,wBAAsB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAG/E;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,CAAC,SAAS,mBAAmB,EAAE,CAAC,EAChE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EACnB,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC,CAoBZ;AAED,oGAAoG;AACpG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,CAAC,SAAS,mBAAmB,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAInH"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE9E,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC5D;AAED,kFAAkF;AAClF,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,IAAI,CAAC;CACtC;AAED,6CAA6C;AAC7C,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,mBAAmB,GAAG,mBAAmB;IAC7E,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAMtD;AA6CD,6DAA6D;AAC7D,eAAO,MAAM,MAAM,EAAE,SAAS,CAAC,WAAW,CAA6B,CAAC;AAExE,oGAAoG;AACpG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,CAAC,SAAS,mBAAmB,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAInH"}
package/dist/client.js CHANGED
@@ -1,101 +1,65 @@
1
- import { mapRows } from "@typesql/core";
2
- /** Thrown by `one`, `maybeOne` and `many` when the row count does not match. */
3
- export class RowCountError extends Error {
4
- code;
5
- expected;
6
- actual;
7
- text;
8
- name = "RowCountError";
9
- constructor(code, expected, actual, text) {
10
- super(`Expected ${expected} row(s) but the query returned ${actual}`);
11
- this.code = code;
12
- this.expected = expected;
13
- this.actual = actual;
14
- this.text = text;
15
- }
16
- }
17
- function run(client, q) {
18
- // `pg` requires a mutable array (`QueryConfigValues<I>` matches `Array<U>` only).
19
- return client.query(q.text, Array.from(q.values));
20
- }
1
+ import { runTransaction } from "@typesql/core";
21
2
  /**
22
3
  * An `Executor` for core's `q.execute()`: runs queries on `client` and resolves to
23
4
  * `{ rows, rowCount }` (pg's `rowCount`, so `q.executeCount()` works).
24
5
  */
25
6
  export function executor(client) {
26
7
  return async (text, values) => {
8
+ // `pg` requires a mutable array (`QueryConfigValues<I>` matches `Array<U>` only).
27
9
  const { rows, rowCount } = await client.query(text, Array.from(values));
28
10
  return { rows, rowCount };
29
11
  };
30
12
  }
31
- /** Run a query and return all rows, typed from the query. */
32
- export async function query(client, q) {
33
- const result = await run(client, q);
34
- return mapRows(q, result.rows);
35
- }
36
- /** Exactly one row, otherwise throws `RowCountError`. */
37
- export async function one(client, q) {
38
- const rows = await query(client, q);
39
- if (rows.length !== 1) {
40
- throw new RowCountError(rows.length === 0 ? "NO_ROWS" : "TOO_MANY_ROWS", "exactly 1", rows.length, q.text);
41
- }
42
- return rows[0];
43
- }
44
- /** Zero or one row (`null` for zero); throws `RowCountError` on more than one. */
45
- export async function maybeOne(client, q) {
46
- const rows = await query(client, q);
47
- if (rows.length > 1)
48
- throw new RowCountError("TOO_MANY_ROWS", "at most 1", rows.length, q.text);
49
- return rows.length === 0 ? null : rows[0];
13
+ function isPool(client) {
14
+ // `pg.Client` has `connect()` too (it opens the socket), and `processID`; a `PoolClient` has `release()`.
15
+ return typeof client.connect === "function" && !("release" in client) && !("processID" in client);
50
16
  }
51
- /** At least one row, otherwise throws `RowCountError`. */
52
- export async function many(client, q) {
53
- const rows = await query(client, q);
54
- if (rows.length === 0)
55
- throw new RowCountError("NO_ROWS", "at least 1", 0, q.text);
56
- return rows;
57
- }
58
- /** Run a statement for its side effect and return the affected row count (`rowCount ?? 0`). */
59
- export async function execute(client, q) {
60
- const result = await run(client, q);
61
- return result.rowCount ?? 0;
17
+ function isConnection(client) {
18
+ return "release" in client || "processID" in client;
62
19
  }
20
+ const on = (c) => (statement) => c.query(statement);
63
21
  /**
64
- * Check out a client, run `fn` inside BEGIN/COMMIT, ROLLBACK on error, and release the
65
- * client. If ROLLBACK itself fails the client is released with the error so the pool
66
- * discards the connection instead of reusing it.
22
+ * Check out a client, run `fn` with it between `BEGIN` and `COMMIT`, and release it, with the
23
+ * error when `ROLLBACK` fails so the pool discards the connection instead of reusing it.
67
24
  */
68
- export async function transaction(pool, fn) {
69
- const client = await pool.connect();
25
+ async function inPoolTransaction(pool, fn) {
26
+ const conn = await pool.connect();
70
27
  let releaseWith;
71
28
  try {
72
- await client.query("BEGIN");
73
- try {
74
- const result = await fn(client);
75
- await client.query("COMMIT");
76
- return result;
77
- }
78
- catch (err) {
79
- try {
80
- await client.query("ROLLBACK");
81
- }
82
- catch (rollbackErr) {
83
- releaseWith = rollbackErr instanceof Error ? rollbackErr : new Error(String(rollbackErr));
84
- }
85
- throw err;
86
- }
29
+ return await runTransaction(on(conn), () => fn(conn), {
30
+ onRollbackError: (err) => {
31
+ releaseWith = err instanceof Error ? err : new Error(String(err));
32
+ },
33
+ });
87
34
  }
88
35
  finally {
89
- client.release(releaseWith);
36
+ conn.release(releaseWith);
37
+ }
38
+ }
39
+ /**
40
+ * Run `fn` inside a transaction: on a pool, on a client checked out of it; on a `Client` or
41
+ * `PoolClient`, on that connection. With `depth > 0` the client is already in a transaction,
42
+ * and `fn` runs in a savepoint.
43
+ */
44
+ function transaction(client, depth, fn) {
45
+ if (depth > 0)
46
+ return runTransaction(on(client), () => fn(client), { savepoint: `typesql_${depth}` });
47
+ if (isPool(client))
48
+ return inPoolTransaction(client, fn);
49
+ if (!isConnection(client)) {
50
+ return Promise.reject(new Error("typesql: sql.transaction() needs a pg Pool, Client or PoolClient, so that BEGIN and COMMIT reach one connection"));
90
51
  }
52
+ return runTransaction(on(client), () => fn(client));
91
53
  }
54
+ /** How `createSql(client)` runs queries and transactions. */
55
+ export const driver = { executor, transaction };
92
56
  /**
93
57
  * Run a migration's statements in order on one connection, inside a single transaction
94
58
  * (all or nothing). Statements are sent as simple queries without parameters, so scripts
95
59
  * with `$$`-quoted function bodies work.
96
60
  */
97
61
  export async function migrate(pool, m) {
98
- await transaction(pool, async (client) => {
62
+ await inPoolTransaction(pool, async (client) => {
99
63
  for (const statement of m.statements)
100
64
  await client.query(statement);
101
65
  });
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAiC,MAAM,eAAe,CAAC;AA0B9E;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAmB;IAC1C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;QAC5B,kFAAkF;QAClF,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACxE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,MAAmB;IACjC,0GAA0G;IAC1G,OAAO,OAAQ,MAA8B,CAAC,OAAO,KAAK,UAAU,IAAI,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,IAAI,MAAM,CAAC,CAAC;AAC7H,CAAC;AAED,SAAS,YAAY,CAAC,MAAmB;IACvC,OAAO,SAAS,IAAI,MAAM,IAAI,WAAW,IAAI,MAAM,CAAC;AACtD,CAAC;AAED,MAAM,EAAE,GAAG,CAAC,CAAc,EAAE,EAAE,CAAC,CAAC,SAAiB,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAEzE;;;GAGG;AACH,KAAK,UAAU,iBAAiB,CAAmC,IAAmB,EAAE,EAA2B;IACjH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IAClC,IAAI,WAA8B,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,MAAM,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;YACpD,eAAe,EAAE,CAAC,GAAG,EAAE,EAAE;gBACvB,WAAW,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACpE,CAAC;SACF,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAI,MAAmB,EAAE,KAAa,EAAE,EAAmC;IAC7F,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,EAAE,CAAC,CAAC;IACtG,IAAI,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,iBAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iHAAiH,CAAC,CAAC,CAAC;IACtJ,CAAC;IACD,OAAO,cAAc,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,MAAM,MAAM,GAA2B,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAOxE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAgC,IAAmB,EAAE,CAAkB;IAClG,MAAM,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAC7C,KAAK,MAAM,SAAS,IAAI,CAAC,CAAC,UAAU;YAAE,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,18 +1,21 @@
1
- import { type Sql } from "@typesql/core";
1
+ import { type RowKeys, type TransactionalSql } from "@typesql/core";
2
2
  import { type PgQueryable } from "./client.js";
3
3
  import type { PgTypeMap } from "./type-map.js";
4
- export { NoResultError, type Executor } from "@typesql/core";
4
+ export { NoResultError, type Executor, type TransactionalSql } from "@typesql/core";
5
5
  export type { PgInterval, PgParamTypeMap, PgTypeMap } from "./type-map.js";
6
- export { query, one, maybeOne, many, execute, executor, transaction, migrate, type MigrationSource, RowCountError, type PgQueryable, type PgResult, type PgTransactionClient, type PgPoolLike, } from "./client.js";
6
+ export { executor, migrate, type MigrationSource, type PgQueryable, type PgResult, type PgTransactionClient, type PgPoolLike, } from "./client.js";
7
+ /** What `createSql<DB>(client)` returns: core's `Sql` under `PgTypeMap`, with `transaction(fn)`. */
8
+ export type PgSql<DB, Keys extends RowKeys = "preserve"> = TransactionalSql<DB, PgTypeMap, Keys>;
7
9
  /**
8
10
  * `createSql<DB>()` from core, pre-bound to `PgTypeMap` (`count(*)` is `string`, `int8` is `string`, dates are `Date`).
9
- * With a `client` (a `Pool`, `Client` or `PoolClient`), the queries' `execute*` methods run on it.
11
+ * The queries' `execute*` methods run on `client` (a `Pool`, `Client` or `PoolClient`), and
12
+ * `sql.transaction(fn)` runs `fn` with a `sql` bound to one transaction.
10
13
  * `{ rowKeys: "camelCase" }` camelCases the rows' keys, in the types and at runtime.
11
14
  */
12
15
  export declare function createSql<DB>(client: PgQueryable | undefined, options: {
13
16
  readonly rowKeys: "camelCase";
14
- }): Sql<DB, PgTypeMap, "camelCase">;
17
+ }): PgSql<DB, "camelCase">;
15
18
  export declare function createSql<DB>(client?: PgQueryable, options?: {
16
19
  readonly rowKeys?: "preserve";
17
- }): Sql<DB, PgTypeMap>;
20
+ }): PgSql<DB>;
18
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,KAAK,GAAG,EAAmB,MAAM,eAAe,CAAC;AACpG,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EACL,KAAK,EACL,GAAG,EACH,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,WAAW,EACX,OAAO,EACP,KAAK,eAAe,EACpB,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AAErB;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GAAG,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;AAC5I,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC7G,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACpF,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EACL,QAAQ,EACR,OAAO,EACP,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AAErB,oGAAoG;AACpG,MAAM,MAAM,KAAK,CAAC,EAAE,EAAE,IAAI,SAAS,OAAO,GAAG,UAAU,IAAI,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;AAEjG;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GAAG,KAAK,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;AACnI,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { createSql as createCoreSql } from "@typesql/core";
2
- import { executor } from "./client.js";
1
+ import { createTransactionalSql } from "@typesql/core";
2
+ import { driver } from "./client.js";
3
3
  export { NoResultError } from "@typesql/core";
4
- export { query, one, maybeOne, many, execute, executor, transaction, migrate, RowCountError, } from "./client.js";
4
+ export { executor, migrate, } from "./client.js";
5
5
  export function createSql(client, options) {
6
- return createCoreSql(client && executor(client), options);
6
+ return createTransactionalSql(client, driver, options);
7
7
  }
8
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,aAAa,EAA2C,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,QAAQ,EAAoB,MAAM,aAAa,CAAC;AAGzD,OAAO,EAAE,aAAa,EAAiB,MAAM,eAAe,CAAC;AAE7D,OAAO,EACL,KAAK,EACL,GAAG,EACH,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,WAAW,EACX,OAAO,EAEP,aAAa,GAKd,MAAM,aAAa,CAAC;AASrB,MAAM,UAAU,SAAS,CAAK,MAAoB,EAAE,OAAoB;IACtE,OAAO,aAAa,CAAgB,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,OAA4C,CAAC,CAAC;AAChH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAwD,MAAM,eAAe,CAAC;AAC7G,OAAO,EAAE,MAAM,EAAoB,MAAM,aAAa,CAAC;AAGvD,OAAO,EAAE,aAAa,EAAwC,MAAM,eAAe,CAAC;AAEpF,OAAO,EACL,QAAQ,EACR,OAAO,GAMR,MAAM,aAAa,CAAC;AAarB,MAAM,UAAU,SAAS,CAAK,MAAoB,EAAE,OAAoB;IACtE,OAAO,sBAAsB,CAA6B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACrF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typesql/pg",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "description": "node-postgres (pg) adapter for typesql, with `typesql introspect` to generate a schema from a live database.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@typesql/core": "0.0.4"
38
+ "@typesql/core": "0.1.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "pg": ">=8"
package/src/client.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { mapRows, type Executor, type Query, type RowType } from "@typesql/core";
1
+ import { runTransaction, type Executor, type SqlDriver } from "@typesql/core";
2
2
 
3
- /** The subset of a `pg` query result the helpers rely on. */
3
+ /** The subset of a `pg` query result the adapter relies on. */
4
4
  export interface PgResult {
5
5
  readonly rows: unknown[];
6
6
  readonly rowCount: number | null;
@@ -24,102 +24,64 @@ export interface PgPoolLike<C extends PgTransactionClient = PgTransactionClient>
24
24
  connect(): Promise<C>;
25
25
  }
26
26
 
27
- /** Thrown by `one`, `maybeOne` and `many` when the row count does not match. */
28
- export class RowCountError extends Error {
29
- override readonly name = "RowCountError";
30
- constructor(
31
- readonly code: "NO_ROWS" | "TOO_MANY_ROWS",
32
- readonly expected: string,
33
- readonly actual: number,
34
- readonly text: string,
35
- ) {
36
- super(`Expected ${expected} row(s) but the query returned ${actual}`);
37
- }
38
- }
39
-
40
- type AnyQuery = Query<unknown, readonly unknown[]>;
41
-
42
- function run(client: PgQueryable, q: AnyQuery): Promise<PgResult> {
43
- // `pg` requires a mutable array (`QueryConfigValues<I>` matches `Array<U>` only).
44
- return client.query(q.text, Array.from(q.values));
45
- }
46
-
47
27
  /**
48
28
  * An `Executor` for core's `q.execute()`: runs queries on `client` and resolves to
49
29
  * `{ rows, rowCount }` (pg's `rowCount`, so `q.executeCount()` works).
50
30
  */
51
31
  export function executor(client: PgQueryable): Executor {
52
32
  return async (text, values) => {
33
+ // `pg` requires a mutable array (`QueryConfigValues<I>` matches `Array<U>` only).
53
34
  const { rows, rowCount } = await client.query(text, Array.from(values));
54
35
  return { rows, rowCount };
55
36
  };
56
37
  }
57
38
 
58
- /** Run a query and return all rows, typed from the query. */
59
- export async function query<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<RowType<Q>[]> {
60
- const result = await run(client, q);
61
- return mapRows(q, result.rows);
62
- }
63
-
64
- /** Exactly one row, otherwise throws `RowCountError`. */
65
- export async function one<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<RowType<Q>> {
66
- const rows = await query(client, q);
67
- if (rows.length !== 1) {
68
- throw new RowCountError(rows.length === 0 ? "NO_ROWS" : "TOO_MANY_ROWS", "exactly 1", rows.length, q.text);
69
- }
70
- return rows[0] as RowType<Q>;
71
- }
72
-
73
- /** Zero or one row (`null` for zero); throws `RowCountError` on more than one. */
74
- export async function maybeOne<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<RowType<Q> | null> {
75
- const rows = await query(client, q);
76
- if (rows.length > 1) throw new RowCountError("TOO_MANY_ROWS", "at most 1", rows.length, q.text);
77
- return rows.length === 0 ? null : (rows[0] as RowType<Q>);
39
+ function isPool(client: PgQueryable): client is PgQueryable & PgPoolLike {
40
+ // `pg.Client` has `connect()` too (it opens the socket), and `processID`; a `PoolClient` has `release()`.
41
+ return typeof (client as Partial<PgPoolLike>).connect === "function" && !("release" in client) && !("processID" in client);
78
42
  }
79
43
 
80
- /** At least one row, otherwise throws `RowCountError`. */
81
- export async function many<Q extends AnyQuery>(client: PgQueryable, q: Q): Promise<[RowType<Q>, ...RowType<Q>[]]> {
82
- const rows = await query(client, q);
83
- if (rows.length === 0) throw new RowCountError("NO_ROWS", "at least 1", 0, q.text);
84
- return rows as [RowType<Q>, ...RowType<Q>[]];
44
+ function isConnection(client: PgQueryable): boolean {
45
+ return "release" in client || "processID" in client;
85
46
  }
86
47
 
87
- /** Run a statement for its side effect and return the affected row count (`rowCount ?? 0`). */
88
- export async function execute(client: PgQueryable, q: AnyQuery): Promise<number> {
89
- const result = await run(client, q);
90
- return result.rowCount ?? 0;
91
- }
48
+ const on = (c: PgQueryable) => (statement: string) => c.query(statement);
92
49
 
93
50
  /**
94
- * Check out a client, run `fn` inside BEGIN/COMMIT, ROLLBACK on error, and release the
95
- * client. If ROLLBACK itself fails the client is released with the error so the pool
96
- * discards the connection instead of reusing it.
51
+ * Check out a client, run `fn` with it between `BEGIN` and `COMMIT`, and release it, with the
52
+ * error when `ROLLBACK` fails so the pool discards the connection instead of reusing it.
97
53
  */
98
- export async function transaction<C extends PgTransactionClient, T>(
99
- pool: PgPoolLike<C>,
100
- fn: (client: C) => Promise<T>,
101
- ): Promise<T> {
102
- const client = await pool.connect();
54
+ async function inPoolTransaction<C extends PgTransactionClient, T>(pool: PgPoolLike<C>, fn: (conn: C) => Promise<T>): Promise<T> {
55
+ const conn = await pool.connect();
103
56
  let releaseWith: Error | undefined;
104
57
  try {
105
- await client.query("BEGIN");
106
- try {
107
- const result = await fn(client);
108
- await client.query("COMMIT");
109
- return result;
110
- } catch (err) {
111
- try {
112
- await client.query("ROLLBACK");
113
- } catch (rollbackErr) {
114
- releaseWith = rollbackErr instanceof Error ? rollbackErr : new Error(String(rollbackErr));
115
- }
116
- throw err;
117
- }
58
+ return await runTransaction(on(conn), () => fn(conn), {
59
+ onRollbackError: (err) => {
60
+ releaseWith = err instanceof Error ? err : new Error(String(err));
61
+ },
62
+ });
118
63
  } finally {
119
- client.release(releaseWith);
64
+ conn.release(releaseWith);
120
65
  }
121
66
  }
122
67
 
68
+ /**
69
+ * Run `fn` inside a transaction: on a pool, on a client checked out of it; on a `Client` or
70
+ * `PoolClient`, on that connection. With `depth > 0` the client is already in a transaction,
71
+ * and `fn` runs in a savepoint.
72
+ */
73
+ function transaction<T>(client: PgQueryable, depth: number, fn: (tx: PgQueryable) => Promise<T>): Promise<T> {
74
+ if (depth > 0) return runTransaction(on(client), () => fn(client), { savepoint: `typesql_${depth}` });
75
+ if (isPool(client)) return inPoolTransaction(client, fn);
76
+ if (!isConnection(client)) {
77
+ return Promise.reject(new Error("typesql: sql.transaction() needs a pg Pool, Client or PoolClient, so that BEGIN and COMMIT reach one connection"));
78
+ }
79
+ return runTransaction(on(client), () => fn(client));
80
+ }
81
+
82
+ /** How `createSql(client)` runs queries and transactions. */
83
+ export const driver: SqlDriver<PgQueryable> = { executor, transaction };
84
+
123
85
  /** Anything carrying an ordered list of DDL statements, e.g. a `migrations()` builder from core. */
124
86
  export interface MigrationSource {
125
87
  readonly statements: readonly string[];
@@ -131,7 +93,7 @@ export interface MigrationSource {
131
93
  * with `$$`-quoted function bodies work.
132
94
  */
133
95
  export async function migrate<C extends PgTransactionClient>(pool: PgPoolLike<C>, m: MigrationSource): Promise<void> {
134
- await transaction(pool, async (client) => {
96
+ await inPoolTransaction(pool, async (client) => {
135
97
  for (const statement of m.statements) await client.query(statement);
136
98
  });
137
99
  }
package/src/index.ts CHANGED
@@ -1,33 +1,30 @@
1
- import { createSql as createCoreSql, type RowKeys, type Sql, type SqlOptions } from "@typesql/core";
2
- import { executor, type PgQueryable } from "./client.js";
1
+ import { createTransactionalSql, type RowKeys, type SqlOptions, type TransactionalSql } from "@typesql/core";
2
+ import { driver, type PgQueryable } from "./client.js";
3
3
  import type { PgTypeMap } from "./type-map.js";
4
4
 
5
- export { NoResultError, type Executor } from "@typesql/core";
5
+ export { NoResultError, type Executor, type TransactionalSql } from "@typesql/core";
6
6
  export type { PgInterval, PgParamTypeMap, PgTypeMap } from "./type-map.js";
7
7
  export {
8
- query,
9
- one,
10
- maybeOne,
11
- many,
12
- execute,
13
8
  executor,
14
- transaction,
15
9
  migrate,
16
10
  type MigrationSource,
17
- RowCountError,
18
11
  type PgQueryable,
19
12
  type PgResult,
20
13
  type PgTransactionClient,
21
14
  type PgPoolLike,
22
15
  } from "./client.js";
23
16
 
17
+ /** What `createSql<DB>(client)` returns: core's `Sql` under `PgTypeMap`, with `transaction(fn)`. */
18
+ export type PgSql<DB, Keys extends RowKeys = "preserve"> = TransactionalSql<DB, PgTypeMap, Keys>;
19
+
24
20
  /**
25
21
  * `createSql<DB>()` from core, pre-bound to `PgTypeMap` (`count(*)` is `string`, `int8` is `string`, dates are `Date`).
26
- * With a `client` (a `Pool`, `Client` or `PoolClient`), the queries' `execute*` methods run on it.
22
+ * The queries' `execute*` methods run on `client` (a `Pool`, `Client` or `PoolClient`), and
23
+ * `sql.transaction(fn)` runs `fn` with a `sql` bound to one transaction.
27
24
  * `{ rowKeys: "camelCase" }` camelCases the rows' keys, in the types and at runtime.
28
25
  */
29
- export function createSql<DB>(client: PgQueryable | undefined, options: { readonly rowKeys: "camelCase" }): Sql<DB, PgTypeMap, "camelCase">;
30
- export function createSql<DB>(client?: PgQueryable, options?: { readonly rowKeys?: "preserve" }): Sql<DB, PgTypeMap>;
31
- export function createSql<DB>(client?: PgQueryable, options?: SqlOptions): Sql<DB, PgTypeMap, RowKeys> {
32
- return createCoreSql<DB, PgTypeMap>(client && executor(client), options as { readonly rowKeys: "camelCase" });
26
+ export function createSql<DB>(client: PgQueryable | undefined, options: { readonly rowKeys: "camelCase" }): PgSql<DB, "camelCase">;
27
+ export function createSql<DB>(client?: PgQueryable, options?: { readonly rowKeys?: "preserve" }): PgSql<DB>;
28
+ export function createSql<DB>(client?: PgQueryable, options?: SqlOptions): PgSql<DB, RowKeys> {
29
+ return createTransactionalSql<DB, PgTypeMap, PgQueryable>(client, driver, options);
33
30
  }