kysely-gen 0.10.0 → 0.11.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 (2) hide show
  1. package/dist/cli.js +285 -18
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -109667,6 +109667,25 @@ 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
+ type = { kind: "primitive", value: "boolean" };
109673
+ if (column.isNullable) {
109674
+ type = {
109675
+ kind: "union",
109676
+ types: [type, { kind: "primitive", value: "null" }]
109677
+ };
109678
+ }
109679
+ } else {
109680
+ const literalTypes = column.checkConstraint.values.map((v) => ({
109681
+ kind: "literal",
109682
+ value: v
109683
+ }));
109684
+ if (column.isNullable) {
109685
+ literalTypes.push({ kind: "primitive", value: "null" });
109686
+ }
109687
+ type = { kind: "union", types: literalTypes };
109688
+ }
109670
109689
  } else {
109671
109690
  type = mapType(column.dataType, {
109672
109691
  isNullable: column.isNullable,
@@ -109723,25 +109742,139 @@ var Result = import_lib.default.Result;
109723
109742
  var TypeOverrides = import_lib.default.TypeOverrides;
109724
109743
  var defaults = import_lib.default.defaults;
109725
109744
 
109745
+ // src/utils/check-constraint-parser.ts
109746
+ var ANY_ARRAY_REGEX = /= ANY \(\(?ARRAY\[(.*?)\]\)?(?:::[\w\[\]]+)?\)/;
109747
+ function parseCheckConstraint(definition) {
109748
+ const anyArrayMatch = definition.match(ANY_ARRAY_REGEX);
109749
+ if (anyArrayMatch) {
109750
+ const arrayContent = anyArrayMatch[1];
109751
+ if (!arrayContent || arrayContent.trim() === "")
109752
+ return null;
109753
+ const numericValues = parseNumericArray(arrayContent);
109754
+ if (numericValues !== null) {
109755
+ return { type: "number", values: numericValues };
109756
+ }
109757
+ const stringValues = parseStringArray(arrayContent);
109758
+ if (stringValues !== null && stringValues.length > 0) {
109759
+ return { type: "string", values: stringValues };
109760
+ }
109761
+ }
109762
+ const orValues = parseOrChain(definition);
109763
+ if (orValues !== null && orValues.length > 0) {
109764
+ return { type: "string", values: orValues };
109765
+ }
109766
+ return null;
109767
+ }
109768
+ function parseNumericArray(arrayContent) {
109769
+ const isNumeric = /^\s*-?\d+(\s*,\s*-?\d+)*\s*$/.test(arrayContent);
109770
+ if (!isNumeric)
109771
+ return null;
109772
+ const values = arrayContent.split(",").map((v) => parseInt(v.trim(), 10));
109773
+ if (values.some((v) => isNaN(v)))
109774
+ return null;
109775
+ if (values.length === 0)
109776
+ return null;
109777
+ return values;
109778
+ }
109779
+ function parseStringArray(arrayContent) {
109780
+ const values = [];
109781
+ let current = "";
109782
+ let inQuote = false;
109783
+ let i = 0;
109784
+ while (i < arrayContent.length) {
109785
+ const char = arrayContent[i];
109786
+ if (char === "'" && !inQuote) {
109787
+ inQuote = true;
109788
+ i++;
109789
+ continue;
109790
+ }
109791
+ if (char === "'" && inQuote) {
109792
+ if (arrayContent[i + 1] === "'") {
109793
+ current += "'";
109794
+ i += 2;
109795
+ continue;
109796
+ }
109797
+ values.push(current);
109798
+ current = "";
109799
+ inQuote = false;
109800
+ i++;
109801
+ continue;
109802
+ }
109803
+ if (inQuote) {
109804
+ current += char;
109805
+ }
109806
+ i++;
109807
+ }
109808
+ return values.length > 0 ? values : null;
109809
+ }
109810
+ function parseOrChain(definition) {
109811
+ const singleValueRegex = /\([^)]+= '([^']+)'(?:::[\w]+)?\)/g;
109812
+ const values = [];
109813
+ let match;
109814
+ if (!definition.includes(" OR ")) {
109815
+ return null;
109816
+ }
109817
+ while ((match = singleValueRegex.exec(definition)) !== null) {
109818
+ let value = match[1];
109819
+ value = value.replace(/''/g, "'");
109820
+ values.push(value);
109821
+ }
109822
+ return values.length > 0 ? values : null;
109823
+ }
109824
+ var SQLITE_IN_REGEX = /\w+\s+IN\s*\(([^)]+)\)/i;
109825
+ function parseSqliteCheckConstraint(definition) {
109826
+ const match = definition.match(SQLITE_IN_REGEX);
109827
+ if (!match)
109828
+ return null;
109829
+ const valuesPart = match[1];
109830
+ if (!valuesPart || valuesPart.trim() === "")
109831
+ return null;
109832
+ const numericValues = parseNumericArray(valuesPart);
109833
+ if (numericValues !== null) {
109834
+ if (isBooleanPattern(numericValues)) {
109835
+ return { type: "boolean" };
109836
+ }
109837
+ return { type: "number", values: numericValues };
109838
+ }
109839
+ const stringValues = parseStringArray(valuesPart);
109840
+ if (stringValues !== null && stringValues.length > 0) {
109841
+ return { type: "string", values: stringValues };
109842
+ }
109843
+ return null;
109844
+ }
109845
+ function isBooleanPattern(values) {
109846
+ if (values.length !== 2)
109847
+ return false;
109848
+ const sorted = [...values].sort((a, b) => a - b);
109849
+ return sorted[0] === 0 && sorted[1] === 1;
109850
+ }
109851
+
109726
109852
  // src/dialects/postgres/introspect.ts
