kysely-ddl 0.2.0 → 0.3.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 +17 -0
- package/README.md +40 -8
- package/dist/index.d.ts +1 -1
- package/dist/kysely/infer.d.ts +40 -12
- package/dist/table/columns.d.ts +27 -5
- package/dist/table/columns.js +5 -1
- package/dist/table/define.d.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ver
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.3.0] — 2026-09-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `{ bigint: true }` in the infer options: `bigint()` columns as `bigint` and
|
|
12
|
+
`bigint().array()` columns as `bigint[]`, for a driver that returns int8 that
|
|
13
|
+
way (`Bun.SQL` with `{ bigint: true }`, `pg` with a type parser for oid 20 and,
|
|
14
|
+
for arrays, 1016). A column with `$type<T>()` keeps `T`. The `InferOptions`
|
|
15
|
+
type is exported.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Breaking:** the second type parameter of `inferKyselyTable` and
|
|
20
|
+
`inferKyselyDatabase` is an options object instead of a boolean:
|
|
21
|
+
`inferKyselyDatabase<typeof schema, true>` becomes
|
|
22
|
+
`inferKyselyDatabase<typeof schema, { camelCase: true }>`.
|
|
23
|
+
|
|
7
24
|
## [0.2.0] — 2026-09-12
|
|
8
25
|
|
|
9
26
|
### Added
|
package/README.md
CHANGED
|
@@ -134,8 +134,9 @@ config second: `t.varchar('title', { length: 200 })`.
|
|
|
134
134
|
|
|
135
135
|
TypeScript value types match what the driver returns, without modes like
|
|
136
136
|
`bigint({ mode })`: `bigint` and `numeric` are strings (no precision loss),
|
|
137
|
-
`timestamp` is `Date`, `jsonb` is `unknown`.
|
|
138
|
-
|
|
137
|
+
`timestamp` is `Date`, `jsonb` is `unknown`. A driver told to return int8 as
|
|
138
|
+
`bigint` is matched by the `bigint: true` infer option, see "Types for Kysely".
|
|
139
|
+
For another type use `$type<T>()` and convert on your side.
|
|
139
140
|
|
|
140
141
|
### `enum([...])`
|
|
141
142
|
|
|
@@ -206,9 +207,10 @@ type NewUser = Insertable<UserTable>;
|
|
|
206
207
|
```
|
|
207
208
|
|
|
208
209
|
Keys are **the column names in the database**: they are known at the type level
|
|
209
|
-
because `defineTable` keeps them as literals.
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
because `defineTable` keeps them as literals. The second parameter of both types
|
|
211
|
+
is an options object, `InferOptions`: `{ camelCase: true }` for Kysely's
|
|
212
|
+
`CamelCasePlugin`, `{ bigint: true }` for a driver that returns int8 as `bigint`;
|
|
213
|
+
both are described below, and they combine. Write rules:
|
|
212
214
|
|
|
213
215
|
| column | `Insertable` |
|
|
214
216
|
|---|---|
|
|
@@ -221,13 +223,13 @@ camelCase, see below. Write rules:
|
|
|
221
223
|
### CamelCasePlugin
|
|
222
224
|
|
|
223
225
|
`CamelCasePlugin` rewrites camelCase in code to snake_case in SQL and back in
|
|
224
|
-
result row keys. To have the types reflect that,
|
|
225
|
-
|
|
226
|
+
result row keys. To have the types reflect that, pass `{ camelCase: true }` to
|
|
227
|
+
either `infer` type:
|
|
226
228
|
|
|
227
229
|
```ts
|
|
228
230
|
import { CamelCasePlugin, Kysely } from 'kysely';
|
|
229
231
|
|
|
230
|
-
type DB = inferKyselyDatabase<typeof schema, true>;
|
|
232
|
+
type DB = inferKyselyDatabase<typeof schema, { camelCase: true }>;
|
|
231
233
|
const db = new Kysely<DB>({ dialect, plugins: [new CamelCasePlugin()] });
|
|
232
234
|
|
|
233
235
|
await db.selectFrom('auditLog').select(['userId', 'happenedAt']).execute();
|
|
@@ -246,6 +248,35 @@ leading underscore. Plugin options (`upperCase`, `underscoreBeforeDigits`,
|
|
|
246
248
|
conversions themselves are exported too: the types `SnakeCase`, `CamelCase` and
|
|
247
249
|
the functions `toSnakeCase`, `toCamelCase`.
|
|
248
250
|
|
|
251
|
+
### bigint: when the driver returns `bigint`
|
|
252
|
+
|
|
253
|
+
`bigint()` columns are strings in the types because that is what `pg` and
|
|
254
|
+
`Bun.SQL` return for int8 by default. Both can return `bigint` instead, and then
|
|
255
|
+
the types follow with `{ bigint: true }`: `bigint()` columns read and write as
|
|
256
|
+
`bigint`, `bigint().array()` columns as `bigint[]`, nullable ones as
|
|
257
|
+
`bigint | null`. A column with `$type<T>()` stays `T`, and `numeric` stays a
|
|
258
|
+
string either way.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
// Bun.SQL: its own option
|
|
262
|
+
const sql = new SQL(process.env.DATABASE_URL, { bigint: true });
|
|
263
|
+
|
|
264
|
+
// pg: a parser for int8 (oid 20); int8[] (oid 1016) is parsed separately and has no entry in pg.types.builtins
|
|
265
|
+
pg.types.setTypeParser(pg.types.builtins.INT8, BigInt);
|
|
266
|
+
const INT8_ARRAY = 1016 as Parameters<typeof pg.types.getTypeParser>[0];
|
|
267
|
+
const parseInt8Array = pg.types.getTypeParser(INT8_ARRAY) as (value: string) => (string | null)[];
|
|
268
|
+
pg.types.setTypeParser(INT8_ARRAY, value => parseInt8Array(value).map(item => (item === null ? null : BigInt(item))));
|
|
269
|
+
|
|
270
|
+
type DB = inferKyselyDatabase<typeof schema, { bigint: true }>;
|
|
271
|
+
// together with CamelCasePlugin: { camelCase: true, bigint: true }
|
|
272
|
+
|
|
273
|
+
const row = await db.selectFrom('ledger').select(['amount', 'history']).executeTakeFirstOrThrow();
|
|
274
|
+
// ^? { amount: bigint; history: bigint[] }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Values past 2^53 stay exact on both drivers, in parameters as well as in
|
|
278
|
+
results, which is the point of `bigint` over `number`.
|
|
279
|
+
|
|
249
280
|
### jsonb: values through `jsonb()` and `jsonbArray()`
|
|
250
281
|
|
|
251
282
|
Drivers disagree on how a jsonb parameter should be passed, and there is no raw
|
|
@@ -465,6 +496,7 @@ What the checks showed on `pg` 8.23 and `Bun.SQL` 1.4 (parameters via Kysely):
|
|
|
465
496
|
| array of objects -> `jsonb[]` | ok | error: arrays are not encoded | `jsonbArray([...])` |
|
|
466
497
|
| array of strings -> `varchar[]` | ok, including commas, quotes, `null` | error: elements joined with commas | a Bun dialect must encode arrays into literals itself, as the test one does |
|
|
467
498
|
| `numeric` = 0 on read | `'0.00'` | `'0'` | compare as numbers |
|
|
499
|
+
| `int8` on read | `'42'`; `42n` with a parser for oid 20, and 1016 for `int8[]` | `'42'`; `42n` with `{ bigint: true }` | the `bigint: true` infer option once the driver returns `bigint` |
|
|
468
500
|
|
|
469
501
|
Reading jsonb, JSON arrays, `jsonb[]` and `varchar[]` yields parsed JS values
|
|
470
502
|
with both drivers.
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type { GenerateResult } from './generator/generate.ts';
|
|
|
27
27
|
export { renderChange, renderChanges, renderConcurrentStatements, renderStatements } from './generator/render.ts';
|
|
28
28
|
export { buildSnapshot, EMPTY_SNAPSHOT, SNAPSHOT_VERSION } from './generator/snapshot.ts';
|
|
29
29
|
export type { ColumnSnapshot, Snapshot, TableSnapshot } from './generator/snapshot.ts';
|
|
30
|
-
export type { inferKyselyDatabase, inferKyselyTable } from './kysely/infer.ts';
|
|
30
|
+
export type { inferKyselyDatabase, inferKyselyTable, InferOptions } from './kysely/infer.ts';
|
|
31
31
|
export { jsonb, jsonbArray } from './kysely/json.ts';
|
|
32
32
|
export type { Jsonb } from './kysely/json.ts';
|
|
33
33
|
export { CONCURRENTLY_SUFFIX, listMigrations, MIGRATION_EXTENSION, migrationTimestamp, NO_TRANSACTION_MARKER, readLatestSnapshot, readMigration, readStatements, SNAPSHOT_FILE, STATEMENT_SEPARATOR, writeMigration, } from './migrator/store.ts';
|
package/dist/kysely/infer.d.ts
CHANGED
|
@@ -12,13 +12,17 @@
|
|
|
12
12
|
* because `defineTable` keeps them as literals, so the bridge does not depend on
|
|
13
13
|
* casing plugins or on conventions shared between two libraries.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
* the column and table keys then become camelCase versions of the database names,
|
|
17
|
-
* exactly as the plugin rewrites them with default options:
|
|
15
|
+
* The second parameter is an options object, `InferOptions`, and the options combine:
|
|
18
16
|
*
|
|
19
17
|
* ```ts
|
|
20
|
-
*
|
|
18
|
+
* // Kysely runs with CamelCasePlugin: column and table keys become camelCase,
|
|
19
|
+
* // exactly as the plugin rewrites them with default options
|
|
20
|
+
* type DB = inferKyselyDatabase<typeof schema, { camelCase: true }>;
|
|
21
21
|
* const db = new Kysely<DB>({ dialect, plugins: [new CamelCasePlugin()] });
|
|
22
|
+
*
|
|
23
|
+
* // the driver returns int8 as BigInt (Bun.SQL with `{ bigint: true }`, pg with a
|
|
24
|
+
* // type parser): bigint() columns become bigint, bigint().array() ones bigint[]
|
|
25
|
+
* type DB = inferKyselyDatabase<typeof schema, { bigint: true }>;
|
|
22
26
|
* ```
|
|
23
27
|
*
|
|
24
28
|
* `kysely` is imported as a type only; there is no runtime dependency.
|
|
@@ -27,18 +31,42 @@ import type { CamelCase } from '../table/casing.ts';
|
|
|
27
31
|
import type { ResolvedColumnCfg, Table } from '../table/define.ts';
|
|
28
32
|
import type { Jsonb } from './json.ts';
|
|
29
33
|
import type { ColumnType } from 'kysely';
|
|
34
|
+
/** The second parameter of `inferKyselyTable` and `inferKyselyDatabase`. Everything is off by default. */
|
|
35
|
+
export interface InferOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Column and table keys as `CamelCasePlugin` rewrites them: `created_at` ->
|
|
38
|
+
* `createdAt`, `audit_log` -> `auditLog`. Otherwise the names in the database.
|
|
39
|
+
*/
|
|
40
|
+
readonly camelCase?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* `bigint()` columns as `bigint` and `bigint().array()` columns as `bigint[]`,
|
|
43
|
+
* for a driver that returns int8 that way: `Bun.SQL` with `{ bigint: true }`,
|
|
44
|
+
* `pg` with a type parser for oid 20 (and 1016 for arrays). Otherwise strings,
|
|
45
|
+
* which is what both drivers return by default. A column with `$type<T>()`
|
|
46
|
+
* stays `T` either way.
|
|
47
|
+
*/
|
|
48
|
+
readonly bigint?: boolean;
|
|
49
|
+
}
|
|
50
|
+
type Camel<O extends InferOptions> = O extends {
|
|
51
|
+
readonly camelCase: true;
|
|
52
|
+
} ? true : false;
|
|
53
|
+
type Big<O extends InferOptions> = O extends {
|
|
54
|
+
readonly bigint: true;
|
|
55
|
+
} ? true : false;
|
|
30
56
|
type Cols<T extends Table> = T['_']['columns'];
|
|
31
57
|
/** The key in the Kysely interface: the database name or, under `CamelCasePlugin`, its camelCase. */
|
|
32
|
-
type Key<Name extends string,
|
|
58
|
+
type Key<Name extends string, O extends InferOptions> = Camel<O> extends true ? CamelCase<Name> : Name;
|
|
59
|
+
/** The JS value of a column: `data` from the builder, or `bigint` for an int8 the driver returns that way. */
|
|
60
|
+
type Data<C extends ResolvedColumnCfg, O extends InferOptions> = C['bigint'] extends true ? Big<O> extends true ? C['array'] extends true ? bigint[] : bigint : C['data'] : C['data'];
|
|
33
61
|
/** What comes back from SELECT. */
|
|
34
|
-
type Select<C extends ResolvedColumnCfg> = C['notNull'] extends true ? C
|
|
62
|
+
type Select<C extends ResolvedColumnCfg, O extends InferOptions> = C['notNull'] extends true ? Data<C, O> : Data<C, O> | null;
|
|
35
63
|
type ElementOf<T> = T extends readonly (infer E)[] ? E : never;
|
|
36
64
|
/**
|
|
37
65
|
* What goes into INSERT / UPDATE before nullability and defaults are applied. For
|
|
38
66
|
* jsonb this is branded JSON text: only `jsonb()` produces it (`jsonbArray()` for
|
|
39
67
|
* `jsonb[]`), a raw object or string does not compile.
|
|
40
68
|
*/
|
|
41
|
-
type Written<C extends ResolvedColumnCfg> = C['json'] extends true ? C['array'] extends true ? Jsonb<ElementOf<C['data']>>[] : Jsonb<C['data']> : C
|
|
69
|
+
type Written<C extends ResolvedColumnCfg, O extends InferOptions> = C['json'] extends true ? C['array'] extends true ? Jsonb<ElementOf<C['data']>>[] : Jsonb<C['data']> : Data<C, O>;
|
|
42
70
|
/**
|
|
43
71
|
* What can be passed for a write:
|
|
44
72
|
* identity always -> not at all;
|
|
@@ -46,16 +74,16 @@ type Written<C extends ResolvedColumnCfg> = C['json'] extends true ? C['array']
|
|
|
46
74
|
* has a default -> optional;
|
|
47
75
|
* nullable -> optional and accepts null.
|
|
48
76
|
*/
|
|
49
|
-
type Insert<C extends ResolvedColumnCfg> = C['identity'] extends 'always' ? never : C['notNull'] extends true ? C['hasDefault'] extends true ? Written<C> | undefined : Written<C> : Written<C> | null | undefined;
|
|
50
|
-
export type inferKyselyTable<T extends Table,
|
|
51
|
-
[K in keyof Cols<T> as Key<Cols<T>[K]['name'],
|
|
77
|
+
type Insert<C extends ResolvedColumnCfg, O extends InferOptions> = C['identity'] extends 'always' ? never : C['notNull'] extends true ? C['hasDefault'] extends true ? Written<C, O> | undefined : Written<C, O> : Written<C, O> | null | undefined;
|
|
78
|
+
export type inferKyselyTable<T extends Table, O extends InferOptions = InferOptions> = {
|
|
79
|
+
[K in keyof Cols<T> as Key<Cols<T>[K]['name'], O>]: ColumnType<Select<Cols<T>[K], O>, Insert<Cols<T>[K], O>, Insert<Cols<T>[K], O>>;
|
|
52
80
|
};
|
|
53
81
|
/**
|
|
54
82
|
* The interface of the whole database from a schema module: the key is the table
|
|
55
83
|
* name, the value is `inferKyselyTable`. Anything that is not a table (constants,
|
|
56
84
|
* types, zod schemas) is filtered out.
|
|
57
85
|
*/
|
|
58
|
-
export type inferKyselyDatabase<TSchema,
|
|
59
|
-
[K in keyof TSchema as TSchema[K] extends Table ? Key<TSchema[K]['_']['name'],
|
|
86
|
+
export type inferKyselyDatabase<TSchema, O extends InferOptions = InferOptions> = {
|
|
87
|
+
[K in keyof TSchema as TSchema[K] extends Table ? Key<TSchema[K]['_']['name'], O> : never]: TSchema[K] extends Table ? inferKyselyTable<TSchema[K], O> : never;
|
|
60
88
|
};
|
|
61
89
|
export {};
|
package/dist/table/columns.d.ts
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* int8 -> string (no precision loss beyond 2^53)
|
|
17
17
|
* numeric -> string (no float error)
|
|
18
18
|
*
|
|
19
|
+
* The one option there is follows the driver instead of replacing it: `pg` and
|
|
20
|
+
* `Bun.SQL` can both be told to return int8 as `bigint`, and `{ bigint: true }` in
|
|
21
|
+
* the infer options (see the kysely layer) types `bigint()` columns to match.
|
|
19
22
|
* For another type use `$type<T>()` plus an explicit conversion on your side.
|
|
20
23
|
*/
|
|
21
24
|
import type { Sql } from './sql.ts';
|
|
@@ -32,6 +35,12 @@ export interface ColumnCfg {
|
|
|
32
35
|
readonly enumValues: readonly string[] | undefined;
|
|
33
36
|
/** A jsonb column: values for writes go through `jsonb()`, see the kysely layer. */
|
|
34
37
|
readonly json: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* An int8 column still typed as the driver's default, a string. `{ bigint: true }`
|
|
40
|
+
* in the infer options reads such a column as `bigint`; `$type<T>()` clears the
|
|
41
|
+
* flag, since an explicit type is final.
|
|
42
|
+
*/
|
|
43
|
+
readonly bigint: boolean;
|
|
35
44
|
}
|
|
36
45
|
/**
|
|
37
46
|
* A targeted config update: what U has overrides T, the rest is carried over.
|
|
@@ -63,6 +72,9 @@ type Update<T extends ColumnCfg, U extends Partial<ColumnCfg>> = {
|
|
|
63
72
|
readonly json: U extends {
|
|
64
73
|
json: infer V extends boolean;
|
|
65
74
|
} ? V : T['json'];
|
|
75
|
+
readonly bigint: U extends {
|
|
76
|
+
bigint: infer V extends boolean;
|
|
77
|
+
} ? V : T['bigint'];
|
|
66
78
|
};
|
|
67
79
|
/** A default: a literal or an SQL expression. */
|
|
68
80
|
export type DefaultValue = Sql | string | number | boolean | null;
|
|
@@ -102,14 +114,19 @@ export declare class ColumnBuilder<T extends ColumnCfg = ColumnCfg> {
|
|
|
102
114
|
data: T['data'][];
|
|
103
115
|
array: true;
|
|
104
116
|
}>>;
|
|
105
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Narrows the value type without touching the column type in the database. The
|
|
119
|
+
* type is final: on a `bigint()` column it also switches the `bigint: true` infer
|
|
120
|
+
* option off.
|
|
121
|
+
*/
|
|
106
122
|
$type<U>(): ColumnBuilder<Update<T, {
|
|
107
123
|
data: U;
|
|
124
|
+
bigint: false;
|
|
108
125
|
}>>;
|
|
109
126
|
}
|
|
110
127
|
export type AnyColumn = ColumnBuilder<ColumnCfg>;
|
|
111
128
|
/** A fresh column: the name is either explicit or taken from the object key. */
|
|
112
|
-
type Fresh<N extends string | undefined, TData, TEnum extends readonly string[] | undefined = undefined, TJson extends boolean = false> = ColumnBuilder<{
|
|
129
|
+
type Fresh<N extends string | undefined, TData, TEnum extends readonly string[] | undefined = undefined, TJson extends boolean = false, TBigint extends boolean = false> = ColumnBuilder<{
|
|
113
130
|
name: N;
|
|
114
131
|
data: TData;
|
|
115
132
|
notNull: false;
|
|
@@ -118,6 +135,7 @@ type Fresh<N extends string | undefined, TData, TEnum extends readonly string[]
|
|
|
118
135
|
identity: undefined;
|
|
119
136
|
enumValues: TEnum;
|
|
120
137
|
json: TJson;
|
|
138
|
+
bigint: TBigint;
|
|
121
139
|
}>;
|
|
122
140
|
declare function uuid<N extends string>(name: N): Fresh<N, string>;
|
|
123
141
|
declare function uuid(): Fresh<undefined, string>;
|
|
@@ -148,9 +166,13 @@ declare function enumColumn<N extends string, const T extends readonly [string,
|
|
|
148
166
|
declare function enumColumn<const T extends readonly [string, ...string[]]>(values: T): Fresh<undefined, T[number], T>;
|
|
149
167
|
declare function integer<N extends string>(name: N): Fresh<N, number>;
|
|
150
168
|
declare function integer(): Fresh<undefined, number>;
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
/**
|
|
170
|
+
* int8. A string in JS by default: that is what `pg` and `Bun.SQL` return, and no
|
|
171
|
+
* precision is lost. A driver told to return `bigint` is matched by the
|
|
172
|
+
* `bigint: true` infer option, which turns these columns into `bigint`.
|
|
173
|
+
*/
|
|
174
|
+
declare function bigint<N extends string>(name: N): Fresh<N, string, undefined, false, true>;
|
|
175
|
+
declare function bigint(): Fresh<undefined, string, undefined, false, true>;
|
|
154
176
|
declare function boolean<N extends string>(name: N): Fresh<N, boolean>;
|
|
155
177
|
declare function boolean(): Fresh<undefined, boolean>;
|
|
156
178
|
interface NumericConfig {
|
package/dist/table/columns.js
CHANGED
|
@@ -28,7 +28,11 @@ export class ColumnBuilder {
|
|
|
28
28
|
array() {
|
|
29
29
|
return this.next({ array: true });
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Narrows the value type without touching the column type in the database. The
|
|
33
|
+
* type is final: on a `bigint()` column it also switches the `bigint: true` infer
|
|
34
|
+
* option off.
|
|
35
|
+
*/
|
|
32
36
|
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- the type parameter is the whole point of the method
|
|
33
37
|
$type() {
|
|
34
38
|
return this;
|
package/dist/table/define.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export type ResolveColumns<TCols extends Record<string, AnyColumn>> = {
|
|
|
44
44
|
readonly identity: TCols[K]['_']['identity'];
|
|
45
45
|
readonly enumValues: TCols[K]['_']['enumValues'];
|
|
46
46
|
readonly json: TCols[K]['_']['json'];
|
|
47
|
+
readonly bigint: TCols[K]['_']['bigint'];
|
|
47
48
|
};
|
|
48
49
|
};
|
|
49
50
|
export type ReferentialAction = 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default';
|