@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +24 -181
  3. 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
- Use the `DB` interface you already have for [Kysely](https://kysely.dev) as a TypeSQL schema:
4
- the one generated by [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen), by
5
- [prisma-kysely](https://github.com/valtyr/prisma-kysely), or written by hand. The conversion is
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
- pnpm add @typesql/core @typesql/kysely kysely
8
+ npm install @typesql/core @typesql/kysely kysely
10
9
  ```
11
10
 
12
- [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.
11
+ ## Usage
13
12
 
14
13
  ```ts
15
14
  import { createSql } from "@typesql/kysely";
16
- import type { DB } from "./db"; // kysely-codegen output
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 Transaction<DB>
17
+ const sql = createSql<DB>(db); // db: your Kysely<DB> instance, or a transaction
19
18
 
20
- const q = sql(
21
- `SELECT u.id, u.email, p.title, p.views
22
- FROM users u JOIN posts p ON p.author_id = u.id
23
- WHERE u.id = $1 AND p.published = $2`,
24
- 1, true,
25
- );
26
- // ^? Query<{ id: number; email: string; title: string; views: string }, [number | null, boolean | null]>
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
- ## API
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
- A column whose type is a union of string literals becomes an enum column. Its values go into the
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 do not carry the enum's Postgres name, so by default each column gets its own name,
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 { Mood, AuthRole } from "./db"; // kysely-codegen exports one alias per enum
39
+ import type { DefaultTypeMap } from "@typesql/core";
146
40
 
147
- createSql<DB, DefaultTypeMap, { $types: { mood: Mood; "auth.role": AuthRole } }>(db);
41
+ const sql = createSql<DB, DefaultTypeMap, { posts: { score: "float8" } }>(db);
148
42
  ```
149
43
 
150
- If two `$types` entries have the same values the column keeps its derived name; set the column's
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
- If the interface was generated with `kysely-codegen --camel-case` (and the app uses
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
- ```ts
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
- For camelCase **rows**, add `{ rowKeys: "camelCase" }`. The top-level keys are renamed with
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
- ## Limitations
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.0",
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.5.0"
31
+ "@typesql/core": "0.6.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "kysely": ">=0.27"