mutano 3.1.6 → 3.2.1

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 +63 -24
  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;
@@ -770,23 +776,23 @@ async function extractTables(db, config) {
770
776
  switch (origin.type) {
771
777
  case "mysql":
772
778
  const mysqlTables = await db.raw(`
773
- SELECT table_name
774
- FROM information_schema.tables
779
+ SELECT table_name, table_comment
780
+ FROM information_schema.tables
775
781
  WHERE table_schema = ? AND table_type = 'BASE TABLE'
776
782
  `, [origin.database]);
777
- return mysqlTables[0].map((row) => row.table_name);
783
+ return mysqlTables[0].filter((row) => !hasTableIgnoreDirective(row.table_comment || "")).map((row) => row.table_name);
778
784
  case "postgres":
779
785
  const schema = origin.schema || "public";
780
786
  const postgresTables = await db.raw(`
781
- SELECT table_name
782
- FROM information_schema.tables
787
+ SELECT table_name
788
+ FROM information_schema.tables
783
789
  WHERE table_schema = ? AND table_type = 'BASE TABLE'
784
790
  `, [schema]);
785
791
  return postgresTables.rows.map((row) => row.table_name);
786
792
  case "sqlite":
787
793
  const sqliteTables = await db.raw(`
788
- SELECT name
789
- FROM sqlite_master
794
+ SELECT name
795
+ FROM sqlite_master
790
796
  WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
791
797
  `);
792
798
  return sqliteTables.map((row) => row.name);
@@ -799,23 +805,23 @@ async function extractViews(db, config) {
799
805
  switch (origin.type) {
800
806
  case "mysql":
801
807
  const mysqlViews = await db.raw(`
802
- SELECT table_name
803
- FROM information_schema.tables
808
+ SELECT table_name, table_comment
809
+ FROM information_schema.tables
804
810
  WHERE table_schema = ? AND table_type = 'VIEW'
805
811
  `, [origin.database]);
806
- return mysqlViews[0].map((row) => row.table_name);
812
+ return mysqlViews[0].filter((row) => !hasTableIgnoreDirective(row.table_comment || "")).map((row) => row.table_name);
807
813
  case "postgres":
808
814
  const schema = origin.schema || "public";
809
815
  const postgresViews = await db.raw(`
810
- SELECT table_name
811
- FROM information_schema.tables
816
+ SELECT table_name
817
+ FROM information_schema.tables
812
818
  WHERE table_schema = ? AND table_type = 'VIEW'
813
819
  `, [schema]);
814
820
  return postgresViews.rows.map((row) => row.table_name);
815
821
  case "sqlite":
816
822
  const sqliteViews = await db.raw(`
817
- SELECT name
818
- FROM sqlite_master
823
+ SELECT name
824
+ FROM sqlite_master
819
825
  WHERE type = 'view'
820
826
  `);
821
827
  return sqliteViews.map((row) => row.name);
@@ -828,18 +834,18 @@ async function extractColumnDescriptions(db, config, tableName) {
828
834
  switch (origin.type) {
829
835
  case "mysql":
830
836
  const mysqlColumns = await db.raw(`
831
- SELECT
837
+ SELECT
832
838
  column_name as \`Field\`,
833
839
  column_default as \`Default\`,
834
840
  extra as \`Extra\`,
835
841
  is_nullable as \`Null\`,
836
842
  column_type as \`Type\`,
837
843
  column_comment as \`Comment\`
838
- FROM information_schema.columns
844
+ FROM information_schema.columns
839
845
  WHERE table_schema = ? AND table_name = ?
840
846
  ORDER BY ordinal_position
841
847
  `, [origin.database, tableName]);
842
- return mysqlColumns[0].map((row) => ({
848
+ return mysqlColumns[0].filter((row) => !hasIgnoreDirective(row.Comment || "")).map((row) => ({
843
849
  Field: row.Field,
844
850
  Default: row.Default,
845
851
  Extra: row.Extra || "",
@@ -850,18 +856,18 @@ async function extractColumnDescriptions(db, config, tableName) {
850
856
  case "postgres":
851
857
  const schema = origin.schema || "public";
852
858
  const postgresColumns = await db.raw(`
853
- SELECT
859
+ SELECT
854
860
  column_name as "Field",
855
861
  column_default as "Default",
856
862
  '' as "Extra",
857
863
  is_nullable as "Null",
858
864
  data_type as "Type",
859
865
  '' as "Comment"
860
- FROM information_schema.columns
866
+ FROM information_schema.columns
861
867
  WHERE table_schema = ? AND table_name = ?
862
868
  ORDER BY ordinal_position
863
869
  `, [schema, tableName]);
864
- return postgresColumns.rows.map((row) => ({
870
+ return postgresColumns.rows.filter((row) => !hasIgnoreDirective(row.Comment || "")).map((row) => ({
865
871
  Field: row.Field,
866
872
  Default: row.Default,
867
873
  Extra: row.Extra || "",
@@ -871,7 +877,7 @@ async function extractColumnDescriptions(db, config, tableName) {
871
877
  }));
872
878
  case "sqlite":
873
879
  const sqliteColumns = await db.raw(`PRAGMA table_info(${tableName})`);
874
- return sqliteColumns.map((row) => ({
880
+ return sqliteColumns.filter((row) => !hasIgnoreDirective(row.Comment || "")).map((row) => ({
875
881
  Field: row.name,
876
882
  Default: row.dflt_value,
877
883
  Extra: row.pk ? "PRIMARY KEY" : "",
@@ -891,7 +897,14 @@ function extractPrismaEntities(config) {
891
897
  const schemaContent = readFileSync(config.origin.path, "utf-8");
892
898
  const schema = createPrismaSchemaBuilder(schemaContent);
893
899
  const prismaModels = schema.findAllByType("model", {});
894
- 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);
895
908
  const prismaViews = schema.findAllByType("view", {});
896
909
  const views = prismaViews.filter((v) => v !== null).map((view) => view.name);
897
910
  const enumDeclarations = {};
@@ -900,7 +913,24 @@ function extractPrismaEntities(config) {
900
913
  if (prismaEnum && "name" in prismaEnum && "enumerators" in prismaEnum) {
901
914
  const enumName = prismaEnum.name;
902
915
  const enumerators = prismaEnum.enumerators;
903
- enumDeclarations[enumName] = enumerators.map((e) => {
916
+ const hasEnumIgnore = enumerators.some(
917
+ (item) => item.type === "attribute" && item.name === "ignore" && item.kind === "object"
918
+ );
919
+ if (hasEnumIgnore) {
920
+ continue;
921
+ }
922
+ const filteredEnumValues = enumerators.filter((e) => {
923
+ if (e.type === "attribute") {
924
+ return false;
925
+ }
926
+ if ("attributes" in e && e.attributes) {
927
+ const hasIgnore = e.attributes.some((attr) => attr.name === "ignore");
928
+ if (hasIgnore) {
929
+ return false;
930
+ }
931
+ }
932
+ return true;
933
+ }).map((e) => {
904
934
  if ("attributes" in e && e.attributes) {
905
935
  const mapAttr = e.attributes.find((attr) => attr.name === "map");
906
936
  if (mapAttr && mapAttr.args && mapAttr.args.length > 0) {
@@ -918,6 +948,7 @@ function extractPrismaEntities(config) {
918
948
  }
919
949
  return e.name;
920
950
  });
951
+ enumDeclarations[enumName] = filteredEnumValues;
921
952
  }
922
953
  }
923
954
  return { tables, views, enumDeclarations };
@@ -935,8 +966,16 @@ function extractPrismaColumnDescriptions(config, entityName, enumDeclarations) {
935
966
  if (!entity || !("properties" in entity)) {
936
967
  return [];
937
968
  }
969
+ if (entity.type === "model" && entity.properties && Array.isArray(entity.properties)) {
970
+ const hasIgnore = entity.properties.some(
971
+ (prop) => prop.type === "attribute" && prop.name === "ignore" && prop.kind === "object"
972
+ );
973
+ if (hasIgnore) {
974
+ return [];
975
+ }
976
+ }
938
977
  const fields = entity.properties.filter(
939
- (p) => p.type === "field" && p.array !== true && !p.attributes?.find((a) => a.name === "relation")
978
+ (p) => p.type === "field" && p.array !== true && !p.attributes?.find((a) => a.name === "relation") && !p.attributes?.find((a) => a.name === "ignore")
940
979
  );
941
980
  return fields.map((field) => {
942
981
  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.6",
4
+ "version": "3.2.1",
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",