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/dist/index.js ADDED
@@ -0,0 +1,573 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ColumnBuilder: () => ColumnBuilder,
24
+ MigrationGenerator: () => MigrationGenerator,
25
+ SchemaDiffer: () => SchemaDiffer,
26
+ TypeGenerator: () => TypeGenerator,
27
+ column: () => column,
28
+ configTemplate: () => configTemplate,
29
+ defineSchema: () => defineSchema,
30
+ describeOperation: () => describeOperation,
31
+ operationLabels: () => operationLabels,
32
+ schemaTemplate: () => schemaTemplate,
33
+ table: () => table,
34
+ validateSchema: () => validateSchema
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/schema/dsl.ts
39
+ var ColumnBuilder = class {
40
+ def;
41
+ constructor(init) {
42
+ this.def = { ...init };
43
+ }
44
+ primaryKey() {
45
+ this.def.primaryKey = true;
46
+ return this;
47
+ }
48
+ notNull() {
49
+ this.def.notNull = true;
50
+ return this;
51
+ }
52
+ nullable() {
53
+ this.def.nullable = true;
54
+ return this;
55
+ }
56
+ unique() {
57
+ this.def.unique = true;
58
+ return this;
59
+ }
60
+ default(value) {
61
+ this.def.default = value;
62
+ return this;
63
+ }
64
+ references(table2, col) {
65
+ this.def.references = { table: table2, column: col };
66
+ return this;
67
+ }
68
+ onDelete(action) {
69
+ this.def.onDelete = action;
70
+ return this;
71
+ }
72
+ onUpdate(action) {
73
+ this.def.onUpdate = action;
74
+ return this;
75
+ }
76
+ index() {
77
+ this.def.index = true;
78
+ return this;
79
+ }
80
+ check(expression) {
81
+ this.def.check = expression;
82
+ return this;
83
+ }
84
+ /** @internal Return the raw column definition. */
85
+ build() {
86
+ return { ...this.def };
87
+ }
88
+ };
89
+ var column = {
90
+ // Numeric types
91
+ serial: () => new ColumnBuilder({ type: "serial" }),
92
+ integer: () => new ColumnBuilder({ type: "integer" }),
93
+ bigint: () => new ColumnBuilder({ type: "bigint" }),
94
+ decimal: (precision, scale) => new ColumnBuilder({ type: "decimal", precision, scale }),
95
+ // Text types
96
+ text: () => new ColumnBuilder({ type: "text" }),
97
+ varchar: (length) => new ColumnBuilder({ type: "varchar", length }),
98
+ // Date / Time types
99
+ timestamp: () => new ColumnBuilder({ type: "timestamp" }),
100
+ date: () => new ColumnBuilder({ type: "date" }),
101
+ time: () => new ColumnBuilder({ type: "time" }),
102
+ // Boolean
103
+ boolean: () => new ColumnBuilder({ type: "boolean" }),
104
+ // JSON
105
+ json: () => new ColumnBuilder({ type: "json" }),
106
+ jsonb: () => new ColumnBuilder({ type: "jsonb" }),
107
+ // Binary
108
+ binary: () => new ColumnBuilder({ type: "binary" }),
109
+ // UUID
110
+ uuid: () => new ColumnBuilder({ type: "uuid" })
111
+ };
112
+ function table(columns) {
113
+ const built = {};
114
+ for (const [name, builder] of Object.entries(columns)) {
115
+ built[name] = builder.build();
116
+ }
117
+ return { columns: built, indexes: [] };
118
+ }
119
+ function defineSchema(tables) {
120
+ return { tables };
121
+ }
122
+
123
+ // src/schema/validators.ts
124
+ function validateSchema(schema) {
125
+ const errors = [];
126
+ const tableNames = Object.keys(schema.tables);
127
+ for (const [tableName, tableDef] of Object.entries(schema.tables)) {
128
+ const columnNames = Object.keys(tableDef.columns);
129
+ if (columnNames.length === 0) {
130
+ errors.push({
131
+ table: tableName,
132
+ message: "Table has no columns defined."
133
+ });
134
+ }
135
+ const pks = columnNames.filter((c) => tableDef.columns[c].primaryKey);
136
+ if (pks.length === 0) {
137
+ errors.push({
138
+ table: tableName,
139
+ message: "Table has no primary key defined."
140
+ });
141
+ }
142
+ for (const [colName, colDef] of Object.entries(tableDef.columns)) {
143
+ if (colDef.notNull && colDef.nullable) {
144
+ errors.push({
145
+ table: tableName,
146
+ column: colName,
147
+ message: "Column cannot be both notNull and nullable."
148
+ });
149
+ }
150
+ if (colDef.references) {
151
+ if (!tableNames.includes(colDef.references.table)) {
152
+ errors.push({
153
+ table: tableName,
154
+ column: colName,
155
+ message: `Foreign key references unknown table '${colDef.references.table}'.`
156
+ });
157
+ } else {
158
+ const targetTable = schema.tables[colDef.references.table];
159
+ if (targetTable && !Object.keys(targetTable.columns).includes(colDef.references.column)) {
160
+ errors.push({
161
+ table: tableName,
162
+ column: colName,
163
+ message: `Foreign key references unknown column '${colDef.references.column}' in table '${colDef.references.table}'.`
164
+ });
165
+ }
166
+ }
167
+ }
168
+ if ((colDef.onDelete || colDef.onUpdate) && !colDef.references) {
169
+ errors.push({
170
+ table: tableName,
171
+ column: colName,
172
+ message: "onDelete/onUpdate specified without a foreign key reference."
173
+ });
174
+ }
175
+ if (colDef.type === "varchar" && colDef.length !== void 0 && colDef.length <= 0) {
176
+ errors.push({
177
+ table: tableName,
178
+ column: colName,
179
+ message: "varchar length must be a positive number."
180
+ });
181
+ }
182
+ if (colDef.type === "decimal") {
183
+ if (colDef.precision !== void 0 && colDef.precision <= 0) {
184
+ errors.push({
185
+ table: tableName,
186
+ column: colName,
187
+ message: "decimal precision must be a positive number."
188
+ });
189
+ }
190
+ if (colDef.scale !== void 0 && colDef.precision !== void 0 && colDef.scale > colDef.precision) {
191
+ errors.push({
192
+ table: tableName,
193
+ column: colName,
194
+ message: "decimal scale cannot exceed precision."
195
+ });
196
+ }
197
+ }
198
+ }
199
+ }
200
+ return errors;
201
+ }
202
+
203
+ // src/generators/migration.ts
204
+ var MigrationGenerator = class {
205
+ /**
206
+ * Generate a full CREATE-TABLE migration for the entire schema.
207
+ */
208
+ generate(schema, migrationName) {
209
+ const timestamp = this.generateTimestamp();
210
+ const filename = `${timestamp}_${this.sanitize(migrationName)}.ts`;
211
+ const upCode = this.generateUpFunction(schema);
212
+ const downCode = this.generateDownFunction(schema);
213
+ const content = [
214
+ `import { Kysely, sql } from 'kysely';`,
215
+ ``,
216
+ `export async function up(db: Kysely<any>): Promise<void> {`,
217
+ upCode,
218
+ `}`,
219
+ ``,
220
+ `export async function down(db: Kysely<any>): Promise<void> {`,
221
+ downCode,
222
+ `}`,
223
+ ``
224
+ ].join("\n");
225
+ return { filename, content };
226
+ }
227
+ // ── Private helpers ──────────────────────────────────────────────────────
228
+ generateUpFunction(schema) {
229
+ const parts = [];
230
+ for (const [tableName, tableDef] of Object.entries(schema.tables)) {
231
+ parts.push(this.generateCreateTable(tableName, tableDef));
232
+ const indexes = this.generateIndexes(tableName, tableDef);
233
+ if (indexes) parts.push(indexes);
234
+ }
235
+ return parts.join("\n");
236
+ }
237
+ generateCreateTable(name, def) {
238
+ const lines = [];
239
+ lines.push(` await db.schema`);
240
+ lines.push(` .createTable('${name}')`);
241
+ for (const [colName, colDef] of Object.entries(def.columns)) {
242
+ lines.push(this.generateColumnDefinition(colName, colDef));
243
+ }
244
+ lines.push(` .execute();`);
245
+ return lines.join("\n");
246
+ }
247
+ generateColumnDefinition(name, def) {
248
+ const type = this.mapColumnType(def);
249
+ const modifiers = this.buildModifiers(def);
250
+ if (modifiers.length > 0) {
251
+ return ` .addColumn('${name}', '${type}', (col) => col.${modifiers.join(".")})`;
252
+ }
253
+ return ` .addColumn('${name}', '${type}')`;
254
+ }
255
+ buildModifiers(def) {
256
+ const mods = [];
257
+ if (def.primaryKey) mods.push("primaryKey()");
258
+ if (def.notNull) mods.push("notNull()");
259
+ if (def.unique) mods.push("unique()");
260
+ if (def.default !== void 0) {
261
+ mods.push(`defaultTo(${this.formatDefaultValue(def.default, def.type)})`);
262
+ }
263
+ if (def.references) {
264
+ mods.push(`references('${def.references.table}.${def.references.column}')`);
265
+ if (def.onDelete) mods.push(`onDelete('${def.onDelete}')`);
266
+ if (def.onUpdate) mods.push(`onUpdate('${def.onUpdate}')`);
267
+ }
268
+ if (def.check) {
269
+ mods.push(`check(sql\`${def.check}\`)`);
270
+ }
271
+ return mods;
272
+ }
273
+ generateDownFunction(schema) {
274
+ const tables = Object.keys(schema.tables).reverse();
275
+ return tables.map((t) => ` await db.schema.dropTable('${t}').ifExists().execute();`).join("\n");
276
+ }
277
+ generateIndexes(tableName, tableDef) {
278
+ const lines = [];
279
+ for (const [colName, colDef] of Object.entries(tableDef.columns)) {
280
+ if (colDef.index || colDef.references) {
281
+ lines.push(` await db.schema`);
282
+ lines.push(` .createIndex('${tableName}_${colName}_index')`);
283
+ lines.push(` .on('${tableName}')`);
284
+ lines.push(` .column('${colName}')`);
285
+ lines.push(` .execute();`);
286
+ }
287
+ }
288
+ for (const idx of tableDef.indexes) {
289
+ const idxName = idx.name || `${tableName}_${idx.columns.join("_")}_index`;
290
+ lines.push(` await db.schema`);
291
+ if (idx.unique) {
292
+ lines.push(` .createIndex('${idxName}')`);
293
+ lines.push(` .on('${tableName}')`);
294
+ lines.push(` .columns([${idx.columns.map((c) => `'${c}'`).join(", ")}])`);
295
+ lines.push(` .unique()`);
296
+ } else {
297
+ lines.push(` .createIndex('${idxName}')`);
298
+ lines.push(` .on('${tableName}')`);
299
+ lines.push(` .columns([${idx.columns.map((c) => `'${c}'`).join(", ")}])`);
300
+ }
301
+ lines.push(` .execute();`);
302
+ }
303
+ return lines.length > 0 ? lines.join("\n") : "";
304
+ }
305
+ mapColumnType(def) {
306
+ switch (def.type) {
307
+ case "varchar":
308
+ return def.length ? `varchar(${def.length})` : "varchar";
309
+ case "decimal":
310
+ if (def.precision !== void 0 && def.scale !== void 0) {
311
+ return `decimal(${def.precision}, ${def.scale})`;
312
+ }
313
+ return "decimal";
314
+ default:
315
+ return def.type;
316
+ }
317
+ }
318
+ formatDefaultValue(value, _type) {
319
+ if (typeof value === "string" && value === "now()") return "sql`now()`";
320
+ if (typeof value === "string") return `'${value}'`;
321
+ if (typeof value === "boolean") return value.toString();
322
+ return String(value);
323
+ }
324
+ generateTimestamp() {
325
+ const now = /* @__PURE__ */ new Date();
326
+ return now.toISOString().replace(/[-:]/g, "").split(".")[0];
327
+ }
328
+ sanitize(name) {
329
+ return name.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
330
+ }
331
+ };
332
+
333
+ // src/generators/types.ts
334
+ var TypeGenerator = class {
335
+ generate(schema) {
336
+ const lines = [];
337
+ lines.push("// Auto-generated by kysely-schema \u2014 DO NOT EDIT");
338
+ lines.push("");
339
+ lines.push("import type { Generated, ColumnType } from 'kysely';");
340
+ lines.push("");
341
+ for (const [tableName, tableDef] of Object.entries(schema.tables)) {
342
+ const pascalName = this.toPascalCase(tableName);
343
+ lines.push(`export interface ${pascalName}Table {`);
344
+ for (const [colName, colDef] of Object.entries(tableDef.columns)) {
345
+ const tsType = this.mapToTypeScript(colDef);
346
+ lines.push(` ${colName}: ${tsType};`);
347
+ }
348
+ lines.push("}");
349
+ lines.push("");
350
+ }
351
+ lines.push("export interface Database {");
352
+ for (const tableName of Object.keys(schema.tables)) {
353
+ const pascalName = this.toPascalCase(tableName);
354
+ lines.push(` ${tableName}: ${pascalName}Table;`);
355
+ }
356
+ lines.push("}");
357
+ lines.push("");
358
+ return lines.join("\n");
359
+ }
360
+ // ── Private helpers ──────────────────────────────────────────────────
361
+ mapToTypeScript(def) {
362
+ const baseType = this.baseTypeMap(def.type);
363
+ let tsType = baseType;
364
+ if (def.type === "serial") {
365
+ tsType = `Generated<${baseType}>`;
366
+ }
367
+ if (def.default !== void 0 && def.type !== "serial") {
368
+ tsType = `Generated<${baseType}>`;
369
+ }
370
+ if (def.nullable || !def.notNull && !def.primaryKey && def.type !== "serial") {
371
+ tsType += " | null";
372
+ }
373
+ return tsType;
374
+ }
375
+ baseTypeMap(type) {
376
+ const map = {
377
+ serial: "number",
378
+ integer: "number",
379
+ bigint: "string",
380
+ decimal: "string",
381
+ text: "string",
382
+ varchar: "string",
383
+ timestamp: "Date",
384
+ date: "Date",
385
+ time: "string",
386
+ boolean: "boolean",
387
+ json: "unknown",
388
+ jsonb: "unknown",
389
+ binary: "Buffer",
390
+ uuid: "string"
391
+ };
392
+ return map[type] || "unknown";
393
+ }
394
+ toPascalCase(str) {
395
+ return str.split(/[_\- ]+/).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
396
+ }
397
+ };
398
+
399
+ // src/generators/templates.ts
400
+ var schemaTemplate = `import { defineSchema, table, column } from 'kysely-schema';
401
+
402
+ export default defineSchema({
403
+ // Define your tables here. Example:
404
+ //
405
+ // user: table({
406
+ // id: column.serial().primaryKey(),
407
+ // email: column.text().notNull().unique(),
408
+ // name: column.text().nullable(),
409
+ // createdAt: column.timestamp().default('now()').notNull(),
410
+ // }),
411
+ });
412
+ `;
413
+ var configTemplate = `import type { KyselySchemaConfig } from 'kysely-schema';
414
+
415
+ const config: KyselySchemaConfig = {
416
+ schemaPath: './schema/index.ts',
417
+ migrationsDir: './migrations',
418
+ generatedDir: './generated',
419
+ snapshotDir: './.kysely-schema',
420
+ };
421
+
422
+ export default config;
423
+ `;
424
+
425
+ // src/differ/index.ts
426
+ var SchemaDiffer = class {
427
+ diff(previous, current) {
428
+ const ops = [];
429
+ const prevTables = new Set(Object.keys(previous.tables));
430
+ const currTables = new Set(Object.keys(current.tables));
431
+ for (const tableName of currTables) {
432
+ if (!prevTables.has(tableName)) {
433
+ ops.push({
434
+ type: "addTable",
435
+ tableName,
436
+ table: current.tables[tableName],
437
+ description: `Create table '${tableName}'`
438
+ });
439
+ }
440
+ }
441
+ for (const tableName of prevTables) {
442
+ if (!currTables.has(tableName)) {
443
+ ops.push({
444
+ type: "dropTable",
445
+ tableName,
446
+ description: `Drop table '${tableName}'`
447
+ });
448
+ }
449
+ }
450
+ for (const tableName of currTables) {
451
+ if (!prevTables.has(tableName)) continue;
452
+ const prevCols = previous.tables[tableName].columns;
453
+ const currCols = current.tables[tableName].columns;
454
+ const prevColNames = new Set(Object.keys(prevCols));
455
+ const currColNames = new Set(Object.keys(currCols));
456
+ for (const colName of currColNames) {
457
+ if (!prevColNames.has(colName)) {
458
+ ops.push({
459
+ type: "addColumn",
460
+ tableName,
461
+ columnName: colName,
462
+ column: currCols[colName],
463
+ description: `Add column '${colName}' to table '${tableName}'`
464
+ });
465
+ }
466
+ }
467
+ for (const colName of prevColNames) {
468
+ if (!currColNames.has(colName)) {
469
+ ops.push({
470
+ type: "dropColumn",
471
+ tableName,
472
+ columnName: colName,
473
+ description: `Drop column '${colName}' from table '${tableName}'`
474
+ });
475
+ }
476
+ }
477
+ for (const colName of currColNames) {
478
+ if (!prevColNames.has(colName)) continue;
479
+ if (!this.columnsEqual(prevCols[colName], currCols[colName])) {
480
+ ops.push({
481
+ type: "alterColumn",
482
+ tableName,
483
+ columnName: colName,
484
+ oldColumn: prevCols[colName],
485
+ newColumn: currCols[colName],
486
+ description: `Alter column '${colName}' in table '${tableName}'`
487
+ });
488
+ }
489
+ }
490
+ }
491
+ return ops;
492
+ }
493
+ /**
494
+ * Generate an ALTER-TABLE migration string from a list of diff operations.
495
+ */
496
+ generateAlterMigration(ops) {
497
+ const upLines = [];
498
+ const downLines = [];
499
+ for (const op of ops) {
500
+ switch (op.type) {
501
+ case "addTable": {
502
+ upLines.push(` await db.schema`);
503
+ upLines.push(` .createTable('${op.tableName}')`);
504
+ for (const [colName, colDef] of Object.entries(op.table.columns)) {
505
+ upLines.push(` .addColumn('${colName}', '${colDef.type}')`);
506
+ }
507
+ upLines.push(` .execute();`);
508
+ downLines.push(` await db.schema.dropTable('${op.tableName}').ifExists().execute();`);
509
+ break;
510
+ }
511
+ case "dropTable": {
512
+ upLines.push(` await db.schema.dropTable('${op.tableName}').ifExists().execute();`);
513
+ downLines.push(` // TODO: Recreate table '${op.tableName}'`);
514
+ break;
515
+ }
516
+ case "addColumn": {
517
+ upLines.push(` await db.schema.alterTable('${op.tableName}').addColumn('${op.columnName}', '${op.column.type}').execute();`);
518
+ downLines.push(` await db.schema.alterTable('${op.tableName}').dropColumn('${op.columnName}').execute();`);
519
+ break;
520
+ }
521
+ case "dropColumn": {
522
+ upLines.push(` await db.schema.alterTable('${op.tableName}').dropColumn('${op.columnName}').execute();`);
523
+ downLines.push(` // TODO: Re-add column '${op.columnName}' to '${op.tableName}'`);
524
+ break;
525
+ }
526
+ case "alterColumn": {
527
+ upLines.push(` await db.schema.alterTable('${op.tableName}').alterColumn('${op.columnName}', (col) => col.setDataType('${op.newColumn.type}')).execute();`);
528
+ downLines.push(` await db.schema.alterTable('${op.tableName}').alterColumn('${op.columnName}', (col) => col.setDataType('${op.oldColumn.type}')).execute();`);
529
+ break;
530
+ }
531
+ default:
532
+ break;
533
+ }
534
+ }
535
+ return {
536
+ up: upLines.join("\n"),
537
+ down: downLines.join("\n")
538
+ };
539
+ }
540
+ // ── Private helpers ──────────────────────────────────────────────────
541
+ columnsEqual(a, b) {
542
+ return JSON.stringify(a) === JSON.stringify(b);
543
+ }
544
+ };
545
+
546
+ // src/differ/operations.ts
547
+ var operationLabels = {
548
+ addTable: "+ ADD TABLE",
549
+ dropTable: "- DROP TABLE",
550
+ addColumn: "+ ADD COLUMN",
551
+ dropColumn: "- DROP COLUMN",
552
+ alterColumn: "~ ALTER COLUMN",
553
+ addIndex: "+ ADD INDEX",
554
+ dropIndex: "- DROP INDEX"
555
+ };
556
+ function describeOperation(op) {
557
+ return `${operationLabels[op.type]}: ${op.description}`;
558
+ }
559
+ // Annotate the CommonJS export names for ESM import in node:
560
+ 0 && (module.exports = {
561
+ ColumnBuilder,
562
+ MigrationGenerator,
563
+ SchemaDiffer,
564
+ TypeGenerator,
565
+ column,
566
+ configTemplate,
567
+ defineSchema,
568
+ describeOperation,
569
+ operationLabels,
570
+ schemaTemplate,
571
+ table,
572
+ validateSchema
573
+ });