109727
109853
  async function introspectPostgres(db, options) {
109728
- const [domains, partitions, baseTables, regularViews, materializedViews, enums] = await Promise.all([
109854
+ const [domains, partitions, baseTables, regularViews, materializedViews, enums, checkConstraints, domainCheckConstraints] = await Promise.all([
109729
109855
  introspectDomains(db),
109730
109856
  introspectPartitions(db),
109731
109857
  introspectTables(db, options.schemas),
109732
109858
  introspectViews(db, options.schemas),
109733
109859
  introspectMaterializedViews(db, options.schemas),
109734
- introspectEnums(db, options.schemas)
109860
+ introspectEnums(db, options.schemas),
109861
+ introspectCheckConstraints(db, options.schemas),
109862
+ introspectDomainCheckConstraints(db, options.schemas)
109735
109863
  ]);
109736
109864
  const tables = [...baseTables, ...regularViews, ...materializedViews].map((table) => {
109737
109865
  const isPartition = partitions.some((partition) => partition.schema === table.schema && partition.name === table.name);
109738
109866
  return {
109739
109867
  ...table,
109740
109868
  isPartition: isPartition || undefined,
109741
- columns: table.columns.map((column) => ({
109742
- ...column,
109743
- dataType: getRootType(column, domains)
109744
- }))
109869
+ columns: table.columns.map((column) => {
109870
+ const rootType = getRootType(column, domains);
109871
+ const checkConstraint = getCheckConstraint(table.schema, table.name, column, checkConstraints, domainCheckConstraints, domains);
109872
+ return {
109873
+ ...column,
109874
+ dataType: rootType,
109875
+ ...checkConstraint && { checkConstraint }
109876
+ };
109877
+ })
109745
109878
  };
109746
109879
  });
109747
109880
  return {
@@ -109761,6 +109894,8 @@ async function introspectTables(db, schemas) {
109761
109894
  c.is_nullable,
109762
109895
  c.is_identity,
109763
109896
  c.column_default,
109897
+ c.domain_name,
109898
+ c.domain_schema,
109764
109899
  pg_catalog.col_description(
109765
109900
  (c.table_schema||'.'||c.table_name)::regclass::oid,
109766
109901
  c.ordinal_position
@@ -109804,6 +109939,10 @@ async function introspectTables(db, schemas) {
109804
109939
  if (row.column_comment) {
109805
109940
  columnMetadata.comment = row.column_comment;
109806
109941
  }
109942
+ if (row.domain_name) {
109943
+ columnMetadata.domainName = row.domain_name;
109944
+ columnMetadata.domainSchema = row.domain_schema ?? undefined;
109945
+ }
109807
109946
  table.columns.push(columnMetadata);
109808
109947
  }
109809
109948
  }
@@ -109821,6 +109960,8 @@ async function introspectViews(db, schemas) {
109821
109960
  c.is_nullable,
109822
109961
  c.is_identity,
109823
109962
  c.column_default,
109963
+ c.domain_name,
109964
+ c.domain_schema,
109824
109965
  pg_catalog.col_description(
109825
109966
  (c.table_schema||'.'||c.table_name)::regclass::oid,
109826
109967
  c.ordinal_position
@@ -109865,6 +110006,10 @@ async function introspectViews(db, schemas) {
109865
110006
  if (row.column_comment) {
109866
110007
  columnMetadata.comment = row.column_comment;
109867
110008
  }
110009
+ if (row.domain_name) {
110010
+ columnMetadata.domainName = row.domain_name;
110011
+ columnMetadata.domainSchema = row.domain_schema ?? undefined;
110012
+ }
109868
110013
  table.columns.push(columnMetadata);
109869
110014
  }
109870
110015
  }
@@ -110007,6 +110152,72 @@ function parsePostgresArray(value) {
110007
110152
  }
110008
110153
  return [value];
110009
110154
  }
110155
+ async function introspectCheckConstraints(db, schemas) {
110156
+ const rawConstraints = await sql`
110157
+ SELECT
110158
+ n.nspname AS table_schema,
110159
+ c.relname AS table_name,
110160
+ a.attname AS column_name,
110161
+ pg_get_constraintdef(pgc.oid) AS check_definition
110162
+ FROM pg_constraint pgc
110163
+ JOIN pg_class c ON pgc.conrelid = c.oid
110164
+ JOIN pg_namespace n ON c.relnamespace = n.oid
110165
+ JOIN pg_attribute a ON a.attrelid = pgc.conrelid AND a.attnum = ANY(pgc.conkey)
110166
+ WHERE pgc.contype = 'c'
110167
+ AND array_length(pgc.conkey, 1) = 1
110168
+ AND n.nspname = ANY(${schemas})
110169
+ `.execute(db);
110170
+ const constraintMap = new Map;
110171
+ for (const row of rawConstraints.rows) {
110172
+ const key = `${row.table_schema}.${row.table_name}.${row.column_name}`;
110173
+ if (constraintMap.has(key))
110174
+ continue;
110175
+ const parsed = parseCheckConstraint(row.check_definition);
110176
+ if (parsed) {
110177
+ constraintMap.set(key, parsed);
110178
+ }
110179
+ }
110180
+ return constraintMap;
110181
+ }
110182
+ async function introspectDomainCheckConstraints(db, schemas) {
110183
+ const rawConstraints = await sql`
110184
+ SELECT
110185
+ n.nspname AS domain_schema,
110186
+ t.typname AS domain_name,
110187
+ pg_get_constraintdef(pgc.oid) AS check_definition
110188
+ FROM pg_constraint pgc
110189
+ JOIN pg_type t ON pgc.contypid = t.oid
110190
+ JOIN pg_namespace n ON t.typnamespace = n.oid
110191
+ WHERE pgc.contype = 'c'
110192
+ AND n.nspname = ANY(${schemas})
110193
+ `.execute(db);
110194
+ const constraintMap = new Map;
110195
+ for (const row of rawConstraints.rows) {
110196
+ const key = `${row.domain_schema}.${row.domain_name}`;
110197
+ if (constraintMap.has(key))
110198
+ continue;
110199
+ const parsed = parseCheckConstraint(row.check_definition);
110200
+ if (parsed) {
110201
+ constraintMap.set(key, parsed);
110202
+ }
110203
+ }
110204
+ return constraintMap;
110205
+ }
110206
+ function getCheckConstraint(tableSchema, tableName, column, checkConstraints, domainCheckConstraints, domains) {
110207
+ const columnKey = `${tableSchema}.${tableName}.${column.name}`;
110208
+ const directConstraint = checkConstraints.get(columnKey);
110209
+ if (directConstraint) {
110210
+ return directConstraint;
110211
+ }
110212
+ if (column.domainName && column.domainSchema) {
110213
+ const domainKey = `${column.domainSchema}.${column.domainName}`;
110214
+ const domainConstraint = domainCheckConstraints.get(domainKey);
110215
+ if (domainConstraint) {
110216
+ return domainConstraint;
110217
+ }
110218
+ }
110219
+ return;
110220
+ }
110010
110221
 
110011
110222
  // src/dialects/postgres/type-mapper.ts
110012
110223
  function helper(name, options) {
@@ -110457,10 +110668,27 @@ class MysqlDialect2 {
110457
110668
  }
110458
110669
  }
110459
110670
 
110671
+ // src/utils/sqlite-ddl-parser.ts
110672
+ function parseSqliteTableDDL(sql2) {
110673
+ const constraints = [];
110674
+ const checkRegex = /CHECK\s*\(\s*(\w+)\s+IN\s*\(([^)]+)\)\s*\)/gi;
110675
+ let match;
110676
+ while ((match = checkRegex.exec(sql2)) !== null) {
110677
+ const columnName = match[1];
110678
+ const valuesPart = match[2];
110679
+ constraints.push({
110680
+ columnName,
110681
+ definition: `${columnName} IN (${valuesPart})`
110682
+ });
110683
+ }
110684
+ return constraints;
110685
+ }
110686
+
110460
110687
  // src/dialects/sqlite/introspect.ts
110461
110688
  async function introspectSqlite(db, _options) {
110689
+ const checkConstraints = await introspectCheckConstraints2(db);
110462
110690
  const [baseTables, views] = await Promise.all([
110463
- introspectTables3(db),
110691
+ introspectTables3(db, checkConstraints),
110464
110692
  introspectViews3(db)
110465
110693
  ]);
110466
110694
  return {
@@ -110468,7 +110696,7 @@ async function introspectSqlite(db, _options) {
110468
110696
  enums: []
110469
110697
  };
110470
110698
  }
110471
- async function introspectTables3(db) {
110699
+ async function introspectTables3(db, checkConstraints) {
110472
110700
  const rawTables = await sql`
110473
110701
  SELECT name, type FROM sqlite_master
110474
110702
  WHERE type = 'table'
@@ -110477,7 +110705,7 @@ async function introspectTables3(db) {
110477
110705
  `.execute(db);
110478
110706
  const tables = [];
110479
110707
  for (const rawTable of rawTables.rows) {
110480
- const columns = await introspectColumns(db, rawTable.name, false);
110708
+ const columns = await introspectColumns(db, rawTable.name, false, checkConstraints);
110481
110709
  tables.push({
110482
110710
  schema: "main",
110483
110711
  name: rawTable.name,
@@ -110504,20 +110732,23 @@ async function introspectViews3(db) {
110504
110732
  }
110505
110733
  return tables;
110506
110734
  }
110507
- async function introspectColumns(db, tableName, isView) {
110735
+ async function introspectColumns(db, tableName, isView, checkConstraints) {
110508
110736
  const rawColumns = await sql`
110509
110737
  PRAGMA table_info(${sql.raw(`'${tableName}'`)})
110510
110738
  `.execute(db);
110511
110739
  return rawColumns.rows.map((col) => {
110512
110740
  const isIntegerPk = col.pk === 1 && col.type.toUpperCase() === "INTEGER";
110513
110741
  const isAutoIncrement = !isView && isIntegerPk;
110742
+ const constraintKey = `${tableName}.${col.name}`;
110743
+ const checkConstraint = checkConstraints?.get(constraintKey);
110514
110744
  return {
110515
110745
  name: col.name,
110516
110746
  dataType: normalizeDataType2(col.type),
110517
110747
  dataTypeSchema: "main",
110518
110748
  isNullable: col.notnull === 0 && col.pk === 0,
110519
110749
  isAutoIncrement,
110520
- hasDefaultValue: col.dflt_value !== null || isAutoIncrement
110750
+ hasDefaultValue: col.dflt_value !== null || isAutoIncrement,
110751
+ ...checkConstraint && { checkConstraint }
110521
110752
  };
110522
110753
  });
110523
110754
  }
@@ -110528,6 +110759,26 @@ function normalizeDataType2(type) {
110528
110759
  }
110529
110760
  return lowerType;
110530
110761
  }
110762
+ async function introspectCheckConstraints2(db) {
110763
+ const result = await sql`
110764
+ SELECT name, sql FROM sqlite_master
110765
+ WHERE type = 'table'
110766
+ AND name NOT LIKE 'sqlite_%'
110767
+ AND sql IS NOT NULL
110768
+ `.execute(db);
110769
+ const constraints = new Map;
110770
+ for (const row of result.rows) {
110771
+ const parsed = parseSqliteTableDDL(row.sql);
110772
+ for (const { columnName, definition } of parsed) {
110773
+ const constraint = parseSqliteCheckConstraint(definition);
110774
+ if (constraint) {
110775
+ const key = `${row.name}.${columnName}`;
110776
+ constraints.set(key, constraint);
110777
+ }
110778
+ }
110779
+ }
110780
+ return constraints;
110781
+ }
110531
110782
 
110532
110783
  // src/dialects/sqlite/type-mapper.ts
110533
110784
  function mapSqliteType(dataType, options) {
@@ -111289,20 +111540,36 @@ function transformColumnToZod(column, enums, enumResolver, mode, options) {
111289
111540
  const columnName = options?.camelCase ? toCamelCase(column.name) : column.name;
111290
111541
  const matchingEnum = enums.find((e) => e.name === column.dataType && e.schema === (column.dataTypeSchema ?? "public"));
111291
111542
  let schema;
111543
+ const modifiers = [];
111544
+ if (column.isNullable)
111545
+ modifiers.push("nullable");
111546
+ const isOptional = mode === "update" || mode === "insert" && (column.isAutoIncrement || column.hasDefaultValue);
111547
+ if (isOptional)
111548
+ modifiers.push("optional");
111292
111549
  if (matchingEnum) {
111293
111550
  const enumName = enumResolver.getName(matchingEnum);
111294
111551
  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");
111552
+ if (modifiers.length > 0) {
111553
+ schema = { kind: "zod-modified", schema, modifiers };
111554
+ }
111555
+ } else if (column.checkConstraint) {
111556
+ if (column.checkConstraint.type === "boolean") {
111557
+ schema = { kind: "zod-primitive", method: "boolean" };
111558
+ } else if (column.checkConstraint.type === "string") {
111559
+ schema = { kind: "zod-enum", values: column.checkConstraint.values };
111560
+ } else {
111561
+ schema = {
111562
+ kind: "zod-union",
111563
+ schemas: column.checkConstraint.values.map((v) => ({
111564
+ kind: "zod-literal",
111565
+ value: v
111566
+ }))
111567
+ };
111568
+ }
111301
111569
  if (modifiers.length > 0) {
111302
111570
  schema = { kind: "zod-modified", schema, modifiers };
111303
111571
  }
111304
111572
  } else {
111305
- const isOptional = mode === "update" || mode === "insert" && (column.isAutoIncrement || column.hasDefaultValue);
111306
111573
  schema = mapPostgresTypeToZod(column.dataType, {
111307
111574
  isNullable: column.isNullable,
111308
111575
  isArray: column.isArray,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely-gen",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Database type generator for Kysely - Supports PostgreSQL, MySQL, SQLite, and MSSQL",
5
5
  "type": "module",
6
6
  "license": "MIT",