@typesql/kysely 0.2.0 → 0.2.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 +9 -0
- package/README.md +24 -181
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @typesql/kysely
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The README is shorter: install, one example and links to the docs site, which now has the details it used to repeat.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies:
|
|
10
|
+
- @typesql/core@0.6.0
|
|
11
|
+
|
|
3
12
|
## 0.2.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,209 +1,52 @@
|
|
|
1
1
|
# @typesql/kysely
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type-level only; nothing is generated and no database is contacted.
|
|
3
|
+
Write [TypeSQL](https://www.npmjs.com/package/@typesql/core) queries against the `DB` interface you already have for [Kysely](https://kysely.dev), and run them on your Kysely instance. The interface can come from [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen), [prisma-kysely](https://github.com/valtyr/prisma-kysely), or be written by hand.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
7
6
|
|
|
8
7
|
```sh
|
|
9
|
-
|
|
8
|
+
npm install @typesql/core @typesql/kysely kysely
|
|
10
9
|
```
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
## Usage
|
|
13
12
|
|
|
14
13
|
```ts
|
|
15
14
|
import { createSql } from "@typesql/kysely";
|
|
16
|
-
import type { DB } from "./db"; //
|
|
15
|
+
import type { DB } from "./db"; // the interface you pass to new Kysely<DB>()
|
|
17
16
|
|
|
18
|
-
const sql = createSql<DB>(db); // db: your Kysely<DB> instance, or a
|
|
17
|
+
const sql = createSql<DB>(db); // db: your Kysely<DB> instance, or a transaction
|
|
19
18
|
|
|
20
|
-
const
|
|
21
|
-
`SELECT u.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
1,
|
|
25
|
-
);
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
const rows = await q.execute();
|
|
19
|
+
const posts = await sql(
|
|
20
|
+
`SELECT u.email, p.title
|
|
21
|
+
FROM users u JOIN posts p ON p.author_id = u.id
|
|
22
|
+
WHERE u.id = $1`,
|
|
23
|
+
1,
|
|
24
|
+
).execute();
|
|
25
|
+
// { email: string; title: string }[]
|
|
29
26
|
|
|
30
27
|
await sql.transaction(async (tx) => {
|
|
31
28
|
await tx("UPDATE posts SET published = true WHERE author_id = $1", 1).execute();
|
|
32
29
|
});
|
|
33
30
|
```
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| Export | What it is |
|
|
38
|
-
|---|---|
|
|
39
|
-
| `createSql<DB, TM = DefaultTypeMap, Overrides = {}, Options = {}>(db, options?)` | core's `createSql<FromKysely<DB, Overrides, Options>, TM>()` with the queries running on `db`; `{ rowKeys: "camelCase" }` gives `CamelCasePlugin`-style row keys |
|
|
40
|
-
| `q.execute()`, `q.executeTakeFirst()`, `q.executeTakeFirstOrThrow()`, `q.executeCount()` | `Promise<Row[]>`, `Promise<Row \| undefined>`, `Promise<Row>` (`NoResultError` on 0 rows), `Promise<number>` (`numAffectedRows`; for other statements the number of rows returned, 0 for DDL) |
|
|
41
|
-
| `sql.transaction(fn)` | `db.transaction().execute(...)` with `tx` bound to the transaction; on a `Transaction`, and for `tx.transaction(fn)`, a savepoint |
|
|
42
|
-
| `FromKysely<DB, Overrides = {}, Options = {}>` | the converted schema type, for use with core's `createSql<Schema, TM>()` or the adapters' `createSql<Schema>(client)` |
|
|
43
|
-
| `KyselySql<DB, TM, Overrides, Options, Keys>` | the type `createSql` returns |
|
|
44
|
-
| `executor(db)` | an `Executor` for `q.execute(executor)` and core's `createSql(executor)`, also bypassing plugins |
|
|
45
|
-
| `InferColumn<T>` | the descriptor inferred for one Kysely column type (handy to check a mapping) |
|
|
46
|
-
| `SnakeCase<S>` | `CamelCasePlugin`'s default camelCase → snake_case mapping, at the type level |
|
|
47
|
-
| `KyselyOverrides`, `TableOverride`, `ColumnOverride`, `KyselyOptions`, `KyselyExecutor` | parameter types |
|
|
48
|
-
|
|
49
|
-
`TM` is the type map for the driver Kysely runs on. Kysely's `PostgresDialect` uses `pg`, so
|
|
50
|
-
`PgTypeMap` from `@typesql/pg` is the exact choice (`Buffer` for `bytea`, interval methods); core's
|
|
51
|
-
`DefaultTypeMap` already matches `pg` otherwise:
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import type { PgTypeMap } from "@typesql/pg";
|
|
55
|
-
import { createSql, type FromKysely } from "@typesql/kysely";
|
|
56
|
-
import { createSql as createCoreSql } from "@typesql/core";
|
|
57
|
-
|
|
58
|
-
const sql = createSql<DB, PgTypeMap>(db);
|
|
59
|
-
const same = createCoreSql<FromKysely<DB>, PgTypeMap>(); // types only, no client
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Queries are sent as `CompiledQuery.raw(q.text, q.values)` through
|
|
63
|
-
`db.withoutPlugins().executeQuery(...)`. Plugins are bypassed on purpose: TypeSQL's row types
|
|
64
|
-
describe what Postgres returns (`created_at`), and a result transformer such as
|
|
65
|
-
`CamelCasePlugin` would rename the keys (use `{ rowKeys: "camelCase" }` for camelCase rows).
|
|
66
|
-
Logging, the dialect and the connection pool are unaffected. Inside a Kysely transaction,
|
|
67
|
-
`createSql<DB>(trx)` binds a `sql` to it, so builder and SQL queries share the transaction.
|
|
68
|
-
|
|
69
|
-
## How columns are converted
|
|
70
|
-
|
|
71
|
-
Kysely describes a column by its TS type; TypeSQL needs a Postgres type name. The TS shape
|
|
72
|
-
is read back as follows (for `ColumnType<S, I, U>` the select type `S` decides, the insert type
|
|
73
|
-
`I` breaks ties):
|
|
74
|
-
|
|
75
|
-
| Kysely column type | Postgres type | Notes |
|
|
76
|
-
|---|---|---|
|
|
77
|
-
| `number` | `int4` | also what codegen emits for `int2`, `float4`, `float8`, `oid`: override those |
|
|
78
|
-
| `string` | `text` | also `varchar`, `uuid`, `time`, `money`, `inet`, …: override if it matters |
|
|
79
|
-
| `boolean` | `bool` | |
|
|
80
|
-
| `bigint` | `int8` | |
|
|
81
|
-
| `Date` | `timestamptz` | |
|
|
82
|
-
| `Buffer` / `Uint8Array` | `bytea` | |
|
|
83
|
-
| `Int8` = `ColumnType<string, bigint \| number \| string, …>` | `int8` | `bigint` in the insert type |
|
|
84
|
-
| `Numeric` = `ColumnType<string, number \| string, …>` | `numeric` | `number` in the insert type |
|
|
85
|
-
| `Timestamp` = `ColumnType<Date, Date \| string, …>` | `timestamptz` | codegen uses `Timestamp` for `date`, `timestamp` and `timestamptz` alike |
|
|
86
|
-
| `Interval` (an object with `days`) | `interval` | |
|
|
87
|
-
| `Point` (`{ x, y }`) / `Circle` (`{ x, y, radius }`) | `point` / `circle` | |
|
|
88
|
-
| `Json` / `JsonValue`, `unknown`, `JSONColumnType<T>`, any other object | `jsonb` | always nullable (see below) |
|
|
89
|
-
| `T[]`, `ArrayType<T>` | element type + `[]` | `number[][]` is `int4[]` too |
|
|
90
|
-
| `"a" \| "b"` (string-literal union, codegen enums, prisma-kysely enums, TS string enums) | an enum | registered in `$types`, see [Enums](#enums) |
|
|
91
|
-
| `T \| null` | nullable | |
|
|
92
|
-
| `Generated<T>` (kysely's or codegen's), `GeneratedAlways<T>` | has a default | optional in `INSERT`; `GeneratedAlways<Int8>` stays `int8` |
|
|
93
|
-
| `"auth.users"` key | `Schema["auth"]["users"]` | query it as `auth.users` |
|
|
94
|
-
| `"public.users"` key | `Schema["users"]` | |
|
|
95
|
-
|
|
96
|
-
Rows then come out through the type map, so `int8` and `numeric` columns are `string`, as
|
|
97
|
-
kysely-codegen declares them and as `pg` returns them.
|
|
98
|
-
|
|
99
|
-
## Overrides
|
|
100
|
-
|
|
101
|
-
The mapping is a best guess. The third type argument (`Overrides`) pins the exact Postgres type of any
|
|
102
|
-
column, keyed by the table key and the column key as they appear in your `DB` interface:
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
const sql = createSql<DB, DefaultTypeMap, {
|
|
106
|
-
users: {
|
|
107
|
-
id: "int8"; // just a type name: nullability and default are kept
|
|
108
|
-
score: "float8";
|
|
109
|
-
birthday: "date";
|
|
110
|
-
created_at: { type: "timestamp"; hasDefault: true }; // or a partial descriptor
|
|
111
|
-
};
|
|
112
|
-
"auth.users": { id: "uuid" };
|
|
113
|
-
active_users: { $view: true }; // a view: INSERT/UPDATE/DELETE are rejected
|
|
114
|
-
$types: { mood: Mood; "auth.role": AuthRole }; // enum names, see below
|
|
115
|
-
}>(db);
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
A descriptor may set any of `type`, `nullable` and `hasDefault`; the others stay inferred.
|
|
119
|
-
Declare the overrides with a `type` alias or inline, not an `interface` (an interface has no
|
|
120
|
-
index signature, so it does not satisfy the `KyselyOverrides` constraint).
|
|
121
|
-
|
|
122
|
-
Worth overriding:
|
|
123
|
-
|
|
124
|
-
- `float4`/`float8` columns (inferred `int4`): `score / 2` is integer division for `int4`, and
|
|
125
|
-
`sum`/`avg` result types differ.
|
|
126
|
-
- `date` and `timestamp` columns (inferred `timestamptz`): `date - date` is an `int4` number of
|
|
127
|
-
days, `timestamptz - date` an `interval`.
|
|
128
|
-
- `uuid`, `varchar`, `time`, `inet`… (inferred `text`): row and parameter types are `string`
|
|
129
|
-
either way; override when you use functions or operators that exist for that type only.
|
|
130
|
-
- `json` columns (inferred `jsonb`).
|
|
131
|
-
|
|
132
|
-
## Enums
|
|
32
|
+
[Open an example in the TypeScript Playground](https://www.typescriptlang.org/play/#code/PTAEBUE8AcFMGUCKAZUB3AlgFwBagNKQDOsANpKABQACWMsRAjqcANbFmQCUAdKAJoB7AK4AnAh3KgAIgCFQGAHZZYogGYBDAMawq7EuQC0WwQBNYAc1iLQIrNGFZbogFAhQODYtOG0o7CqKXApEoLi6RFo4sAC2GgA0oF6moKIaaKBIqKLCiqGCNpAi4oQGFEpEWF46fAASggBuqmHRqQzCpFhEPC4uGDHQgqJOAN6gWqKwGirwzKAAvqBqooIxoABEtPRMLPqc6wDcfQNDoy4AkHRwoADCgqTCMYpQcPEXV7oA4taq07Cmb3OpU4gIACoJKhZJkRpBgNKRYFosG9FstVhs9uRDsdBsNQGNwfcFksVmt1tALNi3GBMZBjGZLNZbI4HMiwv4YjF-i4PqAAJLKAAcoAAvLd7o9nvQADwXSr+RQWQEAIwwFiUTgAPqBFI9lc1tfKlEqLqr1cpQNrdTF9eJDVgFRYXAA+I688D9BhVAai8UPJ4vWDS6R-RIhlSW0BGxVhv6R6MWV08+igACygjMvvWnmg0Eg60j6yIGlMVI1qk0Ohk8hGF2EJFERAAXPiLucMKYW99FL8VKZpdbbUnzudYhoMKQWwmjiOYhnO2n55HdaRSDPzhMpn2APrTLs-NJ96UermVDQDYfzGeDSrN1sjjv7nuH-7SgVYQXD84aRw4Ibbx8dT1VR1ywbAESnB1jXXBxlVIDAiGiBdu17V9lQzBEvEvI55l6Ew8icUxlV9HsMmBchg1kZ1KFrc5TDhBEkRbUjQHBSFoVheFESwGjQEGe5mNgDJCVIShgnmLgUS4I58MqKM5jFTc-lmUhKOoojpN6dxwFaRhhFUChlWECdzHENQhnQTwnGwUBTEEBh0DIUhEiyJYLPCNpKkSKJEVYf4kgsccCJaCJz10Ko4IYHpZMItI1C6X10nHQjlQuHgDG4gAxUlKHJCEunWLg0oypFKAAbXWDt1kSdYwKwBF1gAXSK84eDQaJJly2D4MQ-5qo2EV+s0UgSBanhYAAD0RRxYDEo4XBi0BMNM9CNFEFIxR2SgLgAA3gABRZB9pucBQGEca4gnRJzrnMxvJEZRKGgHgO2CABBeA+Pyog3lAP7-oBwGgeB-60kUVgxNAAB5AA1faACUqCh+HpAR0BZH4cYHp457Xpkfb4Bud7PugUhtFgFwQap4HMvhqHUzOhtQmEUAjsy06ACkob5AA5L7bz46G+een9cH-DtfXOiW3p56Q+J4bqEKQynqdVv6AHVagR-azp4W6NtAAASABGFW1epz46YAVVBdHMal0wdsBbMNFzfM3k0xawb8g2kps5bVFW9bxqmrQZrm6kAYAPQAfi0sABSIDtdA0CQynZLxiyRDACkSVVvCSeTSDCQQFCcLBS5iDAJtAIyTOaZJMhQUA9NUDAopcP2Up4B1M+0MCCjEkPppUSgNCISBFC0KgHQm4IRWde8u-ZCa2x4YRoFMP5wA0SKuu+wq15IHixkV3qFwdfSFha1r2tUWb1lFv9RAA0sasGxJjZv4ew9HzTvzQMlcYkxlLMDUpQWeXBtojh2qjI64Ada03pvzBKmttZJF-OLA2JtQAyzljzKGp0z5ISdm2Y2gJeCTRHrNTSEl5pbT2odY6p0JZIIZvWVQoQ0Hwx1vrX0JsnYbC8FCfM0lQDuFUCsUQzFBBOFTg0eEV9BBqBCqAPh1hHguCAA) <!-- playground:kysely --> to hover the types and edit the SQL, with nothing to install.
|
|
133
33
|
|
|
134
|
-
|
|
135
|
-
schema's `$types`, so rows and parameters have the literal union (`"happy" | "sad"`), and a
|
|
136
|
-
string literal in SQL is accepted where Postgres accepts it.
|
|
34
|
+
## Pin column types
|
|
137
35
|
|
|
138
|
-
Kysely types
|
|
139
|
-
`table.column` (`users.mood`, `auth.users.role`). That is enough for reading and filtering, but
|
|
140
|
-
two columns of the same enum are then different types: `SELECT mood FROM users UNION SELECT mood
|
|
141
|
-
FROM posts` is rejected, like it would be for two distinct Postgres enums. Name your enums once
|
|
142
|
-
under `$types` and every column whose values are **exactly** that union gets the name:
|
|
36
|
+
Kysely describes columns with TypeScript types, so TypeSQL guesses the Postgres type: `number` is `int4`, `string` is `text`, `Date` is `timestamptz`. Rows come out right either way, but division, aggregates and date arithmetic can differ. Pin the columns where it matters:
|
|
143
37
|
|
|
144
38
|
```ts
|
|
145
|
-
import type {
|
|
39
|
+
import type { DefaultTypeMap } from "@typesql/core";
|
|
146
40
|
|
|
147
|
-
createSql<DB, DefaultTypeMap, {
|
|
41
|
+
const sql = createSql<DB, DefaultTypeMap, { posts: { score: "float8" } }>(db);
|
|
148
42
|
```
|
|
149
43
|
|
|
150
|
-
|
|
151
|
-
type explicitly (`{ users: { mood: "mood" } }`). A column override with a non-builtin name also
|
|
152
|
-
registers the column's union under that name; an override with a built-in name (`"text"`) drops
|
|
153
|
-
the union.
|
|
154
|
-
|
|
155
|
-
## camelCase
|
|
44
|
+
For an interface generated with `kysely-codegen --camel-case`, pass `{ camelCase: true }` as the fourth type argument.
|
|
156
45
|
|
|
157
|
-
|
|
158
|
-
`CamelCasePlugin`), pass `{ camelCase: true }` as the options. Table and column keys are mapped
|
|
159
|
-
back to the database's snake_case names the way `CamelCasePlugin` does by default (`createdAt` →
|
|
160
|
-
`created_at`, `userID` → `user_id`), so the SQL you write and the rows you get use the real names:
|
|
46
|
+
## Documentation
|
|
161
47
|
|
|
162
|
-
|
|
163
|
-
const sql = createSql<DB, DefaultTypeMap, {}, { camelCase: true }>(db);
|
|
164
|
-
const q = sql("SELECT author_id, created_at FROM blog_posts WHERE id = $1", 1);
|
|
165
|
-
// ^? Query<{ author_id: number; created_at: Date }, [number | null]>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Overrides still use the interface's (camelCase) keys. The plugin's non-default options
|
|
169
|
-
(`upperCase`, `underscoreBeforeDigits`, `underscoreBetweenUppercaseLetters`) are not modelled.
|
|
48
|
+
See [From Kysely, Drizzle or Prisma](https://typesql.karsli.net/guide/orms#kysely) for how columns are converted, overrides, enums and camelCase, and [Limits](https://typesql.karsli.net/guide/limits#orm-schemas) for what the conversion can't tell.
|
|
170
49
|
|
|
171
|
-
|
|
172
|
-
`CamelCasePlugin`'s default snake_case → camelCase mapping, in the row types and at runtime.
|
|
173
|
-
The plugin itself stays bypassed, so keys inside JSON values are left alone:
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
const sql = createSql<DB, DefaultTypeMap, {}, { camelCase: true }>(db, { rowKeys: "camelCase" });
|
|
177
|
-
const post = await sql("SELECT author_id, created_at FROM blog_posts WHERE id = $1", 1).executeTakeFirstOrThrow();
|
|
178
|
-
// ^? { authorId: number; createdAt: Date }
|
|
179
|
-
```
|
|
50
|
+
## License
|
|
180
51
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
- **The TS → Postgres mapping is lossy.** `number`, `string` and `Date` each stand for several
|
|
184
|
-
Postgres types; see [Overrides](#overrides). Row types are still right for the inferred type
|
|
185
|
-
(a `float8` inferred as `int4` is `number` either way); the difference shows in expression
|
|
186
|
-
typing (division, aggregates, operators that exist for one type only).
|
|
187
|
-
- **JSON columns are always nullable.** kysely-codegen's `JsonValue` (and prisma-kysely's
|
|
188
|
-
`unknown`) include `null`, so `Json` and `Json | null` cannot be told apart. The row type is
|
|
189
|
-
`unknown` either way; only `INSERT` stops requiring the column. Override with
|
|
190
|
-
`{ nullable: false }` to get the not-null check back.
|
|
191
|
-
- **JSON shapes are not kept.** `JSONColumnType<{ a: number }>` becomes `jsonb`, read as
|
|
192
|
-
`unknown` (the type map decides; TypeSQL has no per-column TS types).
|
|
193
|
-
- **Views look like tables.** Kysely interfaces do not mark views; mark them with `$view: true`
|
|
194
|
-
to have writes rejected.
|
|
195
|
-
- **Defaults come from `Generated`/`GeneratedAlways` only.** A column with a default that the
|
|
196
|
-
interface declares as a plain type is required in `INSERT`; override `{ hasDefault: true }`.
|
|
197
|
-
- kysely-codegen options that change the select types (`--date-parser string`,
|
|
198
|
-
`--numeric-parser number`, …) change the inference accordingly (e.g. dates as `string` become
|
|
199
|
-
`text`): override the affected columns.
|
|
200
|
-
- Domains, ranges, `tsvector`, network and geometric types other than `point`/`circle` are
|
|
201
|
-
emitted by codegen as `string` and become `text`.
|
|
202
|
-
|
|
203
|
-
The conversion is a flat mapped type per table with a fixed number of conditional types per
|
|
204
|
-
column; converting a 50-table, 600-column schema costs about 20k type instantiations (~10 ms),
|
|
205
|
-
next to the 100k+ that core spends on its first query.
|
|
206
|
-
|
|
207
|
-
## Examples
|
|
208
|
-
|
|
209
|
-
[`examples/basic.ts`](examples/basic.ts) walks through the features above; it is type-checked with the package (`pnpm typecheck`).
|
|
52
|
+
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typesql/kysely",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Use Kysely database interfaces (kysely-codegen, prisma-kysely) as a TypeSQL schema.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@typesql/core": "0.
|
|
31
|
+
"@typesql/core": "0.6.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"kysely": ">=0.27"
|