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.
- package/README.md +16 -0
- package/dist/cli.js +404 -24
- 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
|
-
|
|
109743
|
-
|
|
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
|
-
|
|
111296
|
-
|
|
111297
|
-
|
|
111298
|
-
|
|
111299
|
-
if (
|
|
111300
|
-
|
|
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;
|