mutano 3.1.5 → 3.2.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 +72 -0
- package/dist/main.js +48 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,6 +191,7 @@ CREATE TABLE `user` (
|
|
|
191
191
|
`name` varchar(255) COMMENT '@zod(z.string().min(2).max(50))',
|
|
192
192
|
`email` varchar(255) COMMENT '@ts(EmailAddress) @kysely(string)',
|
|
193
193
|
`metadata` json COMMENT '@ts(UserMetadata)',
|
|
194
|
+
`password_hash` varchar(255) COMMENT '@ignore',
|
|
194
195
|
PRIMARY KEY (`id`)
|
|
195
196
|
);
|
|
196
197
|
```
|
|
@@ -199,6 +200,77 @@ CREATE TABLE `user` (
|
|
|
199
200
|
- `@zod(...)` - Override Zod schema
|
|
200
201
|
- `@ts(...)` - Override TypeScript type
|
|
201
202
|
- `@kysely(...)` - Override Kysely type
|
|
203
|
+
- `@ignore` - Exclude column from generated types
|
|
204
|
+
- `@@ignore` - Exclude table/model from generated types
|
|
205
|
+
|
|
206
|
+
### Ignoring Columns and Tables
|
|
207
|
+
|
|
208
|
+
Use `@ignore` and `@@ignore` directives to exclude columns and tables from code generation:
|
|
209
|
+
|
|
210
|
+
#### Prisma Schemas
|
|
211
|
+
|
|
212
|
+
**Ignore specific fields:**
|
|
213
|
+
```prisma
|
|
214
|
+
model User {
|
|
215
|
+
id Int @id @default(autoincrement())
|
|
216
|
+
email String @unique
|
|
217
|
+
password String @ignore // This field will be excluded
|
|
218
|
+
createdAt DateTime @default(now())
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Ignore entire models:**
|
|
223
|
+
```prisma
|
|
224
|
+
model AuditLog {
|
|
225
|
+
id Int @id @default(autoincrement())
|
|
226
|
+
action String
|
|
227
|
+
userId Int
|
|
228
|
+
timestamp DateTime @default(now())
|
|
229
|
+
|
|
230
|
+
@@ignore // This entire model will be excluded
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### SQL Databases (MySQL, PostgreSQL, SQLite)
|
|
235
|
+
|
|
236
|
+
**Ignore specific columns in MySQL:**
|
|
237
|
+
```sql
|
|
238
|
+
ALTER TABLE users MODIFY COLUMN password_hash VARCHAR(255) COMMENT '@ignore';
|
|
239
|
+
ALTER TABLE users MODIFY COLUMN internal_id VARCHAR(100) COMMENT 'Internal tracking @ignore';
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Ignore specific columns in PostgreSQL:**
|
|
243
|
+
```sql
|
|
244
|
+
COMMENT ON COLUMN users.password_hash IS '@ignore';
|
|
245
|
+
COMMENT ON COLUMN users.internal_id IS 'Internal tracking @ignore';
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Ignore entire tables in MySQL:**
|
|
249
|
+
```sql
|
|
250
|
+
ALTER TABLE audit_logs COMMENT = '@@ignore';
|
|
251
|
+
ALTER TABLE internal_metrics COMMENT = 'Internal table @@ignore';
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Ignore entire tables in PostgreSQL:**
|
|
255
|
+
```sql
|
|
256
|
+
COMMENT ON TABLE audit_logs IS '@@ignore';
|
|
257
|
+
COMMENT ON TABLE internal_metrics IS 'Internal table @@ignore';
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Example with mixed ignored and non-ignored columns:**
|
|
261
|
+
```sql
|
|
262
|
+
CREATE TABLE `user` (
|
|
263
|
+
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
264
|
+
`email` varchar(255) NOT NULL,
|
|
265
|
+
`name` varchar(255),
|
|
266
|
+
`password_hash` varchar(255) COMMENT '@ignore',
|
|
267
|
+
`internal_tracking_id` varchar(100) COMMENT 'Internal use only @ignore',
|
|
268
|
+
`metadata` json COMMENT '@ts(UserMetadata)',
|
|
269
|
+
PRIMARY KEY (`id`)
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Generated types will only include: `id`, `email`, `name`, and `metadata`
|
|
202
274
|
|
|
203
275
|
## Type Overrides
|
|
204
276
|
|
package/dist/main.js
CHANGED
|
@@ -200,6 +200,12 @@ const extractTypeExpression = (comment, prefix) => {
|
|
|
200
200
|
const extractTSExpression = (comment) => extractTypeExpression(comment, "@ts(");
|
|
201
201
|
const extractKyselyExpression = (comment) => extractTypeExpression(comment, "@kysely(");
|
|
202
202
|
const extractZodExpression = (comment) => extractTypeExpression(comment, "@zod(");
|
|
203
|
+
const hasIgnoreDirective = (comment) => {
|
|
204
|
+
return comment.includes("@ignore");
|
|
205
|
+
};
|
|
206
|
+
const hasTableIgnoreDirective = (comment) => {
|
|
207
|
+
return comment.includes("@@ignore");
|
|
208
|
+
};
|
|
203
209
|
|
|
204
210
|
function getType(op, desc, config, destination) {
|
|
205
211
|
const { Default, Extra, Null, Type, Comment, EnumOptions } = desc;
|
|
@@ -218,8 +224,7 @@ function getType(op, desc, config, destination) {
|
|
|
218
224
|
if (config.magicComments) {
|
|
219
225
|
const kyselyOverrideType = extractKyselyExpression(Comment);
|
|
220
226
|
if (kyselyOverrideType) {
|
|
221
|
-
|
|
222
|
-
return shouldBeNullable2 ? kyselyOverrideType.includes("| null") ? kyselyOverrideType : `${kyselyOverrideType} | null` : kyselyOverrideType;
|
|
227
|
+
return kyselyOverrideType;
|
|
223
228
|
}
|
|
224
229
|
}
|
|
225
230
|
const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
|
|
@@ -228,53 +233,20 @@ function getType(op, desc, config, destination) {
|
|
|
228
233
|
if (isKyselyDestination && config.magicComments) {
|
|
229
234
|
const kyselyOverrideType = extractKyselyExpression(Comment);
|
|
230
235
|
if (kyselyOverrideType) {
|
|
231
|
-
|
|
232
|
-
return shouldBeNullable ? kyselyOverrideType.includes("| null") ? kyselyOverrideType : `${kyselyOverrideType} | null` : kyselyOverrideType;
|
|
236
|
+
return kyselyOverrideType;
|
|
233
237
|
}
|
|
234
238
|
}
|
|
235
239
|
if ((isTsDestination || isKyselyDestination) && config.magicComments) {
|
|
236
240
|
const tsOverrideType = extractTSExpression(Comment);
|
|
237
241
|
if (tsOverrideType) {
|
|
238
|
-
|
|
239
|
-
return shouldBeNullable ? tsOverrideType.includes("| null") ? tsOverrideType : `${tsOverrideType} | null` : tsOverrideType;
|
|
242
|
+
return tsOverrideType;
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
if (isZodDestination && config.magicComments) {
|
|
244
247
|
const zodOverrideType = extractZodExpression(Comment);
|
|
245
248
|
if (zodOverrideType) {
|
|
246
|
-
|
|
247
|
-
const shouldBeOptional = op === "insertable" && (hasDefaultValue || isGenerated) || op === "updateable";
|
|
248
|
-
const nullishOption = destination.nullish;
|
|
249
|
-
const nullableMethod = nullishOption && op !== "selectable" ? "nullish" : "nullable";
|
|
250
|
-
let finalType = zodOverrideType;
|
|
251
|
-
if (shouldBeNullable && shouldBeOptional) {
|
|
252
|
-
if (!zodOverrideType.includes(`.${nullableMethod}()`) && !zodOverrideType.includes(".optional()")) {
|
|
253
|
-
finalType = `${zodOverrideType}.${nullableMethod}()`;
|
|
254
|
-
}
|
|
255
|
-
} else if (shouldBeNullable) {
|
|
256
|
-
if (!zodOverrideType.includes(`.${nullableMethod}()`) && !zodOverrideType.includes(".optional()")) {
|
|
257
|
-
finalType = `${zodOverrideType}.${nullableMethod}()`;
|
|
258
|
-
}
|
|
259
|
-
} else if (shouldBeOptional) {
|
|
260
|
-
if (!zodOverrideType.includes(".optional()") && !zodOverrideType.includes(`.${nullableMethod}()`)) {
|
|
261
|
-
finalType = `${zodOverrideType}.optional()`;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if ((op === "table" || op === "insertable") && hasDefaultValue && Default !== null && !isGenerated) {
|
|
265
|
-
let defaultValueFormatted = Default;
|
|
266
|
-
if (typeMappings.stringTypes.includes(type) || typeMappings.dateTypes.includes(type)) {
|
|
267
|
-
defaultValueFormatted = `'${Default}'`;
|
|
268
|
-
} else if (typeMappings.booleanTypes.includes(type)) {
|
|
269
|
-
defaultValueFormatted = Default.toLowerCase() === "true" ? "true" : "false";
|
|
270
|
-
} else if (typeMappings.numberTypes.includes(type)) {
|
|
271
|
-
defaultValueFormatted = Default;
|
|
272
|
-
} else {
|
|
273
|
-
defaultValueFormatted = `'${Default}'`;
|
|
274
|
-
}
|
|
275
|
-
finalType = `${finalType}.default(${defaultValueFormatted})`;
|
|
276
|
-
}
|
|
277
|
-
return finalType;
|
|
249
|
+
return zodOverrideType;
|
|
278
250
|
}
|
|
279
251
|
}
|
|
280
252
|
const overrideTypes = config.origin.overrideTypes;
|
|
@@ -804,23 +776,23 @@ async function extractTables(db, config) {
|
|
|
804
776
|
switch (origin.type) {
|
|
805
777
|
case "mysql":
|
|
806
778
|
const mysqlTables = await db.raw(`
|
|
807
|
-
SELECT table_name
|
|
808
|
-
FROM information_schema.tables
|
|
779
|
+
SELECT table_name, table_comment
|
|
780
|
+
FROM information_schema.tables
|
|
809
781
|
WHERE table_schema = ? AND table_type = 'BASE TABLE'
|
|
810
782
|
`, [origin.database]);
|
|
811
|
-
return mysqlTables[0].map((row) => row.table_name);
|
|
783
|
+
return mysqlTables[0].filter((row) => !hasTableIgnoreDirective(row.table_comment || "")).map((row) => row.table_name);
|
|
812
784
|
case "postgres":
|
|
813
785
|
const schema = origin.schema || "public";
|
|
814
786
|
const postgresTables = await db.raw(`
|
|
815
|
-
SELECT table_name
|
|
816
|
-
FROM information_schema.tables
|
|
787
|
+
SELECT table_name
|
|
788
|
+
FROM information_schema.tables
|
|
817
789
|
WHERE table_schema = ? AND table_type = 'BASE TABLE'
|
|
818
790
|
`, [schema]);
|
|
819
791
|
return postgresTables.rows.map((row) => row.table_name);
|
|
820
792
|
case "sqlite":
|
|
821
793
|
const sqliteTables = await db.raw(`
|
|
822
|
-
SELECT name
|
|
823
|
-
FROM sqlite_master
|
|
794
|
+
SELECT name
|
|
795
|
+
FROM sqlite_master
|
|
824
796
|
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
|
|
825
797
|
`);
|
|
826
798
|
return sqliteTables.map((row) => row.name);
|
|
@@ -833,23 +805,23 @@ async function extractViews(db, config) {
|
|
|
833
805
|
switch (origin.type) {
|
|
834
806
|
case "mysql":
|
|
835
807
|
const mysqlViews = await db.raw(`
|
|
836
|
-
SELECT table_name
|
|
837
|
-
FROM information_schema.tables
|
|
808
|
+
SELECT table_name, table_comment
|
|
809
|
+
FROM information_schema.tables
|
|
838
810
|
WHERE table_schema = ? AND table_type = 'VIEW'
|
|
839
811
|
`, [origin.database]);
|
|
840
|
-
return mysqlViews[0].map((row) => row.table_name);
|
|
812
|
+
return mysqlViews[0].filter((row) => !hasTableIgnoreDirective(row.table_comment || "")).map((row) => row.table_name);
|
|
841
813
|
case "postgres":
|
|
842
814
|
const schema = origin.schema || "public";
|
|
843
815
|
const postgresViews = await db.raw(`
|
|
844
|
-
SELECT table_name
|
|
845
|
-
FROM information_schema.tables
|
|
816
|
+
SELECT table_name
|
|
817
|
+
FROM information_schema.tables
|
|
846
818
|
WHERE table_schema = ? AND table_type = 'VIEW'
|
|
847
819
|
`, [schema]);
|
|
848
820
|
return postgresViews.rows.map((row) => row.table_name);
|
|
849
821
|
case "sqlite":
|
|
850
822
|
const sqliteViews = await db.raw(`
|
|
851
|
-
SELECT name
|
|
852
|
-
FROM sqlite_master
|
|
823
|
+
SELECT name
|
|
824
|
+
FROM sqlite_master
|
|
853
825
|
WHERE type = 'view'
|
|
854
826
|
`);
|
|
855
827
|
return sqliteViews.map((row) => row.name);
|
|
@@ -862,18 +834,18 @@ async function extractColumnDescriptions(db, config, tableName) {
|
|
|
862
834
|
switch (origin.type) {
|
|
863
835
|
case "mysql":
|
|
864
836
|
const mysqlColumns = await db.raw(`
|
|
865
|
-
SELECT
|
|
837
|
+
SELECT
|
|
866
838
|
column_name as \`Field\`,
|
|
867
839
|
column_default as \`Default\`,
|
|
868
840
|
extra as \`Extra\`,
|
|
869
841
|
is_nullable as \`Null\`,
|
|
870
842
|
column_type as \`Type\`,
|
|
871
843
|
column_comment as \`Comment\`
|
|
872
|
-
FROM information_schema.columns
|
|
844
|
+
FROM information_schema.columns
|
|
873
845
|
WHERE table_schema = ? AND table_name = ?
|
|
874
846
|
ORDER BY ordinal_position
|
|
875
847
|
`, [origin.database, tableName]);
|
|
876
|
-
return mysqlColumns[0].map((row) => ({
|
|
848
|
+
return mysqlColumns[0].filter((row) => !hasIgnoreDirective(row.Comment || "")).map((row) => ({
|
|
877
849
|
Field: row.Field,
|
|
878
850
|
Default: row.Default,
|
|
879
851
|
Extra: row.Extra || "",
|
|
@@ -884,18 +856,18 @@ async function extractColumnDescriptions(db, config, tableName) {
|
|
|
884
856
|
case "postgres":
|
|
885
857
|
const schema = origin.schema || "public";
|
|
886
858
|
const postgresColumns = await db.raw(`
|
|
887
|
-
SELECT
|
|
859
|
+
SELECT
|
|
888
860
|
column_name as "Field",
|
|
889
861
|
column_default as "Default",
|
|
890
862
|
'' as "Extra",
|
|
891
863
|
is_nullable as "Null",
|
|
892
864
|
data_type as "Type",
|
|
893
865
|
'' as "Comment"
|
|
894
|
-
FROM information_schema.columns
|
|
866
|
+
FROM information_schema.columns
|
|
895
867
|
WHERE table_schema = ? AND table_name = ?
|
|
896
868
|
ORDER BY ordinal_position
|
|
897
869
|
`, [schema, tableName]);
|
|
898
|
-
return postgresColumns.rows.map((row) => ({
|
|
870
|
+
return postgresColumns.rows.filter((row) => !hasIgnoreDirective(row.Comment || "")).map((row) => ({
|
|
899
871
|
Field: row.Field,
|
|
900
872
|
Default: row.Default,
|
|
901
873
|
Extra: row.Extra || "",
|
|
@@ -905,7 +877,7 @@ async function extractColumnDescriptions(db, config, tableName) {
|
|
|
905
877
|
}));
|
|
906
878
|
case "sqlite":
|
|
907
879
|
const sqliteColumns = await db.raw(`PRAGMA table_info(${tableName})`);
|
|
908
|
-
return sqliteColumns.map((row) => ({
|
|
880
|
+
return sqliteColumns.filter((row) => !hasIgnoreDirective(row.Comment || "")).map((row) => ({
|
|
909
881
|
Field: row.name,
|
|
910
882
|
Default: row.dflt_value,
|
|
911
883
|
Extra: row.pk ? "PRIMARY KEY" : "",
|
|
@@ -925,7 +897,14 @@ function extractPrismaEntities(config) {
|
|
|
925
897
|
const schemaContent = readFileSync(config.origin.path, "utf-8");
|
|
926
898
|
const schema = createPrismaSchemaBuilder(schemaContent);
|
|
927
899
|
const prismaModels = schema.findAllByType("model", {});
|
|
928
|
-
const tables = prismaModels.filter((m) => m !== null).
|
|
900
|
+
const tables = prismaModels.filter((m) => m !== null).filter((model) => {
|
|
901
|
+
if (model.properties && Array.isArray(model.properties)) {
|
|
902
|
+
return !model.properties.some(
|
|
903
|
+
(prop) => prop.type === "attribute" && prop.name === "ignore" && prop.kind === "object"
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
return true;
|
|
907
|
+
}).map((model) => model.name);
|
|
929
908
|
const prismaViews = schema.findAllByType("view", {});
|
|
930
909
|
const views = prismaViews.filter((v) => v !== null).map((view) => view.name);
|
|
931
910
|
const enumDeclarations = {};
|
|
@@ -969,8 +948,16 @@ function extractPrismaColumnDescriptions(config, entityName, enumDeclarations) {
|
|
|
969
948
|
if (!entity || !("properties" in entity)) {
|
|
970
949
|
return [];
|
|
971
950
|
}
|
|
951
|
+
if (entity.type === "model" && entity.properties && Array.isArray(entity.properties)) {
|
|
952
|
+
const hasIgnore = entity.properties.some(
|
|
953
|
+
(prop) => prop.type === "attribute" && prop.name === "ignore" && prop.kind === "object"
|
|
954
|
+
);
|
|
955
|
+
if (hasIgnore) {
|
|
956
|
+
return [];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
972
959
|
const fields = entity.properties.filter(
|
|
973
|
-
(p) => p.type === "field" && p.array !== true && !p.attributes?.find((a) => a.name === "relation")
|
|
960
|
+
(p) => p.type === "field" && p.array !== true && !p.attributes?.find((a) => a.name === "relation") && !p.attributes?.find((a) => a.name === "ignore")
|
|
974
961
|
);
|
|
975
962
|
return fields.map((field) => {
|
|
976
963
|
let defaultGenerated = false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mutano",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.2.0",
|
|
5
5
|
"description": "Converts Prisma/MySQL/PostgreSQL/SQLite schemas to Zod/TS/Kysely interfaces",
|
|
6
6
|
"author": "Alisson Cavalcante Agiani <thelinuxlich@gmail.com>",
|
|
7
7
|
"license": "MIT",
|