kysely-gen 0.10.0 → 0.12.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.
Files changed (3) hide show
  1. package/README.md +16 -0
  2. package/dist/cli.js +404 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -52,6 +52,7 @@ npx kysely-gen --dialect mysql --url mysql://user:pass@localhost:3306/db
52
52
  | `--include-pattern <glob>` | Only include matching tables |
53
53
  | `--exclude-pattern <glob>` | Exclude matching tables |
54
54
  | `--zod` | Generate Zod schemas instead of TypeScript interfaces |
55
+ | `--no-boolean-coerce` | Output `0 \| 1` instead of coercing to `boolean` for CHECK constraints |
55
56
 
56
57
  ## Zod Schema Generation
57
58
 
@@ -159,6 +160,21 @@ export interface DB {
159
160
  - All MSSQL types: `uniqueidentifier`, `datetime2`, `datetimeoffset`, `money`, `xml`, `varbinary`, etc.
160
161
  - Both URL (`mssql://`) and ADO.NET connection string formats
161
162
 
163
+ ### Boolean Coercion (SQLite/MSSQL)
164
+
165
+ SQLite and MSSQL store booleans as integers (0/1). When a `CHECK(col IN (0, 1))` constraint is detected, kysely-gen generates:
166
+
167
+ **TypeScript:** `boolean`
168
+ **Zod:** `z.union([z.literal(0), z.literal(1)]).transform(v => v === 1)`
169
+
170
+ The Zod transform coerces the raw 0/1 from the database to `true`/`false`, ensuring runtime validation passes.
171
+
172
+ Use `--no-boolean-coerce` to output raw `0 | 1` instead:
173
+
174
+ ```sh
175
+ kysely-gen --zod --no-boolean-coerce
176
+ ```
177
+
162
178
  ## Type Mappings
163
179
 
164
180
  Types are generated to match the default behavior of each database driver. If you customize your driver's type parsers, the generated types may not match.
package/dist/cli.js CHANGED
@@ -109667,6 +109667,35 @@ function transformColumn(column, enums, enumResolver, mapType, options, unknownT
109667
109667
  types: [type, { kind: "primitive", value: "null" }]
109668
109668
  };
109669
109669
  }
