kysely-ddl 0.1.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.
@@ -0,0 +1,162 @@
1
+ function byName(items) {
2
+ return new Map(items.map(item => [item.name, item]));
3
+ }
4
+ function sameArray(a, b) {
5
+ return a.length === b.length && a.every((v, i) => v === b[i]);
6
+ }
7
+ function sameConstraint(a, b) {
8
+ if (a.type !== b.type) {
9
+ return false;
10
+ }
11
+ if (a.type === 'check' && b.type === 'check') {
12
+ return a.def.expression === b.def.expression;
13
+ }
14
+ if (a.type === 'foreignKey' && b.type === 'foreignKey') {
15
+ return (sameArray(a.def.columns, b.def.columns) &&
16
+ a.def.refTable === b.def.refTable &&
17
+ sameArray(a.def.refColumns, b.def.refColumns) &&
18
+ a.def.onDelete === b.def.onDelete &&
19
+ a.def.onUpdate === b.def.onUpdate);
20
+ }
21
+ if ((a.type === 'primaryKey' || a.type === 'unique') &&
22
+ (b.type === 'primaryKey' || b.type === 'unique')) {
23
+ return sameArray(a.def.columns, b.def.columns);
24
+ }
25
+ return false;
26
+ }
27
+ function sameIndex(a, b) {
28
+ return a.unique === b.unique && sameArray(a.columns, b.columns) && a.where === b.where;
29
+ }
30
+ function sameColumn(a, b) {
31
+ return (a.type === b.type &&
32
+ a.notNull === b.notNull &&
33
+ a.default === b.default &&
34
+ a.identity === b.identity);
35
+ }
36
+ /** All constraints of a table in one list: their names share one namespace. */
37
+ function constraintsOf(table, opts) {
38
+ const list = [];
39
+ if (table.primaryKey !== null) {
40
+ list.push({ type: 'primaryKey', def: table.primaryKey });
41
+ }
42
+ for (const def of table.uniques) {
43
+ list.push({ type: 'unique', def });
44
+ }
45
+ for (const def of table.checks) {
46
+ list.push({ type: 'check', def });
47
+ }
48
+ if (opts.withForeignKeys) {
49
+ for (const def of table.foreignKeys) {
50
+ list.push({ type: 'foreignKey', def });
51
+ }
52
+ }
53
+ return list;
54
+ }
55
+ export function diffSnapshots(prev, next) {
56
+ const prevTables = byName(prev.tables);
57
+ const nextTables = byName(next.tables);
58
+ const dropIndexes = [];
59
+ const dropConstraints = [];
60
+ const createTables = [];
61
+ const addColumns = [];
62
+ const alterColumns = [];
63
+ const addConstraints = [];
64
+ const createIndexes = [];
65
+ const addForeignKeys = [];
66
+ const dropColumns = [];
67
+ const dropTables = [];
68
+ // ── new tables ─────────────────────────────────────────────────────────────
69
+ for (const table of next.tables) {
70
+ if (prevTables.has(table.name)) {
71
+ continue;
72
+ }
73
+ // pk / unique / check go inside CREATE TABLE, fk and indexes separately
74
+ createTables.push({ kind: 'createTable', table });
75
+ for (const index of table.indexes) {
76
+ createIndexes.push({ kind: 'createIndex', table: table.name, index });
77
+ }
78
+ for (const def of table.foreignKeys) {
79
+ addForeignKeys.push({
80
+ kind: 'addConstraint',
81
+ table: table.name,
82
+ constraint: { type: 'foreignKey', def },
83
+ });
84
+ }
85
+ }
86
+ // ── dropped tables ─────────────────────────────────────────────────────────
87
+ for (const table of prev.tables) {
88
+ if (!nextTables.has(table.name)) {
89
+ dropTables.push({ kind: 'dropTable', table: table.name });
90
+ }
91
+ }
92
+ // ── existing tables ────────────────────────────────────────────────────────
93
+ for (const after of next.tables) {
94
+ const before = prevTables.get(after.name);
95
+ if (before === undefined) {
96
+ continue;
97
+ }
98
+ const beforeColumns = byName(before.columns);
99
+ const afterColumns = byName(after.columns);
100
+ for (const column of after.columns) {
101
+ const old = beforeColumns.get(column.name);
102
+ if (old === undefined) {
103
+ addColumns.push({ kind: 'addColumn', table: after.name, column });
104
+ }
105
+ else if (!sameColumn(old, column)) {
106
+ alterColumns.push({ kind: 'alterColumn', table: after.name, from: old, to: column });
107
+ }
108
+ }
109
+ for (const column of before.columns) {
110
+ if (!afterColumns.has(column.name)) {
111
+ dropColumns.push({ kind: 'dropColumn', table: after.name, column: column.name });
112
+ }
113
+ }
114
+ const beforeConstraints = byName(constraintsOf(before, { withForeignKeys: true }).map(c => ({ name: c.def.name, c })));
115
+ const afterConstraints = constraintsOf(after, { withForeignKeys: true });
116
+ const afterNames = new Set(afterConstraints.map(c => c.def.name));
117
+ for (const constraint of afterConstraints) {
118
+ const old = beforeConstraints.get(constraint.def.name);
119
+ if (old === undefined) {
120
+ const target = constraint.type === 'foreignKey' ? addForeignKeys : addConstraints;
121
+ target.push({ kind: 'addConstraint', table: after.name, constraint });
122
+ }
123
+ else if (!sameConstraint(old.c, constraint)) {
124
+ addConstraints.push({ kind: 'replaceConstraint', table: after.name, constraint });
125
+ }
126
+ }
127
+ for (const [name] of beforeConstraints) {
128
+ if (!afterNames.has(name)) {
129
+ dropConstraints.push({ kind: 'dropConstraint', table: after.name, name });
130
+ }
131
+ }
132
+ const beforeIndexes = byName(before.indexes);
133
+ const afterIndexes = byName(after.indexes);
134
+ for (const index of after.indexes) {
135
+ const old = beforeIndexes.get(index.name);
136
+ if (old === undefined) {
137
+ createIndexes.push({ kind: 'createIndex', table: after.name, index });
138
+ }
139
+ else if (!sameIndex(old, index)) {
140
+ dropIndexes.push({ kind: 'dropIndex', index: index.name });
141
+ createIndexes.push({ kind: 'createIndex', table: after.name, index });
142
+ }
143
+ }
144
+ for (const index of before.indexes) {
145
+ if (!afterIndexes.has(index.name)) {
146
+ dropIndexes.push({ kind: 'dropIndex', index: index.name });
147
+ }
148
+ }
149
+ }
150
+ return [
151
+ ...dropIndexes,
152
+ ...dropConstraints,
153
+ ...createTables,
154
+ ...addColumns,
155
+ ...alterColumns,
156
+ ...addConstraints,
157
+ ...createIndexes,
158
+ ...addForeignKeys,
159
+ ...dropColumns,
160
+ ...dropTables,
161
+ ];
162
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Facade: schema + previous snapshot -> migration SQL and the new snapshot.
3
+ *
4
+ * How to write it to disk and how to apply it is not this function's concern.
5
+ */
6
+ import type { AnyTable } from '../table/define.ts';
7
+ import { type Change } from './diff.ts';
8
+ import { type Snapshot } from './snapshot.ts';
9
+ export interface GenerateResult {
10
+ /** Migration SQL; an empty string means no changes. */
11
+ readonly sql: string;
12
+ /** The same SQL as individual statements; the runner executes these. */
13
+ readonly statements: readonly string[];
14
+ /** The snapshot to store next to the migration. */
15
+ readonly snapshot: Snapshot;
16
+ /** Parsed changes; handy for tests and for a "what changed" summary. */
17
+ readonly changes: readonly Change[];
18
+ }
19
+ export declare function generateMigration(tables: readonly AnyTable[], previous?: Snapshot): GenerateResult;
@@ -0,0 +1,8 @@
1
+ import { diffSnapshots } from './diff.js';
2
+ import { renderChanges, renderStatements } from './render.js';
3
+ import { buildSnapshot, EMPTY_SNAPSHOT } from './snapshot.js';
4
+ export function generateMigration(tables, previous = EMPTY_SNAPSHOT) {
5
+ const snapshot = buildSnapshot(tables);
6
+ const changes = diffSnapshots(previous, snapshot);
7
+ return { sql: renderChanges(changes), statements: renderStatements(changes), snapshot, changes };
8
+ }
@@ -0,0 +1,6 @@
1
+ import type { Change } from './diff.ts';
2
+ export declare function renderChange(change: Change): string[];
3
+ /** Individual statements; `writeMigration` writes these to disk. */
4
+ export declare function renderStatements(changes: readonly Change[]): string[];
5
+ /** Multi-line statements are separated by a blank line, single-line ones follow each other. */
6
+ export declare function renderChanges(changes: readonly Change[]): string;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Changes -> SQL. Decides nothing, only prints: all decisions are made in diff.ts.
3
+ */
4
+ import { quoteIdentifier as q } from '../table/sql.js';
5
+ /** Every variant is handled above; TypeScript does not let control get here. */
6
+ function unreachable(value) {
7
+ throw new Error(`unknown variant: ${JSON.stringify(value)}`);
8
+ }
9
+ function columns(names) {
10
+ return names.map(q).join(',');
11
+ }
12
+ function columnLine(column) {
13
+ const parts = [q(column.name), column.type];
14
+ if (column.default !== null) {
15
+ parts.push(`DEFAULT ${column.default}`);
16
+ }
17
+ if (column.identity !== null) {
18
+ parts.push(`GENERATED ${column.identity === 'always' ? 'ALWAYS' : 'BY DEFAULT'} AS IDENTITY`);
19
+ }
20
+ if (column.notNull) {
21
+ parts.push('NOT NULL');
22
+ }
23
+ return parts.join(' ');
24
+ }
25
+ /** Constraint body without the CONSTRAINT keyword and the name. */
26
+ function constraintBody(constraint) {
27
+ switch (constraint.type) {
28
+ case 'primaryKey':
29
+ return `PRIMARY KEY(${columns(constraint.def.columns)})`;
30
+ case 'unique':
31
+ return `UNIQUE(${columns(constraint.def.columns)})`;
32
+ case 'check':
33
+ return `CHECK (${constraint.def.expression})`;
34
+ case 'foreignKey': {
35
+ const { def } = constraint;
36
+ const parts = [
37
+ `FOREIGN KEY (${columns(def.columns)})`,
38
+ `REFERENCES ${q(def.refTable)}(${columns(def.refColumns)})`,
39
+ ];
40
+ if (def.onDelete !== null) {
41
+ parts.push(`ON DELETE ${def.onDelete.toUpperCase()}`);
42
+ }
43
+ if (def.onUpdate !== null) {
44
+ parts.push(`ON UPDATE ${def.onUpdate.toUpperCase()}`);
45
+ }
46
+ return parts.join(' ');
47
+ }
48
+ default:
49
+ return unreachable(constraint);
50
+ }
51
+ }
52
+ function constraintClause(constraint) {
53
+ return `CONSTRAINT ${q(constraint.def.name)} ${constraintBody(constraint)}`;
54
+ }
55
+ function createTable(table) {
56
+ const lines = table.columns.map(columnLine);
57
+ if (table.primaryKey !== null) {
58
+ lines.push(constraintClause({ type: 'primaryKey', def: table.primaryKey }));
59
+ }
60
+ for (const def of table.uniques) {
61
+ lines.push(constraintClause({ type: 'unique', def }));
62
+ }
63
+ for (const def of table.checks) {
64
+ lines.push(constraintClause({ type: 'check', def }));
65
+ }
66
+ return `CREATE TABLE ${q(table.name)} (\n${lines.map(l => `\t${l}`).join(',\n')}\n);`;
67
+ }
68
+ function createIndex(table, index) {
69
+ const unique = index.unique ? 'UNIQUE ' : '';
70
+ const where = index.where === null ? '' : ` WHERE ${index.where}`;
71
+ return `CREATE ${unique}INDEX ${q(index.name)} ON ${q(table)} (${columns(index.columns)})${where};`;
72
+ }
73
+ function alterColumn(table, from, to) {
74
+ const head = `ALTER TABLE ${q(table)} ALTER COLUMN ${q(to.name)}`;
75
+ const out = [];
76
+ if (from.type !== to.type) {
77
+ out.push(`${head} TYPE ${to.type};`);
78
+ }
79
+ if (from.default !== to.default) {
80
+ out.push(to.default === null ? `${head} DROP DEFAULT;` : `${head} SET DEFAULT ${to.default};`);
81
+ }
82
+ if (from.notNull !== to.notNull) {
83
+ out.push(to.notNull ? `${head} SET NOT NULL;` : `${head} DROP NOT NULL;`);
84
+ }
85
+ if (from.identity !== to.identity) {
86
+ out.push(to.identity === null
87
+ ? `${head} DROP IDENTITY;`
88
+ : `${head} ADD GENERATED ${to.identity === 'always' ? 'ALWAYS' : 'BY DEFAULT'} AS IDENTITY;`);
89
+ }
90
+ return out;
91
+ }
92
+ export function renderChange(change) {
93
+ switch (change.kind) {
94
+ case 'createTable':
95
+ return [createTable(change.table)];
96
+ case 'dropTable':
97
+ return [`DROP TABLE ${q(change.table)};`];
98
+ case 'addColumn':
99
+ return [`ALTER TABLE ${q(change.table)} ADD COLUMN ${columnLine(change.column)};`];
100
+ case 'dropColumn':
101
+ return [`ALTER TABLE ${q(change.table)} DROP COLUMN ${q(change.column)};`];
102
+ case 'alterColumn':
103
+ return alterColumn(change.table, change.from, change.to);
104
+ case 'addConstraint':
105
+ return [`ALTER TABLE ${q(change.table)} ADD ${constraintClause(change.constraint)};`];
106
+ case 'dropConstraint':
107
+ return [`ALTER TABLE ${q(change.table)} DROP CONSTRAINT ${q(change.name)};`];
108
+ case 'replaceConstraint':
109
+ return [
110
+ `ALTER TABLE ${q(change.table)} DROP CONSTRAINT ${q(change.constraint.def.name)}, ` +
111
+ `ADD ${constraintClause(change.constraint)};`,
112
+ ];
113
+ case 'createIndex':
114
+ return [createIndex(change.table, change.index)];
115
+ case 'dropIndex':
116
+ return [`DROP INDEX ${q(change.index)};`];
117
+ default:
118
+ return unreachable(change);
119
+ }
120
+ }
121
+ /** Individual statements; `writeMigration` writes these to disk. */
122
+ export function renderStatements(changes) {
123
+ return changes.flatMap(renderChange);
124
+ }
125
+ /** Multi-line statements are separated by a blank line, single-line ones follow each other. */
126
+ export function renderChanges(changes) {
127
+ const statements = renderStatements(changes);
128
+ let out = '';
129
+ statements.forEach((statement, i) => {
130
+ if (i > 0) {
131
+ const previous = statements[i - 1] ?? '';
132
+ out += previous.includes('\n') || statement.includes('\n') ? '\n\n' : '\n';
133
+ }
134
+ out += statement;
135
+ });
136
+ return out === '' ? '' : `${out}\n`;
137
+ }
@@ -0,0 +1,48 @@
1
+ import type { AnyTable, ReferentialAction } from '../table/define.ts';
2
+ export declare const SNAPSHOT_VERSION = 1;
3
+ export interface ColumnSnapshot {
4
+ readonly name: string;
5
+ /** The final postgres type, including `[]` for arrays. */
6
+ readonly type: string;
7
+ readonly notNull: boolean;
8
+ /** The rendered default SQL, or null. */
9
+ readonly default: string | null;
10
+ readonly identity: 'always' | 'byDefault' | null;
11
+ }
12
+ export interface TableSnapshot {
13
+ readonly name: string;
14
+ readonly columns: readonly ColumnSnapshot[];
15
+ readonly primaryKey: {
16
+ readonly name: string;
17
+ readonly columns: readonly string[];
18
+ } | null;
19
+ readonly uniques: readonly {
20
+ readonly name: string;
21
+ readonly columns: readonly string[];
22
+ }[];
23
+ readonly indexes: readonly {
24
+ readonly name: string;
25
+ readonly unique: boolean;
26
+ readonly columns: readonly string[];
27
+ readonly where: string | null;
28
+ }[];
29
+ readonly foreignKeys: readonly {
30
+ readonly name: string;
31
+ readonly columns: readonly string[];
32
+ readonly refTable: string;
33
+ readonly refColumns: readonly string[];
34
+ readonly onDelete: ReferentialAction | null;
35
+ readonly onUpdate: ReferentialAction | null;
36
+ }[];
37
+ readonly checks: readonly {
38
+ readonly name: string;
39
+ readonly expression: string;
40
+ }[];
41
+ }
42
+ export interface Snapshot {
43
+ readonly version: number;
44
+ readonly tables: readonly TableSnapshot[];
45
+ }
46
+ export declare const EMPTY_SNAPSHOT: Snapshot;
47
+ /** Schema (a list of tables) -> snapshot. */
48
+ export declare function buildSnapshot(tables: readonly AnyTable[]): Snapshot;
@@ -0,0 +1,54 @@
1
+ import { isSql, quoteLiteral, renderSql } from '../table/sql.js';
2
+ export const SNAPSHOT_VERSION = 1;
3
+ export const EMPTY_SNAPSHOT = { version: SNAPSHOT_VERSION, tables: [] };
4
+ function renderDefault(value) {
5
+ if (value === undefined) {
6
+ return null;
7
+ }
8
+ if (isSql(value)) {
9
+ return renderSql(value);
10
+ }
11
+ return quoteLiteral(value);
12
+ }
13
+ function tableSnapshot(spec) {
14
+ return {
15
+ name: spec.name,
16
+ columns: spec.columns.map(column => ({
17
+ name: column.name,
18
+ type: column.array ? `${column.sqlType}[]` : column.sqlType,
19
+ notNull: column.notNull,
20
+ default: renderDefault(column.default),
21
+ identity: column.identity ?? null,
22
+ })),
23
+ primaryKey: spec.primaryKey !== undefined
24
+ ? { name: spec.primaryKey.name, columns: [...spec.primaryKey.columns] }
25
+ : null,
26
+ uniques: spec.uniques.map(u => ({ name: u.name, columns: [...u.columns] })),
27
+ indexes: spec.indexes.map(i => ({
28
+ name: i.name,
29
+ unique: i.unique,
30
+ columns: [...i.columns],
31
+ where: i.where !== undefined ? renderSql(i.where) : null,
32
+ })),
33
+ foreignKeys: spec.foreignKeys.map(f => ({
34
+ name: f.name,
35
+ columns: [...f.columns],
36
+ refTable: f.refTable,
37
+ refColumns: [...f.refColumns],
38
+ onDelete: f.onDelete ?? null,
39
+ onUpdate: f.onUpdate ?? null,
40
+ })),
41
+ checks: spec.checks.map(c => ({ name: c.name, expression: renderSql(c.expression) })),
42
+ };
43
+ }
44
+ /** Schema (a list of tables) -> snapshot. */
45
+ export function buildSnapshot(tables) {
46
+ const seen = new Set();
47
+ for (const table of tables) {
48
+ if (seen.has(table.spec.name)) {
49
+ throw new Error(`table "${table.spec.name}" is declared twice`);
50
+ }
51
+ seen.add(table.spec.name);
52
+ }
53
+ return { version: SNAPSHOT_VERSION, tables: tables.map(t => tableSnapshot(t.spec)) };
54
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * kysely-ddl: PostgreSQL schema as code and SQL migration generation.
3
+ * Has no runtime dependency on Kysely or on any driver.
4
+ *
5
+ * Layers and dependency direction:
6
+ *
7
+ * table ◄── generator ◄── migrator ◄── kysely
8
+ *
9
+ * Entry points:
10
+ *
11
+ * kysely-ddl — this file: tables, generation, migration files
12
+ * kysely-ddl/kysely — row types, migration runner, provider for Migrator
13
+ *
14
+ * Exports are sorted by module path: generator (snapshot, diff, render,
15
+ * facade), migrator (files on disk), table (table definitions).
16
+ */
17
+ export { diffSnapshots } from './generator/diff.ts';
18
+ export type { Change, Constraint } from './generator/diff.ts';
19
+ export { generateMigration } from './generator/generate.ts';
20
+ export type { GenerateResult } from './generator/generate.ts';
21
+ export { renderChange, renderChanges, renderStatements } from './generator/render.ts';
22
+ export { buildSnapshot, EMPTY_SNAPSHOT, SNAPSHOT_VERSION } from './generator/snapshot.ts';
23
+ export type { ColumnSnapshot, Snapshot, TableSnapshot } from './generator/snapshot.ts';
24
+ export { listMigrations, MIGRATION_EXTENSION, migrationTimestamp, readLatestSnapshot, readStatements, SNAPSHOT_FILE, STATEMENT_SEPARATOR, writeMigration, } from './migrator/store.ts';
25
+ export { toCamelCase, toSnakeCase } from './table/casing.ts';
26
+ export type { CamelCase, SnakeCase } from './table/casing.ts';
27
+ export { ColumnBuilder, columnBuilders } from './table/columns.ts';
28
+ export type { AnyColumn, ColumnBuilders, ColumnCfg, ColumnSpec, DefaultValue } from './table/columns.ts';
29
+ export { AUTO_NAMES, defineTable, ref } from './table/define.ts';
30
+ export type { AnyTable, ForeignKeyDef, Reference, ReferentialAction, ResolveColumns, ResolvedColumn, ResolvedColumnCfg, Table, TableOptions, TableSpec, } from './table/define.ts';
31
+ export { assertIdentifier, autoName, fitIdentifier, MAX_IDENTIFIER_BYTES } from './table/identifier.ts';
32
+ export { collectColumns, inArray, quoteIdentifier, quoteLiteral, renderSql, sql } from './table/sql.ts';
33
+ export type { ColumnRef, Literal, Sql, SqlChunk } from './table/sql.ts';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * kysely-ddl: PostgreSQL schema as code and SQL migration generation.
3
+ * Has no runtime dependency on Kysely or on any driver.
4
+ *
5
+ * Layers and dependency direction:
6
+ *
7
+ * table ◄── generator ◄── migrator ◄── kysely
8
+ *
9
+ * Entry points:
10
+ *
11
+ * kysely-ddl — this file: tables, generation, migration files
12
+ * kysely-ddl/kysely — row types, migration runner, provider for Migrator
13
+ *
14
+ * Exports are sorted by module path: generator (snapshot, diff, render,
15
+ * facade), migrator (files on disk), table (table definitions).
16
+ */
17
+ export { diffSnapshots } from './generator/diff.js';
18
+ export { generateMigration } from './generator/generate.js';
19
+ export { renderChange, renderChanges, renderStatements } from './generator/render.js';
20
+ export { buildSnapshot, EMPTY_SNAPSHOT, SNAPSHOT_VERSION } from './generator/snapshot.js';
21
+ export { listMigrations, MIGRATION_EXTENSION, migrationTimestamp, readLatestSnapshot, readStatements, SNAPSHOT_FILE, STATEMENT_SEPARATOR, writeMigration, } from './migrator/store.js';
22
+ export { toCamelCase, toSnakeCase } from './table/casing.js';
23
+ export { ColumnBuilder, columnBuilders } from './table/columns.js';
24
+ export { AUTO_NAMES, defineTable, ref } from './table/define.js';
25
+ export { assertIdentifier, autoName, fitIdentifier, MAX_IDENTIFIER_BYTES } from './table/identifier.js';
26
+ export { collectColumns, inArray, quoteIdentifier, quoteLiteral, renderSql, sql } from './table/sql.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Everything tied to Kysely: row types, jsonb values, the migration runner on
3
+ * top of a ready `Kysely`, and the `.sql` file provider for the built-in `Migrator`.
4
+ * Entry point `kysely-ddl/kysely`.
5
+ */
6
+ export type { inferKyselyDatabase, inferKyselyTable } from './infer.ts';
7
+ export { jsonb, jsonbArray } from './json.ts';
8
+ export type { Jsonb } from './json.ts';
9
+ export { sqlFileMigrationProvider } from './provider.ts';
10
+ export type { SqlMigrationProviderOptions } from './provider.ts';
11
+ export { createMigrator, DEFAULT_JOURNAL_TABLE, migrateToLatest, MIGRATION_LOCK_ID, MigrationError, } from './runner.ts';
12
+ export type { MigrationRunResult, MigrationStatus, MigratorOptions, SqlMigrator, TransactionMode } from './runner.ts';
@@ -0,0 +1,3 @@
1
+ export { jsonb, jsonbArray } from './json.js';
2
+ export { sqlFileMigrationProvider } from './provider.js';
3
+ export { createMigrator, DEFAULT_JOURNAL_TABLE, migrateToLatest, MIGRATION_LOCK_ID, MigrationError, } from './runner.js';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Table types for Kysely, like zod's `z.infer`:
3
+ *
4
+ * ```ts
5
+ * type UserTable = inferKyselyTable<typeof userTable>;
6
+ * type DB = inferKyselyDatabase<typeof schema>;
7
+ *
8
+ * const db = new Kysely<DB>({ dialect });
9
+ * ```
10
+ *
11
+ * Keys are the COLUMN NAMES IN THE DATABASE. They are known at the type level
12
+ * because `defineTable` keeps them as literals, so the bridge does not depend on
13
+ * casing plugins or on conventions shared between two libraries.
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:
18
+ *
19
+ * ```ts
20
+ * type DB = inferKyselyDatabase<typeof schema, true>;
21
+ * const db = new Kysely<DB>({ dialect, plugins: [new CamelCasePlugin()] });
22
+ * ```
23
+ *
24
+ * `kysely` is imported as a type only; there is no runtime dependency.
25
+ */
26
+ import type { CamelCase } from '../table/casing.ts';
27
+ import type { ResolvedColumnCfg, Table } from '../table/define.ts';
28
+ import type { Jsonb } from './json.ts';
29
+ import type { ColumnType } from 'kysely';
30
+ type Cols<T extends Table> = T['_']['columns'];
31
+ /** 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;
33
+ /** What comes back from SELECT. */
34
+ type Select<C extends ResolvedColumnCfg> = C['notNull'] extends true ? C['data'] : C['data'] | null;
35
+ type ElementOf<T> = T extends readonly (infer E)[] ? E : never;
36
+ /**
37
+ * What goes into INSERT / UPDATE before nullability and defaults are applied. For
38
+ * jsonb this is branded JSON text: only `jsonb()` produces it (`jsonbArray()` for
39
+ * `jsonb[]`), a raw object or string does not compile.
40
+ */
41
+ type Written<C extends ResolvedColumnCfg> = C['json'] extends true ? C['array'] extends true ? Jsonb<ElementOf<C['data']>>[] : Jsonb<C['data']> : C['data'];
42
+ /**
43
+ * What can be passed for a write:
44
+ * identity always -> not at all;
45
+ * notNull without default -> required field;
46
+ * has a default -> optional;
47
+ * nullable -> optional and accepts null.
48
+ */
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]>>;
52
+ };
53
+ /**
54
+ * The interface of the whole database from a schema module: the key is the table
55
+ * name, the value is `inferKyselyTable`. Anything that is not a table (constants,
56
+ * types, zod schemas) is filtered out.
57
+ */
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;
60
+ };
61
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Values for jsonb columns.
3
+ *
4
+ * Drivers disagree on how a jsonb parameter should be passed. `pg` expects JSON
5
+ * text: it serializes objects itself, but turns a JS array into a postgres array
6
+ * literal (`{1,2,3}`) and gets `invalid input syntax for type json`. `Bun.SQL`
7
+ * looks at the parameter type reported by the server and for jsonb serializes the
8
+ * JS value to JSON itself, so a ready JSON string gets encoded a second time and
9
+ * becomes a jsonb string, while a number or boolean is rejected by postgres.
10
+ *
11
+ * The one representation both understand is a text parameter with an explicit
12
+ * `$1::text::jsonb` cast. That is what `jsonb()` and `jsonbArray()` build, and the
13
+ * `Jsonb<T>` brand in the write types (`inferKyselyTable`) keeps a raw object or
14
+ * string from being passed around the helper.
15
+ *
16
+ * ```ts
17
+ * await db.insertInto('user').values({ settings: jsonb({ theme: 'dark' }) }).execute();
18
+ * await db.updateTable('user').set({ settings: jsonb({ theme: 'light' }) }).execute();
19
+ * await db.selectFrom('user').where('settings', '@>', jsonb({ theme: 'dark' })).execute();
20
+ * ```
21
+ */
22
+ import { type RawBuilder } from 'kysely';
23
+ /** JSON text for a jsonb column. From the outside it is only produced by `jsonb()`. */
24
+ export type Jsonb<T> = string & {
25
+ readonly __jsonb: T;
26
+ };
27
+ /**
28
+ * A value for a jsonb column: an object, an array, a string, a number or a boolean.
29
+ *
30
+ * `null` and `undefined` are rejected on purpose. `jsonb(null)` would write JSON
31
+ * null rather than SQL NULL: it passes a `NOT NULL` column, is invisible to
32
+ * `IS NULL` and `COALESCE`, and is indistinguishable from SQL NULL when read.
33
+ * SQL NULL for a nullable column is passed as a plain `null` without the helper.
34
+ * A top-level JSON null is needed so rarely that raw `sql` remains for it.
35
+ */
36
+ export declare function jsonb<T extends {}>(value: T): RawBuilder<Jsonb<T>>;
37
+ /** A value for a `jsonb[]` column: each element is a separate JSON value, `null` inside is JSON null. */
38
+ export declare function jsonbArray<T>(values: readonly T[]): RawBuilder<Jsonb<T>[]>;
39
+ /**
40
+ * A postgres array literal from strings: `{"a","b,c","d\"e",NULL}`. Every element
41
+ * is quoted, so commas, braces and spaces inside are safe.
42
+ */
43
+ export declare function pgArrayLiteral(values: readonly (string | null)[]): string;