kysely-schema 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.
- package/.turbo/turbo-build.log +20 -0
- package/dist/index.d.mts +207 -0
- package/dist/index.d.ts +207 -0
- package/dist/index.js +573 -0
- package/dist/index.mjs +535 -0
- package/package.json +48 -0
- package/src/differ/index.ts +150 -0
- package/src/differ/operations.ts +33 -0
- package/src/generators/migration.ts +172 -0
- package/src/generators/templates.ts +33 -0
- package/src/generators/types.ts +91 -0
- package/src/index.ts +40 -0
- package/src/schema/dsl.ts +125 -0
- package/src/schema/types.ts +158 -0
- package/src/schema/validators.ts +113 -0
- package/tests/differ.test.ts +161 -0
- package/tests/dsl.test.ts +168 -0
- package/tests/migration.test.ts +140 -0
- package/tests/types.test.ts +176 -0
- package/tests/validators.test.ts +116 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +9 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SchemaDefinition,
|
|
3
|
+
DiffOperation,
|
|
4
|
+
ColumnDefinition,
|
|
5
|
+
} from '../schema/types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compares two schema snapshots and returns a list of diff operations
|
|
9
|
+
* needed to migrate from `previous` to `current`.
|
|
10
|
+
*/
|
|
11
|
+
export class SchemaDiffer {
|
|
12
|
+
diff(previous: SchemaDefinition, current: SchemaDefinition): DiffOperation[] {
|
|
13
|
+
const ops: DiffOperation[] = [];
|
|
14
|
+
|
|
15
|
+
const prevTables = new Set(Object.keys(previous.tables));
|
|
16
|
+
const currTables = new Set(Object.keys(current.tables));
|
|
17
|
+
|
|
18
|
+
// ── Added tables ──
|
|
19
|
+
for (const tableName of currTables) {
|
|
20
|
+
if (!prevTables.has(tableName)) {
|
|
21
|
+
ops.push({
|
|
22
|
+
type: 'addTable',
|
|
23
|
+
tableName,
|
|
24
|
+
table: current.tables[tableName],
|
|
25
|
+
description: `Create table '${tableName}'`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Dropped tables ──
|
|
31
|
+
for (const tableName of prevTables) {
|
|
32
|
+
if (!currTables.has(tableName)) {
|
|
33
|
+
ops.push({
|
|
34
|
+
type: 'dropTable',
|
|
35
|
+
tableName,
|
|
36
|
+
description: `Drop table '${tableName}'`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Changed tables (columns) ──
|
|
42
|
+
for (const tableName of currTables) {
|
|
43
|
+
if (!prevTables.has(tableName)) continue; // already handled as addTable
|
|
44
|
+
|
|
45
|
+
const prevCols = previous.tables[tableName].columns;
|
|
46
|
+
const currCols = current.tables[tableName].columns;
|
|
47
|
+
const prevColNames = new Set(Object.keys(prevCols));
|
|
48
|
+
const currColNames = new Set(Object.keys(currCols));
|
|
49
|
+
|
|
50
|
+
// Added columns
|
|
51
|
+
for (const colName of currColNames) {
|
|
52
|
+
if (!prevColNames.has(colName)) {
|
|
53
|
+
ops.push({
|
|
54
|
+
type: 'addColumn',
|
|
55
|
+
tableName,
|
|
56
|
+
columnName: colName,
|
|
57
|
+
column: currCols[colName],
|
|
58
|
+
description: `Add column '${colName}' to table '${tableName}'`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Dropped columns
|
|
64
|
+
for (const colName of prevColNames) {
|
|
65
|
+
if (!currColNames.has(colName)) {
|
|
66
|
+
ops.push({
|
|
67
|
+
type: 'dropColumn',
|
|
68
|
+
tableName,
|
|
69
|
+
columnName: colName,
|
|
70
|
+
description: `Drop column '${colName}' from table '${tableName}'`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Altered columns
|
|
76
|
+
for (const colName of currColNames) {
|
|
77
|
+
if (!prevColNames.has(colName)) continue;
|
|
78
|
+
if (!this.columnsEqual(prevCols[colName], currCols[colName])) {
|
|
79
|
+
ops.push({
|
|
80
|
+
type: 'alterColumn',
|
|
81
|
+
tableName,
|
|
82
|
+
columnName: colName,
|
|
83
|
+
oldColumn: prevCols[colName],
|
|
84
|
+
newColumn: currCols[colName],
|
|
85
|
+
description: `Alter column '${colName}' in table '${tableName}'`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return ops;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate an ALTER-TABLE migration string from a list of diff operations.
|
|
96
|
+
*/
|
|
97
|
+
generateAlterMigration(ops: DiffOperation[]): { up: string; down: string } {
|
|
98
|
+
const upLines: string[] = [];
|
|
99
|
+
const downLines: string[] = [];
|
|
100
|
+
|
|
101
|
+
for (const op of ops) {
|
|
102
|
+
switch (op.type) {
|
|
103
|
+
case 'addTable': {
|
|
104
|
+
upLines.push(` await db.schema`);
|
|
105
|
+
upLines.push(` .createTable('${op.tableName}')`);
|
|
106
|
+
for (const [colName, colDef] of Object.entries(op.table.columns)) {
|
|
107
|
+
upLines.push(` .addColumn('${colName}', '${colDef.type}')`);
|
|
108
|
+
}
|
|
109
|
+
upLines.push(` .execute();`);
|
|
110
|
+
downLines.push(` await db.schema.dropTable('${op.tableName}').ifExists().execute();`);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'dropTable': {
|
|
114
|
+
upLines.push(` await db.schema.dropTable('${op.tableName}').ifExists().execute();`);
|
|
115
|
+
// down: cannot fully reverse without the old definition
|
|
116
|
+
downLines.push(` // TODO: Recreate table '${op.tableName}'`);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'addColumn': {
|
|
120
|
+
upLines.push(` await db.schema.alterTable('${op.tableName}').addColumn('${op.columnName}', '${op.column.type}').execute();`);
|
|
121
|
+
downLines.push(` await db.schema.alterTable('${op.tableName}').dropColumn('${op.columnName}').execute();`);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'dropColumn': {
|
|
125
|
+
upLines.push(` await db.schema.alterTable('${op.tableName}').dropColumn('${op.columnName}').execute();`);
|
|
126
|
+
downLines.push(` // TODO: Re-add column '${op.columnName}' to '${op.tableName}'`);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'alterColumn': {
|
|
130
|
+
upLines.push(` await db.schema.alterTable('${op.tableName}').alterColumn('${op.columnName}', (col) => col.setDataType('${op.newColumn.type}')).execute();`);
|
|
131
|
+
downLines.push(` await db.schema.alterTable('${op.tableName}').alterColumn('${op.columnName}', (col) => col.setDataType('${op.oldColumn.type}')).execute();`);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
up: upLines.join('\n'),
|
|
141
|
+
down: downLines.join('\n'),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Private helpers ──────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
private columnsEqual(a: ColumnDefinition, b: ColumnDefinition): boolean {
|
|
148
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DiffOperation } from '../schema/types.js';
|
|
2
|
+
|
|
3
|
+
// Re-export the operation types for convenience
|
|
4
|
+
export type {
|
|
5
|
+
DiffOperation,
|
|
6
|
+
AddTableOperation,
|
|
7
|
+
DropTableOperation,
|
|
8
|
+
AddColumnOperation,
|
|
9
|
+
DropColumnOperation,
|
|
10
|
+
AlterColumnOperation,
|
|
11
|
+
AddIndexOperation,
|
|
12
|
+
DropIndexOperation,
|
|
13
|
+
} from '../schema/types.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Labels used when printing diffs to the console.
|
|
17
|
+
*/
|
|
18
|
+
export const operationLabels: Record<DiffOperation['type'], string> = {
|
|
19
|
+
addTable: '+ ADD TABLE',
|
|
20
|
+
dropTable: '- DROP TABLE',
|
|
21
|
+
addColumn: '+ ADD COLUMN',
|
|
22
|
+
dropColumn: '- DROP COLUMN',
|
|
23
|
+
alterColumn: '~ ALTER COLUMN',
|
|
24
|
+
addIndex: '+ ADD INDEX',
|
|
25
|
+
dropIndex: '- DROP INDEX',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Return a human-readable summary for a diff operation.
|
|
30
|
+
*/
|
|
31
|
+
export function describeOperation(op: DiffOperation): string {
|
|
32
|
+
return `${operationLabels[op.type]}: ${op.description}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SchemaDefinition,
|
|
3
|
+
MigrationFile,
|
|
4
|
+
ColumnDefinition,
|
|
5
|
+
TableDefinition,
|
|
6
|
+
} from '../schema/types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generates Kysely migration files from a SchemaDefinition.
|
|
10
|
+
*/
|
|
11
|
+
export class MigrationGenerator {
|
|
12
|
+
/**
|
|
13
|
+
* Generate a full CREATE-TABLE migration for the entire schema.
|
|
14
|
+
*/
|
|
15
|
+
generate(schema: SchemaDefinition, migrationName: string): MigrationFile {
|
|
16
|
+
const timestamp = this.generateTimestamp();
|
|
17
|
+
const filename = `${timestamp}_${this.sanitize(migrationName)}.ts`;
|
|
18
|
+
|
|
19
|
+
const upCode = this.generateUpFunction(schema);
|
|
20
|
+
const downCode = this.generateDownFunction(schema);
|
|
21
|
+
|
|
22
|
+
const content = [
|
|
23
|
+
`import { Kysely, sql } from 'kysely';`,
|
|
24
|
+
``,
|
|
25
|
+
`export async function up(db: Kysely<any>): Promise<void> {`,
|
|
26
|
+
upCode,
|
|
27
|
+
`}`,
|
|
28
|
+
``,
|
|
29
|
+
`export async function down(db: Kysely<any>): Promise<void> {`,
|
|
30
|
+
downCode,
|
|
31
|
+
`}`,
|
|
32
|
+
``,
|
|
33
|
+
].join('\n');
|
|
34
|
+
|
|
35
|
+
return { filename, content };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Private helpers ──────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
private generateUpFunction(schema: SchemaDefinition): string {
|
|
41
|
+
const parts: string[] = [];
|
|
42
|
+
|
|
43
|
+
for (const [tableName, tableDef] of Object.entries(schema.tables)) {
|
|
44
|
+
parts.push(this.generateCreateTable(tableName, tableDef));
|
|
45
|
+
const indexes = this.generateIndexes(tableName, tableDef);
|
|
46
|
+
if (indexes) parts.push(indexes);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return parts.join('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private generateCreateTable(name: string, def: TableDefinition): string {
|
|
53
|
+
const lines: string[] = [];
|
|
54
|
+
lines.push(` await db.schema`);
|
|
55
|
+
lines.push(` .createTable('${name}')`);
|
|
56
|
+
|
|
57
|
+
for (const [colName, colDef] of Object.entries(def.columns)) {
|
|
58
|
+
lines.push(this.generateColumnDefinition(colName, colDef));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lines.push(` .execute();`);
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private generateColumnDefinition(name: string, def: ColumnDefinition): string {
|
|
66
|
+
const type = this.mapColumnType(def);
|
|
67
|
+
const modifiers = this.buildModifiers(def);
|
|
68
|
+
|
|
69
|
+
if (modifiers.length > 0) {
|
|
70
|
+
return ` .addColumn('${name}', '${type}', (col) => col.${modifiers.join('.')})`;
|
|
71
|
+
}
|
|
72
|
+
return ` .addColumn('${name}', '${type}')`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private buildModifiers(def: ColumnDefinition): string[] {
|
|
76
|
+
const mods: string[] = [];
|
|
77
|
+
|
|
78
|
+
if (def.primaryKey) mods.push('primaryKey()');
|
|
79
|
+
if (def.notNull) mods.push('notNull()');
|
|
80
|
+
if (def.unique) mods.push('unique()');
|
|
81
|
+
|
|
82
|
+
if (def.default !== undefined) {
|
|
83
|
+
mods.push(`defaultTo(${this.formatDefaultValue(def.default, def.type)})`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (def.references) {
|
|
87
|
+
mods.push(`references('${def.references.table}.${def.references.column}')`);
|
|
88
|
+
if (def.onDelete) mods.push(`onDelete('${def.onDelete}')`);
|
|
89
|
+
if (def.onUpdate) mods.push(`onUpdate('${def.onUpdate}')`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (def.check) {
|
|
93
|
+
mods.push(`check(sql\`${def.check}\`)`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return mods;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private generateDownFunction(schema: SchemaDefinition): string {
|
|
100
|
+
const tables = Object.keys(schema.tables).reverse();
|
|
101
|
+
return tables
|
|
102
|
+
.map((t) => ` await db.schema.dropTable('${t}').ifExists().execute();`)
|
|
103
|
+
.join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private generateIndexes(tableName: string, tableDef: TableDefinition): string {
|
|
107
|
+
const lines: string[] = [];
|
|
108
|
+
|
|
109
|
+
// Auto-generate indexes for indexed columns and foreign keys
|
|
110
|
+
for (const [colName, colDef] of Object.entries(tableDef.columns)) {
|
|
111
|
+
if (colDef.index || colDef.references) {
|
|
112
|
+
lines.push(` await db.schema`);
|
|
113
|
+
lines.push(` .createIndex('${tableName}_${colName}_index')`);
|
|
114
|
+
lines.push(` .on('${tableName}')`);
|
|
115
|
+
lines.push(` .column('${colName}')`);
|
|
116
|
+
lines.push(` .execute();`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Explicit indexes
|
|
121
|
+
for (const idx of tableDef.indexes) {
|
|
122
|
+
const idxName = idx.name || `${tableName}_${idx.columns.join('_')}_index`;
|
|
123
|
+
lines.push(` await db.schema`);
|
|
124
|
+
if (idx.unique) {
|
|
125
|
+
lines.push(` .createIndex('${idxName}')`);
|
|
126
|
+
lines.push(` .on('${tableName}')`);
|
|
127
|
+
lines.push(` .columns([${idx.columns.map((c) => `'${c}'`).join(', ')}])`);
|
|
128
|
+
lines.push(` .unique()`);
|
|
129
|
+
} else {
|
|
130
|
+
lines.push(` .createIndex('${idxName}')`);
|
|
131
|
+
lines.push(` .on('${tableName}')`);
|
|
132
|
+
lines.push(` .columns([${idx.columns.map((c) => `'${c}'`).join(', ')}])`);
|
|
133
|
+
}
|
|
134
|
+
lines.push(` .execute();`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return lines.length > 0 ? lines.join('\n') : '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private mapColumnType(def: ColumnDefinition): string {
|
|
141
|
+
switch (def.type) {
|
|
142
|
+
case 'varchar':
|
|
143
|
+
return def.length ? `varchar(${def.length})` : 'varchar';
|
|
144
|
+
case 'decimal':
|
|
145
|
+
if (def.precision !== undefined && def.scale !== undefined) {
|
|
146
|
+
return `decimal(${def.precision}, ${def.scale})`;
|
|
147
|
+
}
|
|
148
|
+
return 'decimal';
|
|
149
|
+
default:
|
|
150
|
+
return def.type;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private formatDefaultValue(value: string | number | boolean, _type: string): string {
|
|
155
|
+
if (typeof value === 'string' && value === 'now()') return 'sql`now()`';
|
|
156
|
+
if (typeof value === 'string') return `'${value}'`;
|
|
157
|
+
if (typeof value === 'boolean') return value.toString();
|
|
158
|
+
return String(value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private generateTimestamp(): string {
|
|
162
|
+
const now = new Date();
|
|
163
|
+
return now
|
|
164
|
+
.toISOString()
|
|
165
|
+
.replace(/[-:]/g, '')
|
|
166
|
+
.split('.')[0];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private sanitize(name: string): string {
|
|
170
|
+
return name.replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// ─── Code templates used by the CLI ────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default schema template written by `kysely-schema init`.
|
|
5
|
+
*/
|
|
6
|
+
export const schemaTemplate = `import { defineSchema, table, column } from 'kysely-schema';
|
|
7
|
+
|
|
8
|
+
export default defineSchema({
|
|
9
|
+
// Define your tables here. Example:
|
|
10
|
+
//
|
|
11
|
+
// user: table({
|
|
12
|
+
// id: column.serial().primaryKey(),
|
|
13
|
+
// email: column.text().notNull().unique(),
|
|
14
|
+
// name: column.text().nullable(),
|
|
15
|
+
// createdAt: column.timestamp().default('now()').notNull(),
|
|
16
|
+
// }),
|
|
17
|
+
});
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default config file template.
|
|
22
|
+
*/
|
|
23
|
+
export const configTemplate = `import type { KyselySchemaConfig } from 'kysely-schema';
|
|
24
|
+
|
|
25
|
+
const config: KyselySchemaConfig = {
|
|
26
|
+
schemaPath: './schema/index.ts',
|
|
27
|
+
migrationsDir: './migrations',
|
|
28
|
+
generatedDir: './generated',
|
|
29
|
+
snapshotDir: './.kysely-schema',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default config;
|
|
33
|
+
`;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { SchemaDefinition, ColumnDefinition } from '../schema/types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a TypeScript `Database` interface from a SchemaDefinition.
|
|
5
|
+
*/
|
|
6
|
+
export class TypeGenerator {
|
|
7
|
+
generate(schema: SchemaDefinition): string {
|
|
8
|
+
const lines: string[] = [];
|
|
9
|
+
|
|
10
|
+
lines.push('// Auto-generated by kysely-schema — DO NOT EDIT');
|
|
11
|
+
lines.push('');
|
|
12
|
+
lines.push('import type { Generated, ColumnType } from \'kysely\';');
|
|
13
|
+
lines.push('');
|
|
14
|
+
|
|
15
|
+
// Generate individual table interfaces
|
|
16
|
+
for (const [tableName, tableDef] of Object.entries(schema.tables)) {
|
|
17
|
+
const pascalName = this.toPascalCase(tableName);
|
|
18
|
+
lines.push(`export interface ${pascalName}Table {`);
|
|
19
|
+
|
|
20
|
+
for (const [colName, colDef] of Object.entries(tableDef.columns)) {
|
|
21
|
+
const tsType = this.mapToTypeScript(colDef);
|
|
22
|
+
lines.push(` ${colName}: ${tsType};`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
lines.push('}');
|
|
26
|
+
lines.push('');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Generate the Database interface
|
|
30
|
+
lines.push('export interface Database {');
|
|
31
|
+
for (const tableName of Object.keys(schema.tables)) {
|
|
32
|
+
const pascalName = this.toPascalCase(tableName);
|
|
33
|
+
lines.push(` ${tableName}: ${pascalName}Table;`);
|
|
34
|
+
}
|
|
35
|
+
lines.push('}');
|
|
36
|
+
lines.push('');
|
|
37
|
+
|
|
38
|
+
return lines.join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Private helpers ──────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
private mapToTypeScript(def: ColumnDefinition): string {
|
|
44
|
+
const baseType = this.baseTypeMap(def.type);
|
|
45
|
+
let tsType = baseType;
|
|
46
|
+
|
|
47
|
+
// serial / auto-increment columns are Generated<>
|
|
48
|
+
if (def.type === 'serial') {
|
|
49
|
+
tsType = `Generated<${baseType}>`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Columns with a default value but not serial are also Generated<>
|
|
53
|
+
if (def.default !== undefined && def.type !== 'serial') {
|
|
54
|
+
tsType = `Generated<${baseType}>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Nullable
|
|
58
|
+
if (def.nullable || (!def.notNull && !def.primaryKey && def.type !== 'serial')) {
|
|
59
|
+
tsType += ' | null';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return tsType;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private baseTypeMap(type: string): string {
|
|
66
|
+
const map: Record<string, string> = {
|
|
67
|
+
serial: 'number',
|
|
68
|
+
integer: 'number',
|
|
69
|
+
bigint: 'string',
|
|
70
|
+
decimal: 'string',
|
|
71
|
+
text: 'string',
|
|
72
|
+
varchar: 'string',
|
|
73
|
+
timestamp: 'Date',
|
|
74
|
+
date: 'Date',
|
|
75
|
+
time: 'string',
|
|
76
|
+
boolean: 'boolean',
|
|
77
|
+
json: 'unknown',
|
|
78
|
+
jsonb: 'unknown',
|
|
79
|
+
binary: 'Buffer',
|
|
80
|
+
uuid: 'string',
|
|
81
|
+
};
|
|
82
|
+
return map[type] || 'unknown';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private toPascalCase(str: string): string {
|
|
86
|
+
return str
|
|
87
|
+
.split(/[_\- ]+/)
|
|
88
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
89
|
+
.join('');
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ─── Public API for kysely-schema ────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
// Schema DSL
|
|
4
|
+
export { column, table, defineSchema, ColumnBuilder } from './schema/dsl.js';
|
|
5
|
+
|
|
6
|
+
// Schema types
|
|
7
|
+
export type {
|
|
8
|
+
ColumnDefinition,
|
|
9
|
+
ColumnType,
|
|
10
|
+
ReferentialAction,
|
|
11
|
+
ForeignKeyReference,
|
|
12
|
+
IndexDefinition,
|
|
13
|
+
TableDefinition,
|
|
14
|
+
SchemaDefinition,
|
|
15
|
+
MigrationFile,
|
|
16
|
+
DiffOperation,
|
|
17
|
+
DiffOperationType,
|
|
18
|
+
AddTableOperation,
|
|
19
|
+
DropTableOperation,
|
|
20
|
+
AddColumnOperation,
|
|
21
|
+
DropColumnOperation,
|
|
22
|
+
AlterColumnOperation,
|
|
23
|
+
AddIndexOperation,
|
|
24
|
+
DropIndexOperation,
|
|
25
|
+
SchemaSnapshot,
|
|
26
|
+
KyselySchemaConfig,
|
|
27
|
+
} from './schema/types.js';
|
|
28
|
+
|
|
29
|
+
// Validators
|
|
30
|
+
export { validateSchema } from './schema/validators.js';
|
|
31
|
+
export type { ValidationError } from './schema/validators.js';
|
|
32
|
+
|
|
33
|
+
// Generators
|
|
34
|
+
export { MigrationGenerator } from './generators/migration.js';
|
|
35
|
+
export { TypeGenerator } from './generators/types.js';
|
|
36
|
+
export { schemaTemplate, configTemplate } from './generators/templates.js';
|
|
37
|
+
|
|
38
|
+
// Differ
|
|
39
|
+
export { SchemaDiffer } from './differ/index.js';
|
|
40
|
+
export { operationLabels, describeOperation } from './differ/operations.js';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColumnDefinition,
|
|
3
|
+
ColumnType,
|
|
4
|
+
ReferentialAction,
|
|
5
|
+
TableDefinition,
|
|
6
|
+
SchemaDefinition,
|
|
7
|
+
} from './types.js';
|
|
8
|
+
|
|
9
|
+
// ─── ColumnBuilder ────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export class ColumnBuilder {
|
|
12
|
+
private def: ColumnDefinition;
|
|
13
|
+
|
|
14
|
+
constructor(init: Partial<ColumnDefinition> & { type: ColumnType }) {
|
|
15
|
+
this.def = { ...init } as ColumnDefinition;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
primaryKey(): this {
|
|
19
|
+
this.def.primaryKey = true;
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
notNull(): this {
|
|
24
|
+
this.def.notNull = true;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
nullable(): this {
|
|
29
|
+
this.def.nullable = true;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
unique(): this {
|
|
34
|
+
this.def.unique = true;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
default(value: string | number | boolean): this {
|
|
39
|
+
this.def.default = value;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
references(table: string, col: string): this {
|
|
44
|
+
this.def.references = { table, column: col };
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onDelete(action: ReferentialAction): this {
|
|
49
|
+
this.def.onDelete = action;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
onUpdate(action: ReferentialAction): this {
|
|
54
|
+
this.def.onUpdate = action;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
index(): this {
|
|
59
|
+
this.def.index = true;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
check(expression: string): this {
|
|
64
|
+
this.def.check = expression;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @internal Return the raw column definition. */
|
|
69
|
+
build(): ColumnDefinition {
|
|
70
|
+
return { ...this.def };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─── Column factory ──────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export const column = {
|
|
77
|
+
// Numeric types
|
|
78
|
+
serial: () => new ColumnBuilder({ type: 'serial' }),
|
|
79
|
+
integer: () => new ColumnBuilder({ type: 'integer' }),
|
|
80
|
+
bigint: () => new ColumnBuilder({ type: 'bigint' }),
|
|
81
|
+
decimal: (precision?: number, scale?: number) =>
|
|
82
|
+
new ColumnBuilder({ type: 'decimal', precision, scale }),
|
|
83
|
+
|
|
84
|
+
// Text types
|
|
85
|
+
text: () => new ColumnBuilder({ type: 'text' }),
|
|
86
|
+
varchar: (length?: number) => new ColumnBuilder({ type: 'varchar', length }),
|
|
87
|
+
|
|
88
|
+
// Date / Time types
|
|
89
|
+
timestamp: () => new ColumnBuilder({ type: 'timestamp' }),
|
|
90
|
+
date: () => new ColumnBuilder({ type: 'date' }),
|
|
91
|
+
time: () => new ColumnBuilder({ type: 'time' }),
|
|
92
|
+
|
|
93
|
+
// Boolean
|
|
94
|
+
boolean: () => new ColumnBuilder({ type: 'boolean' }),
|
|
95
|
+
|
|
96
|
+
// JSON
|
|
97
|
+
json: () => new ColumnBuilder({ type: 'json' }),
|
|
98
|
+
jsonb: () => new ColumnBuilder({ type: 'jsonb' }),
|
|
99
|
+
|
|
100
|
+
// Binary
|
|
101
|
+
binary: () => new ColumnBuilder({ type: 'binary' }),
|
|
102
|
+
|
|
103
|
+
// UUID
|
|
104
|
+
uuid: () => new ColumnBuilder({ type: 'uuid' }),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ─── Table helper ─────────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
export function table(
|
|
110
|
+
columns: Record<string, ColumnBuilder>,
|
|
111
|
+
): TableDefinition {
|
|
112
|
+
const built: Record<string, ColumnDefinition> = {};
|
|
113
|
+
for (const [name, builder] of Object.entries(columns)) {
|
|
114
|
+
built[name] = builder.build();
|
|
115
|
+
}
|
|
116
|
+
return { columns: built, indexes: [] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── defineSchema ─────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
export function defineSchema(
|
|
122
|
+
tables: Record<string, TableDefinition>,
|
|
123
|
+
): SchemaDefinition {
|
|
124
|
+
return { tables };
|
|
125
|
+
}
|