109670
+ } else if (column.checkConstraint) {
109671
+ if (column.checkConstraint.type === "boolean") {
109672
+ if (options?.noBooleanCoerce) {
109673
+ type = {
109674
+ kind: "union",
109675
+ types: [
109676
+ { kind: "literal", value: 0 },
109677
+ { kind: "literal", value: 1 }
109678
+ ]
109679
+ };
109680
+ } else {
109681
+ type = { kind: "primitive", value: "boolean" };
109682
+ }
109683
+ if (column.isNullable) {
109684
+ type = {
109685
+ kind: "union",
109686
+ types: [type, { kind: "primitive", value: "null" }]
109687
+ };
109688
+ }
109689
+ } else {
109690
+ const literalTypes = column.checkConstraint.values.map((v) => ({
109691
+ kind: "literal",
109692
+ value: v
109693
+ }));
109694
+ if (column.isNullable) {
109695
+ literalTypes.push({ kind: "primitive", value: "null" });
109696
+ }
109697
+ type = { kind: "union", types: literalTypes };
109698
+ }
109670
109699
  } else {
109671
109700
  type = mapType(column.dataType, {
109672
109701
  isNullable: column.isNullable,
@@ -109723,25 +109752,179 @@ var Result = import_lib.default.Result;
109723
109752
  var TypeOverrides = import_lib.default.TypeOverrides;
109724
109753
  var defaults = import_lib.default.defaults;
109725
109754
 
109755
+ // src/utils/check-constraint-parser.ts
109756
+ var ANY_ARRAY_REGEX = /= ANY \(\(?ARRAY\[(.*?)\]\)?(?:::[\w\[\]]+)?\)/;
109757
+ function parseCheckConstraint(definition) {
109758
+ const anyArrayMatch = definition.match(ANY_ARRAY_REGEX);
109759
+ if (anyArrayMatch) {
109760
+ const arrayContent = anyArrayMatch[1];
109761
+ if (!arrayContent || arrayContent.trim() === "")
109762
+ return null;
109763
+ const numericValues = parseNumericArray(arrayContent);
109764
+ if (numericValues !== null) {
109765
+ return { type: "number", values: numericValues };
109766
+ }
109767
+ const stringValues = parseStringArray(arrayContent);
109768
+ if (stringValues !== null && stringValues.length > 0) {
109769
+ return { type: "string", values: stringValues };
109770
+ }
109771
+ }
109772
+ const orValues = parseOrChain(definition);
109773
+ if (orValues !== null && orValues.length > 0) {
109774
+ return { type: "string", values: orValues };
109775
+ }
109776
+ return null;
109777
+ }
109778
+ function parseNumericArray(arrayContent) {
109779
+ const isNumeric = /^\s*-?\d+(\s*,\s*-?\d+)*\s*$/.test(arrayContent);
109780
+ if (!isNumeric)
109781
+ return null;
109782
+ const values = arrayContent.split(",").map((v) => parseInt(v.trim(), 10));
109783
+ if (values.some((v) => isNaN(v)))
109784
+ return null;
109785
+ if (values.length === 0)
109786
+ return null;
109787
+ return values;
109788
+ }
109789
+ function parseStringArray(arrayContent) {
109790
+ const values = [];
109791
+ let current = "";
109792
+ let inQuote = false;
109793
+ let i = 0;
109794
+ while (i < arrayContent.length) {
109795
+ const char = arrayContent[i];
109796
+ if (char === "'" && !inQuote) {
109797
+ inQuote = true;
109798
+ i++;
109799
+ continue;
109800
+ }
109801
+ if (char === "'" && inQuote) {
109802
+ if (arrayContent[i + 1] === "'") {
109803
+ current += "'";
109804
+ i += 2;
109805
+ continue;
109806
+ }
109807
+ values.push(current);
109808
+ current = "";
109809
+ inQuote = false;
109810
+ i++;
109811
+ continue;
109812
+ }
109813
+ if (inQuote) {
109814
+ current += char;
109815
+ }
109816
+ i++;
109817
+ }
109818
+ return values.length > 0 ? values : null;
109819
+ }
109820
+ function parseOrChain(definition) {
109821
+ const singleValueRegex = /\([^)]+= '([^']+)'(?:::[\w]+)?\)/g;
109822
+ const values = [];
109823
+ let match;
109824
+ if (!definition.includes(" OR ")) {
109825
+ return null;
109826
+ }
109827
+ while ((match = singleValueRegex.exec(definition)) !== null) {
109828
+ let value = match[1];
109829
+ value = value.replace(/''/g, "'");
109830
+ values.push(value);
109831
+ }
109832
+ return values.length > 0 ? values : null;
109833
+ }
109834
+ var SQLITE_IN_REGEX = /\w+\s+IN\s*\(([^)]+)\)/i;
109835
+ function parseSqliteCheckConstraint(definition) {
109836
+ const match = definition.match(SQLITE_IN_REGEX);
109837
+ if (!match)
109838
+ return null;
109839
+ const valuesPart = match[1];
109840
+ if (!valuesPart || valuesPart.trim() === "")
109841
+ return null;
109842
+ const numericValues = parseNumericArray(valuesPart);
109843
+ if (numericValues !== null) {
109844
+ if (isBooleanPattern(numericValues)) {
109845
+ return { type: "boolean" };
109846
+ }
109847
+ return { type: "number", values: numericValues };
109848
+ }
109849
+ const stringValues = parseStringArray(valuesPart);
109850
+ if (stringValues !== null && stringValues.length > 0) {
109851
+ return { type: "string", values: stringValues };
109852
+ }
109853
+ return null;
109854
+ }
109855
+ function isBooleanPattern(values) {
109856
+ if (values.length !== 2)
109857
+ return false;
109858
+ const sorted = [...values].sort((a, b) => a - b);
109859
+ return sorted[0] === 0 && sorted[1] === 1;
109860
+ }
109861
+ var MSSQL_IN_REGEX = /\[\w+\]\s+IN\s*\(([^)]+)\)/i;
109862
+ function parseMssqlCheckConstraint(definition) {
109863
+ if (!definition || definition.trim() === "")
109864
+ return null;
109865
+ const inMatch = definition.match(MSSQL_IN_REGEX);
109866
+ if (inMatch) {
109867
+ const valuesPart = inMatch[1];
109868
+ if (!valuesPart || valuesPart.trim() === "")
109869
+ return null;
109870
+ const numericValues = parseNumericArray(valuesPart);
109871
+ if (numericValues !== null) {
109872
+ if (isBooleanPattern(numericValues)) {
109873
+ return { type: "boolean" };
109874
+ }
109875
+ return { type: "number", values: numericValues };
109876
+ }
109877
+ const stringValues = parseStringArray(valuesPart);
109878
+ if (stringValues !== null && stringValues.length > 0) {
109879
+ return { type: "string", values: stringValues };
109880
+ }
109881
+ }
109882
+ if (definition.includes(" OR ")) {
109883
+ const values = parseMssqlOrChain(definition);
109884
+ if (values !== null && values.length > 0) {
109885
+ return { type: "string", values };
109886
+ }
109887
+ }
109888
+ return null;
109889
+ }
109890
+ function parseMssqlOrChain(definition) {
109891
+ const values = [];
109892
+ let match;
109893
+ const regex2 = /\[\w+\]='((?:[^']|'')*)'/g;
109894
+ while ((match = regex2.exec(definition)) !== null) {
109895
+ let value = match[1];
109896
+ value = value.replace(/''/g, "'");
109897
+ values.push(value);
109898
+ }
109899
+ return values.length > 0 ? values : null;
109900
+ }
109901
+
109726
109902
  // src/dialects/postgres/introspect.ts
