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 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`. For another type use `$type<T>()`
138
- and convert on your side.
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. With Kysely's `CamelCasePlugin`
210
- pass `true` as the second parameter and the column and table keys become
211
- camelCase, see below. Write rules:
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, both `infer` types take `true`
225
- as the second parameter:
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';
@@ -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
- * When Kysely runs with `CamelCasePlugin`, pass `true` as the second parameter:
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
- * type DB = inferKyselyDatabase<typeof schema, true>;
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, Camel extends boolean> = Camel extends true ? CamelCase<Name> : Name;
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['data'] : C['data'] | null;
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['data'];
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, Camel extends boolean = false> = {
51
- [K in keyof Cols<T> as Key<Cols<T>[K]['name'], Camel>]: ColumnType<Select<Cols<T>[K]>, Insert<Cols<T>[K]>, Insert<Cols<T>[K]>>;
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, Camel extends boolean = false> = {
59
- [K in keyof TSchema as TSchema[K] extends Table ? Key<TSchema[K]['_']['name'], Camel> : never]: TSchema[K] extends Table ? inferKyselyTable<TSchema[K], Camel> : never;
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 {};
@@ -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
- /** Narrows the value type without touching the column type in the database. */
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
- /** int8. A string in JS: that is what `pg` returns, and no precision is lost. */
152
- declare function bigint<N extends string>(name: N): Fresh<N, string>;
153
- declare function bigint(): Fresh<undefined, string>;
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 {
@@ -28,7 +28,11 @@ export class ColumnBuilder {
28
28
  array() {
29
29
  return this.next({ array: true });
30
30
  }
31
- /** Narrows the value type without touching the column type in the database. */
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;
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely-ddl",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "PostgreSQL schema as TypeScript code: SQL migrations from snapshot diffs, Kysely table types and a migration runner",
5
5
  "keywords": [
6
6
  "postgres",