@typesql/pg 0.0.4 → 0.1.1
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 +22 -0
- package/README.md +20 -23
- package/dist/bin.js +1 -1
- package/dist/client.d.ts +4 -29
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +36 -72
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/introspect/cli.js +1 -1
- package/dist/introspect/emit.d.ts +1 -1
- package/dist/introspect/emit.js +3 -3
- package/dist/introspect/introspect.d.ts +1 -1
- package/dist/introspect/introspect.js +1 -1
- package/package.json +3 -3
- package/src/bin.ts +1 -1
- package/src/client.ts +37 -75
- package/src/index.ts +12 -15
- package/src/introspect/cli.ts +1 -1
- package/src/introspect/emit.ts +3 -3
- package/src/introspect/introspect.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @typesql/pg
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Docs, package descriptions and error messages spell the project name TypeSQL.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies:
|
|
10
|
+
- @typesql/core@0.1.1
|
|
11
|
+
|
|
12
|
+
## 0.1.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- 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.
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- The README links to the type map tables on the new node-postgres and postgres.js adapter pages of the docs.
|
|
21
|
+
|
|
22
|
+
- Updated dependencies:
|
|
23
|
+
- @typesql/core@0.1.0
|
|
24
|
+
|
|
3
25
|
## 0.0.4
|
|
4
26
|
|
|
5
27
|
### 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,
|
|
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
|
|
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
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
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(
|
|
28
|
-
const { id } = await
|
|
29
|
-
await
|
|
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>(
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
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
|
-
| `
|
|
44
|
+
| `NoResultError`, `PgQueryable`, `PgPoolLike`, `PgTransactionClient`, `PgResult`, `MigrationSource` | errors and parameter types |
|
|
49
45
|
|
|
50
|
-
`client` is anything with `query(text, values)
|
|
51
|
-
|
|
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
|
-
|
|
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/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/** `typesql`: `typesql introspect` writes the
|
|
2
|
+
/** `typesql`: `typesql introspect` writes the TypeSQL schema of a live database. */
|
|
3
3
|
import { main } from "./introspect/cli.js";
|
|
4
4
|
process.exitCode = await main(process.argv.slice(2));
|
|
5
5
|
//# sourceMappingURL=bin.js.map
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Executor, type
|
|
2
|
-
/** The subset of a `pg` query result the
|
|
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
|
-
/**
|
|
38
|
-
export declare
|
|
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
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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 {
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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`
|
|
65
|
-
*
|
|
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
|
-
|
|
69
|
-
const
|
|
25
|
+
async function inPoolTransaction(pool, fn) {
|
|
26
|
+
const conn = await pool.connect();
|
|
70
27
|
let releaseWith;
|
|
71
28
|
try {
|
|
72
|
-
await
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
62
|
+
await inPoolTransaction(pool, async (client) => {
|
|
99
63
|
for (const statement of m.statements)
|
|
100
64
|
await client.query(statement);
|
|
101
65
|
});
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { type
|
|
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 {
|
|
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
|
-
*
|
|
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
|
-
}):
|
|
17
|
+
}): PgSql<DB, "camelCase">;
|
|
15
18
|
export declare function createSql<DB>(client?: PgQueryable, options?: {
|
|
16
19
|
readonly rowKeys?: "preserve";
|
|
17
|
-
}):
|
|
20
|
+
}): PgSql<DB>;
|
|
18
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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 {
|
|
2
|
-
import {
|
|
1
|
+
import { createTransactionalSql } from "@typesql/core";
|
|
2
|
+
import { driver } from "./client.js";
|
|
3
3
|
export { NoResultError } from "@typesql/core";
|
|
4
|
-
export {
|
|
4
|
+
export { executor, migrate, } from "./client.js";
|
|
5
5
|
export function createSql(client, options) {
|
|
6
|
-
return
|
|
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,
|
|
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/dist/introspect/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { generateSource } from "./emit.js";
|
|
|
6
6
|
import { introspect } from "./introspect.js";
|
|
7
7
|
const USAGE = `Usage: typesql introspect [options]
|
|
8
8
|
|
|
9
|
-
Reads a live Postgres database and writes its
|
|
9
|
+
Reads a live Postgres database and writes its TypeSQL schema (tables, views, enums).
|
|
10
10
|
|
|
11
11
|
Options:
|
|
12
12
|
--url <url> connection string (default: $DATABASE_URL)
|
|
@@ -22,7 +22,7 @@ export interface GenerateOptions {
|
|
|
22
22
|
readonly comments?: boolean;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* TypeScript source declaring the
|
|
25
|
+
* TypeScript source declaring the TypeSQL schema of an introspected database: `public`
|
|
26
26
|
* relations at the top level, other schemas nested (`DB["auth"]["sessions"]`), views and
|
|
27
27
|
* materialized views as `View<…>`, enums under `$types`.
|
|
28
28
|
*/
|
package/dist/introspect/emit.js
CHANGED
|
@@ -25,7 +25,7 @@ function columnNotes(c) {
|
|
|
25
25
|
return notes;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* TypeScript source declaring the
|
|
28
|
+
* TypeScript source declaring the TypeSQL schema of an introspected database: `public`
|
|
29
29
|
* relations at the top level, other schemas nested (`DB["auth"]["sessions"]`), views and
|
|
30
30
|
* materialized views as `View<…>`, enums under `$types`.
|
|
31
31
|
*/
|
|
@@ -102,13 +102,13 @@ export function generateSource(db, options = {}) {
|
|
|
102
102
|
}
|
|
103
103
|
for (const [schema, rels] of namespaces) {
|
|
104
104
|
if (topLevel.has(schema)) {
|
|
105
|
-
throw new Error(`schema "${schema}" has the same name as the table "public.${schema}":
|
|
105
|
+
throw new Error(`schema "${schema}" has the same name as the table "public.${schema}": TypeSQL keeps public relations and schemas in one object`);
|
|
106
106
|
}
|
|
107
107
|
const body = rels.map((r) => `${relDoc(r, " ")} ${key(r.name)}: ${relation(r, " ")};`);
|
|
108
108
|
lines.push(` ${key(schema)}: {\n${body.join("\n")}\n };`);
|
|
109
109
|
}
|
|
110
110
|
if (db.enums.length > 0) {
|
|
111
|
-
//
|
|
111
|
+
// TypeSQL's enum registry is flat (`Col<"mood">`, schema qualifiers are dropped), so enums
|
|
112
112
|
// of the same name in different schemas share an entry.
|
|
113
113
|
const byName = new Map();
|
|
114
114
|
for (const e of db.enums) {
|
|
@@ -4,7 +4,7 @@ export type RelationKind = "table" | "partitioned table" | "view" | "materialize
|
|
|
4
4
|
export interface IntrospectedColumn {
|
|
5
5
|
readonly name: string;
|
|
6
6
|
/**
|
|
7
|
-
* Canonical Postgres type name as
|
|
7
|
+
* Canonical Postgres type name as TypeSQL spells it: `int4`, `timestamptz`, `varchar`, an enum
|
|
8
8
|
* name, `${T}[]` for arrays. Domains are replaced by their base type; `citext` is `text`.
|
|
9
9
|
*/
|
|
10
10
|
readonly type: string;
|
|
@@ -7,7 +7,7 @@ const KINDS = {
|
|
|
7
7
|
m: "materialized view",
|
|
8
8
|
f: "foreign table",
|
|
9
9
|
};
|
|
10
|
-
/** Postgres type names
|
|
10
|
+
/** Postgres type names TypeSQL spells differently. */
|
|
11
11
|
const TYPE_ALIASES = { citext: "text" };
|
|
12
12
|
function resolver(types) {
|
|
13
13
|
const resolve = (oid) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typesql/pg",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "node-postgres (pg) adapter for
|
|
3
|
+
"version": "0.1.1",
|
|
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": {
|
|
7
7
|
"type": "git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@typesql/core": "0.
|
|
38
|
+
"@typesql/core": "0.1.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"pg": ">=8"
|
package/src/bin.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/** `typesql`: `typesql introspect` writes the
|
|
2
|
+
/** `typesql`: `typesql introspect` writes the TypeSQL schema of a live database. */
|
|
3
3
|
import { main } from "./introspect/cli.js";
|
|
4
4
|
|
|
5
5
|
process.exitCode = await main(process.argv.slice(2));
|
package/src/client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runTransaction, type Executor, type SqlDriver } from "@typesql/core";
|
|
2
2
|
|
|
3
|
-
/** The subset of a `pg` query result the
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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`
|
|
95
|
-
*
|
|
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
|
-
|
|
99
|
-
pool
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
*
|
|
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" }):
|
|
30
|
-
export function createSql<DB>(client?: PgQueryable, options?: { readonly rowKeys?: "preserve" }):
|
|
31
|
-
export function createSql<DB>(client?: PgQueryable, options?: SqlOptions):
|
|
32
|
-
return
|
|
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
|
}
|
package/src/introspect/cli.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface IntrospectConfig extends GenerateOptions, IntrospectOptions {
|
|
|
16
16
|
|
|
17
17
|
const USAGE = `Usage: typesql introspect [options]
|
|
18
18
|
|
|
19
|
-
Reads a live Postgres database and writes its
|
|
19
|
+
Reads a live Postgres database and writes its TypeSQL schema (tables, views, enums).
|
|
20
20
|
|
|
21
21
|
Options:
|
|
22
22
|
--url <url> connection string (default: $DATABASE_URL)
|
package/src/introspect/emit.ts
CHANGED
|
@@ -51,7 +51,7 @@ function columnNotes(c: IntrospectedColumn): string[] {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* TypeScript source declaring the
|
|
54
|
+
* TypeScript source declaring the TypeSQL schema of an introspected database: `public`
|
|
55
55
|
* relations at the top level, other schemas nested (`DB["auth"]["sessions"]`), views and
|
|
56
56
|
* materialized views as `View<…>`, enums under `$types`.
|
|
57
57
|
*/
|
|
@@ -126,14 +126,14 @@ export function generateSource(db: IntrospectedDatabase, options: GenerateOption
|
|
|
126
126
|
}
|
|
127
127
|
for (const [schema, rels] of namespaces) {
|
|
128
128
|
if (topLevel.has(schema)) {
|
|
129
|
-
throw new Error(`schema "${schema}" has the same name as the table "public.${schema}":
|
|
129
|
+
throw new Error(`schema "${schema}" has the same name as the table "public.${schema}": TypeSQL keeps public relations and schemas in one object`);
|
|
130
130
|
}
|
|
131
131
|
const body = rels.map((r) => `${relDoc(r, " ")} ${key(r.name)}: ${relation(r, " ")};`);
|
|
132
132
|
lines.push(` ${key(schema)}: {\n${body.join("\n")}\n };`);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (db.enums.length > 0) {
|
|
136
|
-
//
|
|
136
|
+
// TypeSQL's enum registry is flat (`Col<"mood">`, schema qualifiers are dropped), so enums
|
|
137
137
|
// of the same name in different schemas share an entry.
|
|
138
138
|
const byName = new Map<string, { values: Set<string>; schemas: string[] }>();
|
|
139
139
|
for (const e of db.enums) {
|
|
@@ -6,7 +6,7 @@ export type RelationKind = "table" | "partitioned table" | "view" | "materialize
|
|
|
6
6
|
export interface IntrospectedColumn {
|
|
7
7
|
readonly name: string;
|
|
8
8
|
/**
|
|
9
|
-
* Canonical Postgres type name as
|
|
9
|
+
* Canonical Postgres type name as TypeSQL spells it: `int4`, `timestamptz`, `varchar`, an enum
|
|
10
10
|
* name, `${T}[]` for arrays. Domains are replaced by their base type; `citext` is `text`.
|
|
11
11
|
*/
|
|
12
12
|
readonly type: string;
|
|
@@ -77,7 +77,7 @@ const KINDS: Readonly<Record<string, RelationKind>> = {
|
|
|
77
77
|
f: "foreign table",
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
/** Postgres type names
|
|
80
|
+
/** Postgres type names TypeSQL spells differently. */
|
|
81
81
|
const TYPE_ALIASES: Readonly<Record<string, string>> = { citext: "text" };
|
|
82
82
|
|
|
83
83
|
interface TypeRow {
|