109727
109903
  async function introspectPostgres(db, options) {
109728
- const [domains, partitions, baseTables, regularViews, materializedViews, enums] = await Promise.all([
109904
+ const [domains, partitions, baseTables, regularViews, materializedViews, enums, checkConstraints, domainCheckConstraints] = await Promise.all([
109729
109905
  introspectDomains(db),
109730
109906
  introspectPartitions(db),
109731
109907
  introspectTables(db, options.schemas),
109732
109908
  introspectViews(db, options.schemas),
109733
109909
  introspectMaterializedViews(db, options.schemas),
109734
- introspectEnums(db, options.schemas)
109910
+ introspectEnums(db, options.schemas),
109911
+ introspectCheckConstraints(db, options.schemas),
109912
+ introspectDomainCheckConstraints(db, options.schemas)
109735
109913
  ]);
109736
109914
  const tables = [...baseTables, ...regularViews, ...materializedViews].map((table) => {
109737
109915
  const isPartition = partitions.some((partition) => partition.schema === table.schema && partition.name === table.name);
109738
109916
  return {
109739
109917
  ...table,
109740
109918
  isPartition: isPartition || undefined,
109741
- columns: table.columns.map((column) => ({
109742
- ...column,
109743
- dataType: getRootType(column, domains)
109744
- }))
109919
+ columns: table.columns.map((column) => {
109920
+ const rootType = getRootType(column, domains);
109921
+ const checkConstraint = getCheckConstraint(table.schema, table.name, column, checkConstraints, domainCheckConstraints, domains);
109922
+ return {
109923
+ ...column,
109924
+ dataType: rootType,
109925
+ ...checkConstraint && { checkConstraint }
109926
+ };
109927
+ })
109745
109928
  };
109746
109929
  });
109747
109930
  return {
@@ -109761,6 +109944,8 @@ async function introspectTables(db, schemas) {
109761
109944
  c.is_nullable,
109762
109945
  c.is_identity,
109763
109946
  c.column_default,
109947
+ c.domain_name,
109948
+ c.domain_schema,
109764
109949
  pg_catalog.col_description(
109765
109950
  (c.table_schema||'.'||c.table_name)::regclass::oid,
109766
109951
  c.ordinal_position
@@ -109804,6 +109989,10 @@ async function introspectTables(db, schemas) {
109804
109989
  if (row.column_comment) {
109805
109990
  columnMetadata.comment = row.column_comment;
109806
109991
  }
109992
+ if (row.domain_name) {
109993
+ columnMetadata.domainName = row.domain_name;
109994
+ columnMetadata.domainSchema = row.domain_schema ?? undefined;
109995
+ }
109807
109996
  table.columns.push(columnMetadata);
109808
109997
  }
109809
109998
  }
@@ -109821,6 +110010,8 @@ async function introspectViews(db, schemas) {
109821
110010
  c.is_nullable,
109822
110011
  c.is_identity,
109823
110012
  c.column_default,
110013
+ c.domain_name,
110014
+ c.domain_schema,
109824
110015
  pg_catalog.col_description(
109825
110016
  (c.table_schema||'.'||c.table_name)::regclass::oid,
109826
110017
  c.ordinal_position
@@ -109865,6 +110056,10 @@ async function introspectViews(db, schemas) {
109865
110056
  if (row.column_comment) {
109866
110057
  columnMetadata.comment = row.column_comment;
109867
110058
  }
110059
+ if (row.domain_name) {
110060
+ columnMetadata.domainName = row.domain_name;
110061
+ columnMetadata.domainSchema = row.domain_schema ?? undefined;
110062
+ }
109868
110063
  table.columns.push(columnMetadata);
109869
110064
  }
109870
110065
  }
@@ -110007,6 +110202,72 @@ function parsePostgresArray(value) {
110007
110202
  }
110008
110203
  return [value];
110009
110204
  }
110205
+ async function introspectCheckConstraints(db, schemas) {
110206
+ const rawConstraints = await sql`
110207
+ SELECT
110208
+ n.nspname AS table_schema,
110209
+ c.relname AS table_name,
110210
+ a.attname AS column_name,
110211
+ pg_get_constraintdef(pgc.oid) AS check_definition
110212
+ FROM pg_constraint pgc
110213
+ JOIN pg_class c ON pgc.conrelid = c.oid
110214
+ JOIN pg_namespace n ON c.relnamespace = n.oid
110215
+ JOIN pg_attribute a ON a.attrelid = pgc.conrelid AND a.attnum = ANY(pgc.conkey)
110216
+ WHERE pgc.contype = 'c'
110217
+ AND array_length(pgc.conkey, 1) = 1
110218
+ AND n.nspname = ANY(${schemas})
110219
+ `.execute(db);
110220
+ const constraintMap = new Map;
110221
+ for (const row of rawConstraints.rows) {
110222
+ const key = `${row.table_schema}.${row.table_name}.${row.column_name}`;
110223
+ if (constraintMap.has(key))
110224
+ continue;
110225
+ const parsed = parseCheckConstraint(row.check_definition);
110226
+ if (parsed) {
110227
+ constraintMap.set(key, parsed);
110228
+ }
110229
+ }
110230
+ return constraintMap;
110231
+ }
110232
+ async function introspectDomainCheckConstraints(db, schemas) {
110233
+ const rawConstraints = await sql`
110234
+ SELECT
110235
+ n.nspname AS domain_schema,
110236
+ t.typname AS domain_name,
110237
+ pg_get_constraintdef(pgc.oid) AS check_definition
110238
+ FROM pg_constraint pgc
110239
+ JOIN pg_type t ON pgc.contypid = t.oid
110240
+ JOIN pg_namespace n ON t.typnamespace = n.oid
110241
+ WHERE pgc.contype = 'c'
110242
+ AND n.nspname = ANY(${schemas})
110243
+ `.execute(db);
110244
+ const constraintMap = new Map;
110245
+ for (const row of rawConstraints.rows) {
110246
+ const key = `${row.domain_schema}.${row.domain_name}`;
110247
+ if (constraintMap.has(key))
110248
+ continue;
110249
+ const parsed = parseCheckConstraint(row.check_definition);
110250
+ if (parsed) {
110251
+ constraintMap.set(key, parsed);
110252
+ }
110253
+ }
110254
+ return constraintMap;
110255
+ }
110256
+ function getCheckConstraint(tableSchema, tableName, column, checkConstraints, domainCheckConstraints, domains) {
110257
+ const columnKey = `${tableSchema}.${tableName}.${column.name}`;
110258
+ const directConstraint = checkConstraints.get(columnKey);
110259
+ if (directConstraint) {
110260
+ return directConstraint;
110261
+ }
110262
+ if (column.domainName && column.domainSchema) {
110263
+ const domainKey = `${column.domainSchema}.${column.domainName}`;
110264
+ const domainConstraint = domainCheckConstraints.get(domainKey);
110265
+ if (domainConstraint) {
110266
+ return domainConstraint;
110267
+ }
110268
+ }
110269
+ return;
110270
+ }
110010
110271
 
110011
110272
  // src/dialects/postgres/type-mapper.ts
110012
110273
  function helper(name, options) {
@@ -110457,10 +110718,27 @@ class MysqlDialect2 {
110457
110718
  }
110458
110719
  }
110459
110720
 
110721
+ // src/utils/sqlite-ddl-parser.ts
110722
+ function parseSqliteTableDDL(sql2) {
110723
+ const constraints = [];
110724
+ const checkRegex = /CHECK\s*\(\s*(\w+)\s+IN\s*\(([^)]+)\)\s*\)/gi;
110725
+ let match;
110726
+ while ((match = checkRegex.exec(sql2)) !== null) {
110727
+ const columnName = match[1];
110728
+ const valuesPart = match[2];
110729
+ constraints.push({
110730
+ columnName,
110731
+ definition: `${columnName} IN (${valuesPart})`
110732
+ });
110733
+ }
110734
+ return constraints;
110735
+ }
110736
+
110460
110737
  // src/dialects/sqlite/introspect.ts
110461
110738
  async function introspectSqlite(db, _options) {
110739
+ const checkConstraints = await introspectCheckConstraints2(db);
110462
110740
  const [baseTables, views] = await Promise.all([
110463
- introspectTables3(db),
110741
+ introspectTables3(db, checkConstraints),
110464
110742
  introspectViews3(db)
110465
110743
  ]);
110466
110744
  return {
@@ -110468,7 +110746,7 @@ async function introspectSqlite(db, _options) {
110468
110746
  enums: []
110469
110747
  };
110470
110748
  }
110471
- async function introspectTables3(db) {
110749
+ async function introspectTables3(db, checkConstraints) {
110472
110750
  const rawTables = await sql`
110473
110751
  SELECT name, type FROM sqlite_master
110474
110752
  WHERE type = 'table'
@@ -110477,7 +110755,7 @@ async function introspectTables3(db) {
110477
110755
  `.execute(db);
110478
110756
  const tables = [];
110479
110757
  for (const rawTable of rawTables.rows) {
110480
- const columns = await introspectColumns(db, rawTable.name, false);
110758
+ const columns = await introspectColumns(db, rawTable.name, false, checkConstraints);
110481
110759
  tables.push({
110482
110760
  schema: "main",
110483
110761
  name: rawTable.name,
@@ -110504,20 +110782,23 @@ async function introspectViews3(db) {
110504
110782
  }
110505
110783
  return tables;
110506
110784
  }
110507
- async function introspectColumns(db, tableName, isView) {
110785
+ async function introspectColumns(db, tableName, isView, checkConstraints) {
110508
110786
  const rawColumns = await sql`
110509
110787
  PRAGMA table_info(${sql.raw(`'${tableName}'`)})
110510
110788
  `.execute(db);
110511
110789
  return rawColumns.rows.map((col) => {
110512
110790
  const isIntegerPk = col.pk === 1 && col.type.toUpperCase() === "INTEGER";
110513
110791
  const isAutoIncrement = !isView && isIntegerPk;
110792
+ const constraintKey = `${tableName}.${col.name}`;
110793
+ const checkConstraint = checkConstraints?.get(constraintKey);
110514
110794
  return {
110515
110795
  name: col.name,
110516
110796
  dataType: normalizeDataType2(col.type),
110517
110797
  dataTypeSchema: "main",
110518
110798
  isNullable: col.notnull === 0 && col.pk === 0,
110519
110799
  isAutoIncrement,
110520
- hasDefaultValue: col.dflt_value !== null || isAutoIncrement
110800
+ hasDefaultValue: col.dflt_value !== null || isAutoIncrement,
110801
+ ...checkConstraint && { checkConstraint }
110521
110802
  };
110522
110803
  });
110523
110804
  }
@@ -110528,6 +110809,26 @@ function normalizeDataType2(type) {
110528
110809
  }
110529
110810
  return lowerType;
110530
110811
  }
110812
+ async function introspectCheckConstraints2(db) {
110813
+ const result = await sql`
110814
+ SELECT name, sql FROM sqlite_master
110815
+ WHERE type = 'table'
110816
+ AND name NOT LIKE 'sqlite_%'
110817
+ AND sql IS NOT NULL
110818
+ `.execute(db);
110819
+ const constraints = new Map;
110820
+ for (const row of result.rows) {
110821
+ const parsed = parseSqliteTableDDL(row.sql);
110822
+ for (const { columnName, definition } of parsed) {
110823
+ const constraint = parseSqliteCheckConstraint(definition);
110824
+ if (constraint) {
110825
+ const key = `${row.name}.${columnName}`;
110826
+ constraints.set(key, constraint);
110827
+ }
110828
+ }
110829
+ }
110830
+ return constraints;
110831
+ }
110531
110832
 
110532
110833
  // src/dialects/sqlite/type-mapper.ts
110533
110834
  function mapSqliteType(dataType, options) {
@@ -110606,11 +110907,22 @@ class SqliteDialect2 {
110606
110907
 
110607
110908
  // src/dialects/mssql/introspect.ts
110608
110909
  async function introspectMssql(db, options) {
110609
- const [baseTables, views] = await Promise.all([
110910
+ const [baseTables, views, checkConstraints] = await Promise.all([
110610
110911
  introspectTables4(db, options.schemas),
110611
- introspectViews4(db, options.schemas)
110912
+ introspectViews4(db, options.schemas),
110913
+ introspectCheckConstraints3(db, options.schemas)
110612
110914
  ]);
110613
- const tables = [...baseTables, ...views];
110915
+ const tables = [...baseTables, ...views].map((table) => ({
110916
+ ...table,
110917
+ columns: table.columns.map((column) => {
110918
+ const key = `${table.schema}.${table.name}.${column.name}`;
110919
+ const checkConstraint = checkConstraints.get(key);
110920
+ return {
110921
+ ...column,
110922
+ ...checkConstraint && { checkConstraint }
110923
+ };
110924
+ })
110925
+ }));
110614
110926
  return {
110615
110927
  tables,
110616
110928
  enums: []
@@ -110686,6 +110998,35 @@ function buildTableMetadata2(rows, isView) {
110686
110998
  }
110687
110999
  return Array.from(tableMap.values());
110688
111000
  }
111001
+ async function introspectCheckConstraints3(db, schemas) {
111002
+ const rawConstraints = await sql`
111003
+ SELECT
111004
+ s.name AS schema_name,
111005
+ t.name AS table_name,
111006
+ c.name AS column_name,
111007
+ cc.definition AS check_definition
111008
+ FROM sys.check_constraints cc
111009
+ JOIN sys.tables t ON cc.parent_object_id = t.object_id
111010
+ JOIN sys.schemas s ON t.schema_id = s.schema_id
111011
+ LEFT JOIN sys.columns c ON cc.parent_column_id = c.column_id AND c.object_id = t.object_id
111012
+ WHERE cc.is_disabled = 0
111013
+ AND cc.parent_column_id > 0
111014
+ AND s.name IN (${sql.join(schemas.map((s) => sql`${s}`))})
111015
+ `.execute(db);
111016
+ const constraintMap = new Map;
111017
+ for (const row of rawConstraints.rows) {
111018
+ if (!row.column_name)
111019
+ continue;
111020
+ const key = `${row.schema_name}.${row.table_name}.${row.column_name}`;
111021
+ if (constraintMap.has(key))
111022
+ continue;
111023
+ const parsed = parseMssqlCheckConstraint(row.check_definition);
111024
+ if (parsed) {
111025
+ constraintMap.set(key, parsed);
111026
+ }
111027
+ }
111028
+ return constraintMap;
111029
+ }
110689
111030
 
110690
111031
  // src/dialects/mssql/type-mapper.ts
110691
111032
  function mapMssqlType(dataType, options) {
@@ -111289,20 +111630,51 @@ function transformColumnToZod(column, enums, enumResolver, mode, options) {
111289
111630
  const columnName = options?.camelCase ? toCamelCase(column.name) : column.name;
111290
111631
  const matchingEnum = enums.find((e) => e.name === column.dataType && e.schema === (column.dataTypeSchema ?? "public"));
111291
111632
  let schema;
111633
+ const modifiers = [];
111634
+ if (column.isNullable)
111635
+ modifiers.push("nullable");
111636
+ const isOptional = mode === "update" || mode === "insert" && (column.isAutoIncrement || column.hasDefaultValue);
111637
+ if (isOptional)
111638
+ modifiers.push("optional");
111292
111639
  if (matchingEnum) {
111293
111640
  const enumName = enumResolver.getName(matchingEnum);
111294
111641
  schema = { kind: "zod-reference", name: `${uncapitalize(enumName)}Schema` };
111295
- const modifiers = [];
111296
- if (column.isNullable)
111297
- modifiers.push("nullable");
111298
- const isOptional = mode === "update" || mode === "insert" && (column.isAutoIncrement || column.hasDefaultValue);
111299
- if (isOptional)
111300
- modifiers.push("optional");
111642
+ if (modifiers.length > 0) {
111643
+ schema = { kind: "zod-modified", schema, modifiers };
111644
+ }
111645
+ } else if (column.checkConstraint) {
111646
+ if (column.checkConstraint.type === "boolean") {
111647
+ const unionSchema = {
111648
+ kind: "zod-union",
111649
+ schemas: [
111650
+ { kind: "zod-literal", value: 0 },
111651
+ { kind: "zod-literal", value: 1 }
111652
+ ]
111653
+ };
111654
+ if (options?.noBooleanCoerce) {
111655
+ schema = unionSchema;
111656
+ } else {
111657
+ schema = {
111658
+ kind: "zod-transform",
111659
+ schema: unionSchema,
111660
+ transformFn: "v => v === 1"
111661
+ };
111662
+ }
111663
+ } else if (column.checkConstraint.type === "string") {
111664
+ schema = { kind: "zod-enum", values: column.checkConstraint.values };
111665
+ } else {
111666
+ schema = {
111667
+ kind: "zod-union",
111668
+ schemas: column.checkConstraint.values.map((v) => ({
111669
+ kind: "zod-literal",
111670
+ value: v
111671
+ }))
111672
+ };
111673
+ }
111301
111674
  if (modifiers.length > 0) {
111302
111675
  schema = { kind: "zod-modified", schema, modifiers };
111303
111676
  }
111304
111677
  } else {
111305
- const isOptional = mode === "update" || mode === "insert" && (column.isAutoIncrement || column.hasDefaultValue);
111306
111678
  schema = mapPostgresTypeToZod(column.dataType, {
111307
111679
  isNullable: column.isNullable,
111308
111680
  isArray: column.isArray,
@@ -111446,6 +111818,8 @@ function serializeZodSchema(node) {
111446
111818
  return serializeZodReference(node);
111447
111819
  case "zod-custom":
111448
111820
  return serializeZodCustom(node);
111821
+ case "zod-transform":
111822
+ return serializeZodTransform(node);
111449
111823
  }
111450
111824
  }
111451
111825
  function serializeZodPrimitive(node) {
@@ -111495,6 +111869,9 @@ function serializeZodReference(node) {
111495
111869
  function serializeZodCustom(node) {
111496
111870
  return `z.custom<${node.typeReference}>()`;
111497
111871
  }
111872
+ function serializeZodTransform(node) {
111873
+ return `${serializeZodSchema(node.schema)}.transform(${node.transformFn})`;
111874
+ }
111498
111875
  function serializeZodDeclaration(node) {
111499
111876
  switch (node.kind) {
111500
111877
  case "zod-import":
@@ -111521,7 +111898,7 @@ function serializeZod(program2) {
111521
111898
 
111522
111899
  // src/cli.ts
111523
111900
  var program2 = new Command;
111524
- program2.name("kysely-gen").description("Generate Kysely types from your database").version("0.1.0").option("-o, --out <path>", "Output file path", "./db.d.ts").option("-s, --schema <name>", "Schema to introspect (can be specified multiple times)", collect, []).option("--url <connection-string>", "Database connection string (overrides DATABASE_URL env)").option("-d, --dialect <name>", "Database dialect (postgres, mysql, sqlite). Auto-detected from URL if not specified").option("--camel-case", "Convert column and table names to camelCase (use with Kysely CamelCasePlugin)").option("--include-pattern <pattern>", "Only include tables matching glob pattern (schema.table format)", collect, []).option("--exclude-pattern <pattern>", "Exclude tables matching glob pattern (schema.table format)", collect, []).option("--print", "Output to stdout instead of writing to file").option("--verify", "Verify types match existing file (exit 1 if different)").option("--zod", "Generate Zod schemas with inferred types instead of TypeScript interfaces").action(async (options) => {
111901
+ program2.name("kysely-gen").description("Generate Kysely types from your database").version("0.1.0").option("-o, --out <path>", "Output file path", "./db.d.ts").option("-s, --schema <name>", "Schema to introspect (can be specified multiple times)", collect, []).option("--url <connection-string>", "Database connection string (overrides DATABASE_URL env)").option("-d, --dialect <name>", "Database dialect (postgres, mysql, sqlite). Auto-detected from URL if not specified").option("--camel-case", "Convert column and table names to camelCase (use with Kysely CamelCasePlugin)").option("--include-pattern <pattern>", "Only include tables matching glob pattern (schema.table format)", collect, []).option("--exclude-pattern <pattern>", "Exclude tables matching glob pattern (schema.table format)", collect, []).option("--print", "Output to stdout instead of writing to file").option("--verify", "Verify types match existing file (exit 1 if different)").option("--zod", "Generate Zod schemas with inferred types instead of TypeScript interfaces").option("--no-boolean-coerce", "Output 0 | 1 instead of coercing to boolean for CHECK(col IN (0, 1))").action(async (options) => {
111525
111902
  try {
111526
111903
  await generate(options);
111527
111904
  } catch (error2) {
@@ -111610,10 +111987,12 @@ async function generate(options) {
111610
111987
  spinner.succeed(`Found ${source_default.bold(tableCount)} tables and ${source_default.bold(enumCount)} enums`);
111611
111988
  let code;
111612
111989
  let warnings = [];
111990
+ const noBooleanCoerce = options.booleanCoerce === false;
111613
111991
  if (options.zod) {
111614
111992
  spinner.start("Generating Zod schemas...");
111615
111993
  const zodProgram = transformDatabaseToZod(metadata, {
111616
- camelCase: options.camelCase
111994
+ camelCase: options.camelCase,
111995
+ noBooleanCoerce
111617
111996
  });
111618
111997
  code = serializeZod(zodProgram);
111619
111998
  } else {
@@ -111622,7 +112001,8 @@ async function generate(options) {
111622
112001
  dialectName,
111623
112002
  camelCase: options.camelCase,
111624
112003
  includePattern: options.includePattern.length > 0 ? options.includePattern : undefined,
111625
- excludePattern: options.excludePattern.length > 0 ? options.excludePattern : undefined
112004
+ excludePattern: options.excludePattern.length > 0 ? options.excludePattern : undefined,
112005
+ noBooleanCoerce
111626
112006
  });
111627
112007
  code = serialize(astProgram);
111628
112008
  warnings = tsWarnings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely-gen",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Database type generator for Kysely - Supports PostgreSQL, MySQL, SQLite, and MSSQL",
5
5
  "type": "module",
6
6
  "license": "MIT",