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.
Files changed (3) hide show
  1. package/README.md +72 -0
  2. package/dist/main.js +48 -61
  3. 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
- const shouldBeNullable2 = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
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
- const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
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
- const shouldBeNullable = isNull || ["insertable", "updateable"].includes(op) && (hasDefaultValue || isGenerated) || op === "updateable" && !isNull && !hasDefaultValue;
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
- const shouldBeNullable = isNull;
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).map((model) => model.name);
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.1.5",
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",