@postxl/generator 0.3.3 → 0.5.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.
@@ -64,6 +64,7 @@ const types_1 = require("./lib/schema/types");
64
64
  const vfs_1 = require("./lib/vfs");
65
65
  const client_path_1 = require("./prisma/client-path");
66
66
  const parse_1 = require("./prisma/parse");
67
+ const emptydatabasemigration_generator_1 = require("./generators/indices/emptydatabasemigration.generator");
67
68
  const configDecoder = zod_1.z
68
69
  .object({
69
70
  project: zod_1.z.string(),
@@ -72,6 +73,7 @@ const configDecoder = zod_1.z
72
73
  pathToSeedLib: zod_1.z.string().optional(),
73
74
  trpcRoutesFolder: zod_1.z.string().optional(),
74
75
  reactFolderOutput: zod_1.z.string().optional(),
76
+ migrationsFolder: zod_1.z.string().optional(),
75
77
  randomSeed: zod_1.z
76
78
  .string()
77
79
  .optional()
@@ -90,6 +92,7 @@ const configDecoder = zod_1.z
90
92
  modelTypeDefinitionsPath: (0, types_1.toPath)(s.pathToTypes || 'types'),
91
93
  seedPath: (0, types_1.toPath)(s.pathToSeedLib || 'seed'),
92
94
  trpcRoutesFolderPath: (0, types_1.toPath)(s.trpcRoutesFolder || 'trpc'),
95
+ migrationsFolderPath: (0, types_1.toPath)(s.migrationsFolder || 'migrations'),
93
96
  },
94
97
  randomSeed: s.randomSeed,
95
98
  force: s.force,
@@ -178,6 +181,7 @@ const generate = ({ models, enums, config, prismaClientPath, logger, }) => __awa
178
181
  vfs.write(`/${meta.data.repositoriesConstFilePath}.ts`, (0, repositories_generator_1.generateRepositoriesArray)({ models, meta }));
179
182
  vfs.write(`/${meta.data.repositoriesIndexFilePath}.ts`, (0, repositories_generator_1.generateRepositoriesIndex)({ models, meta }));
180
183
  vfs.write(`/${meta.data.stubIndexFilePath}.ts`, (0, stubs_generator_1.generateStubsIndex)({ models, meta }));
184
+ vfs.write(`/${meta.migrationsPath}/emptyDatabase.template.sql`, (0, emptydatabasemigration_generator_1.generateEmptyDatabaseStoredProcedure)({ models, meta }));
181
185
  }
182
186
  if (!config.disableGenerators.seed) {
183
187
  vfs.write(`/${meta.seed.indexFilePath}.ts`, (0, seed_generator_1.generateSeedIndex)({ models, meta }));
@@ -0,0 +1,9 @@
1
+ import { SchemaMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Generates a data module class.
5
+ */
6
+ export declare function generateEmptyDatabaseStoredProcedure({ models, meta, }: {
7
+ models: Model[];
8
+ meta: SchemaMetaData;
9
+ }): string;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateEmptyDatabaseStoredProcedure = void 0;
4
+ /**
5
+ * Generates a data module class.
6
+ */
7
+ function generateEmptyDatabaseStoredProcedure({ models, meta, }) {
8
+ const tables = models
9
+ .map((model) => `\t${model.sourceSchemaName !== undefined ? `"${model.sourceSchemaName}".` : ''}"${model.sourceName}"`)
10
+ .join(',\n');
11
+ return `
12
+ CREATE OR REPLACE PROCEDURE "emptyDatabase"() AS $BODY$ BEGIN IF EXISTS (
13
+ SELECT
14
+ FROM "Configuration"."Config"
15
+ WHERE NOT "isTest"
16
+ ) THEN RETURN;
17
+ END IF;
18
+ TRUNCATE TABLE
19
+ ${tables};
20
+ END;
21
+ $BODY$ LANGUAGE plpgsql;
22
+ `;
23
+ }
24
+ exports.generateEmptyDatabaseStoredProcedure = generateEmptyDatabaseStoredProcedure;
@@ -11,9 +11,9 @@ function generateRepositoriesIndex({ models, meta }) {
11
11
  const exports = exports_1.ExportsGenerator.from(meta.data.repositoriesIndexFilePath);
12
12
  for (const model of models) {
13
13
  const meta = (0, meta_1.getModelMetadata)({ model });
14
- exports.exportEverythingFrom(meta.data.repoFilePath);
14
+ exports.exportEverythingFromPath(meta.data.repoFilePath);
15
15
  }
16
- exports.exportEverythingFrom(meta.data.repositoriesConstFilePath);
16
+ exports.exportEverythingFromPath(meta.data.repositoriesConstFilePath);
17
17
  return exports.generate();
18
18
  }
19
19
  exports.generateRepositoriesIndex = generateRepositoriesIndex;
@@ -10,7 +10,7 @@ function generateSeedIndex({ models, meta }) {
10
10
  const exports = exports_1.ExportsGenerator.from(meta.seed.indexFilePath);
11
11
  for (const model of models) {
12
12
  const meta = (0, meta_1.getModelMetadata)({ model });
13
- exports.exportEverythingFrom(meta.seed.filePath);
13
+ exports.exportEverythingFromPath(meta.seed.filePath);
14
14
  }
15
15
  return exports.generate();
16
16
  }
@@ -10,7 +10,7 @@ function generateStubsIndex({ models, meta }) {
10
10
  const exports = exports_1.ExportsGenerator.from(meta.data.stubIndexFilePath);
11
11
  for (const model of models) {
12
12
  const meta = (0, meta_1.getModelMetadata)({ model });
13
- exports.exportEverythingFrom(meta.data.stubFilePath);
13
+ exports.exportEverythingFromPath(meta.data.stubFilePath);
14
14
  }
15
15
  return exports.generate();
16
16
  }
@@ -10,11 +10,11 @@ function generateTypesIndex({ models, enums, meta, }) {
10
10
  const exports = exports_1.ExportsGenerator.from(meta.types.indexFilePath);
11
11
  for (const model of models) {
12
12
  const meta = (0, meta_1.getModelMetadata)({ model });
13
- exports.exportEverythingFrom(meta.types.filePath);
13
+ exports.exportEverythingFromPath(meta.types.filePath);
14
14
  }
15
15
  for (const enumerator of enums) {
16
16
  const meta = (0, meta_1.getEnumMetadata)({ enumerator });
17
- exports.exportEverythingFrom(meta.types.filePath);
17
+ exports.exportEverythingFromPath(meta.types.filePath);
18
18
  }
19
19
  return exports.generate();
20
20
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateReactComponentsForModel = void 0;
4
+ const exports_1 = require("../../../lib/exports");
4
5
  const vfs_1 = require("../../../lib/vfs");
5
6
  const context_generator_1 = require("./context.generator");
6
7
  const library_generator_1 = require("./library.generator");
@@ -10,23 +11,27 @@ const modals_generator_1 = require("./modals.generator");
10
11
  * Generates generic React components for a given model.
11
12
  */
12
13
  function generateReactComponentsForModel({ model, meta }) {
13
- const vfs = new vfs_1.VirtualFS({
14
- '/index.ts': `
15
- export * from './CreateModal'
16
- export * from './EditModal'
17
- export * from './DeleteModal'
18
-
19
- export * from './Form'
20
- export * from './context'
21
- export * from './Library'
22
- `,
23
- });
14
+ const vfs = new vfs_1.VirtualFS({});
15
+ const exports = exports_1.ExportsGenerator.fromRoot()
16
+ .exportEverythingFromFile('./CreateModal')
17
+ .exportEverythingFromFile('./EditModal')
18
+ .exportEverythingFromFile('./DeleteModal')
19
+ .exportEverythingFromFile('./Form')
20
+ .exportEverythingFromFile('./context');
24
21
  vfs.write(`/context.tsx`, (0, context_generator_1.generateModelContext)({ model, meta }));
25
22
  vfs.write(`/CreateModal.tsx`, (0, modals_generator_1.generateModelCreateModalComponent)({ model, meta }));
26
23
  vfs.write(`/EditModal.tsx`, (0, modals_generator_1.generateEditModalModelComponent)({ model, meta }));
27
24
  vfs.write(`/DeleteModal.tsx`, (0, modals_generator_1.generateDeleteModalModelComponent)({ model, meta }));
28
25
  vfs.write(`/Form.tsx`, (0, lookup_generator_1.generateModelLookupComponents)({ model, meta }));
29
- vfs.write(`/Library.tsx`, (0, library_generator_1.generateModelLibraryComponents)({ model, meta }));
26
+ // NOTE: library generator expects that the model has a `name` field.
27
+ // Make sure that you don't make a dependency on this
28
+ // component without checking that a model has a name field.
29
+ const hasNameField = model.nameField != null;
30
+ if (hasNameField) {
31
+ vfs.write(`/Library.tsx`, (0, library_generator_1.generateModelLibraryComponents)({ model, meta }));
32
+ exports.exportEverythingFromFile('./Library');
33
+ }
34
+ vfs.write(`/index.ts`, exports.generate());
30
35
  return vfs;
31
36
  }
32
37
  exports.generateReactComponentsForModel = generateReactComponentsForModel;
@@ -2,6 +2,9 @@ import { ModelMetaData } from '../../../lib/meta';
2
2
  import { Model } from '../../../lib/schema/schema';
3
3
  /**
4
4
  * Generates components that may be used to list all entries of a given data type.
5
+ *
6
+ * NOTE: Library expects that the model has a `name` field. Make sure that you don't make a dependency on
7
+ * this component without checking that a model has a name field.
5
8
  */
6
9
  export declare function generateModelLibraryComponents({ model, meta }: {
7
10
  model: Model;
@@ -6,6 +6,9 @@ const fields_1 = require("../../../lib/schema/fields");
6
6
  const imports_1 = require("../../../lib/imports");
7
7
  /**
8
8
  * Generates components that may be used to list all entries of a given data type.
9
+ *
10
+ * NOTE: Library expects that the model has a `name` field. Make sure that you don't make a dependency on
11
+ * this component without checking that a model has a name field.
9
12
  */
10
13
  function generateModelLibraryComponents({ model, meta }) {
11
14
  const { react: { context, components }, } = meta;
@@ -17,6 +17,11 @@ function generateModelLookupComponents({ model, meta }) {
17
17
  from: meta.types.importPath,
18
18
  });
19
19
  const typeName = model.typeName;
20
+ // NOTE: Depending on the model definition - whether it has a `name` field or not - we'll either
21
+ // auto-populate the `label` property or not.
22
+ const hasNameField = model.nameField != null;
23
+ const tsOmittedFields = hasNameField ? `'label' | 'options' | 'loading'` : `'options' | 'loading'`;
24
+ const reactLabelField = hasNameField ? `label={(l) => l.name}` : '';
20
25
  return `
21
26
  import React, { useMemo } from 'react'
22
27
 
@@ -34,63 +39,63 @@ ${imports.generate()}
34
39
 
35
40
  export const ${components.forms.selectInputName} = ({
36
41
  ...delegated
37
- }: UnionOmit<React.ComponentPropsWithoutRef<typeof SelectInput<${typeName}>>, 'label' | 'options' | 'loading'>) => {
42
+ }: UnionOmit<React.ComponentPropsWithoutRef<typeof SelectInput<${typeName}>>, ${tsOmittedFields}>) => {
38
43
  const { list, ready } = ${context.hookFnName}()
39
- return <SelectInput<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
44
+ return <SelectInput<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
40
45
  }
41
46
 
42
47
  export const ${components.forms.selectFieldName} = ({
43
48
  ...delegated
44
- }: Omit<React.ComponentPropsWithoutRef<typeof SelectField<${typeName}>>, 'label' | 'options' | 'loading'>) => {
49
+ }: Omit<React.ComponentPropsWithoutRef<typeof SelectField<${typeName}>>, ${tsOmittedFields}>) => {
45
50
  const { list, ready } = ${context.hookFnName}()
46
- return <SelectField<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
51
+ return <SelectField<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
47
52
  }
48
53
 
49
54
  // Menu Select
50
55
 
51
56
  export const ${components.forms.menuSelectInputName} = ({
52
57
  ...delegated
53
- }: UnionOmit<React.ComponentPropsWithoutRef<typeof MenuSelectInput<${typeName}>>, 'label' | 'options' | 'loading'>) => {
58
+ }: UnionOmit<React.ComponentPropsWithoutRef<typeof MenuSelectInput<${typeName}>>, ${tsOmittedFields}>) => {
54
59
  const { list, ready } = ${context.hookFnName}()
55
- return <MenuSelectInput<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
60
+ return <MenuSelectInput<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
56
61
  }
57
62
 
58
63
  export const ${components.forms.menuSelectFieldName} = ({
59
64
  ...delegated
60
- }: UnionOmit<React.ComponentPropsWithoutRef<typeof MenuSelectField<${typeName}>>, 'label' | 'options' | 'loading'>) => {
65
+ }: UnionOmit<React.ComponentPropsWithoutRef<typeof MenuSelectField<${typeName}>>, ${tsOmittedFields}>) => {
61
66
  const { list, ready } = ${context.hookFnName}()
62
- return <MenuSelectField<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
67
+ return <MenuSelectField<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
63
68
  }
64
69
 
65
70
  // Search
66
71
 
67
72
  export const ${components.forms.searchInputName} = ({
68
73
  ...delegated
69
- }: UnionOmit<React.ComponentPropsWithoutRef<typeof SearchInput<${typeName}>>, 'label' | 'options' | 'loading'>) => {
74
+ }: UnionOmit<React.ComponentPropsWithoutRef<typeof SearchInput<${typeName}>>, ${tsOmittedFields}>) => {
70
75
  const { list, ready } = ${context.hookFnName}()
71
- return <SearchInput<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
76
+ return <SearchInput<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
72
77
  }
73
78
  export const ${components.forms.searchFieldName} = ({
74
79
  ...delegated
75
- }: Omit<React.ComponentPropsWithoutRef<typeof SearchField<${typeName}>>, 'label' | 'options' | 'loading'>) => {
80
+ }: Omit<React.ComponentPropsWithoutRef<typeof SearchField<${typeName}>>, ${tsOmittedFields}>) => {
76
81
  const { list, ready } = ${context.hookFnName}()
77
- return <SearchField<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
82
+ return <SearchField<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
78
83
  }
79
84
 
80
85
  // Table
81
86
 
82
87
  export const ${components.forms.tableSelectInputName} = ({
83
88
  ...delegated
84
- }: UnionOmit<React.ComponentPropsWithoutRef<typeof TableSelectInput<${typeName}>>, 'label' | 'options' | 'loading'>) => {
89
+ }: UnionOmit<React.ComponentPropsWithoutRef<typeof TableSelectInput<${typeName}>>, ${tsOmittedFields}>) => {
85
90
  const { list, ready } = ${context.hookFnName}()
86
- return <TableSelectInput<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
91
+ return <TableSelectInput<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
87
92
  }
88
93
 
89
94
  export const ${components.forms.tableSelectFieldName} = ({
90
95
  ...delegated
91
- }: UnionOmit<React.ComponentPropsWithoutRef<typeof TableSelectField<${typeName}>>, 'label' | 'options' | 'loading'>) => {
96
+ }: UnionOmit<React.ComponentPropsWithoutRef<typeof TableSelectField<${typeName}>>, ${tsOmittedFields}>) => {
92
97
  const { list, ready } = ${context.hookFnName}()
93
- return <TableSelectField<${typeName}> options={list} label={(l) => l.name} loading={!ready} {...delegated} />
98
+ return <TableSelectField<${typeName}> options={list} ${reactLabelField} loading={!ready} {...delegated} />
94
99
  }
95
100
  `;
96
101
  }
@@ -21,6 +21,13 @@ export type ModelAttributes = {
21
21
  * Schema tag: ´@@Description("Description of the model")`
22
22
  */
23
23
  description?: string;
24
+ /**
25
+ * Schema tag: ´@@Schema("Configuration")`
26
+ * The database schema that the model belongs to.
27
+ * @internal
28
+ * Note: Prisma has it's own schema attribute - but does not expose it in the DMMF.
29
+ */
30
+ databaseSchema?: string;
24
31
  };
25
32
  export type FieldAttributes = {
26
33
  /**
@@ -8,17 +8,32 @@ export declare class ExportsGenerator {
8
8
  * Path of the file we're generating exports for.
9
9
  */
10
10
  private _path;
11
- constructor({ path }: {
11
+ /**
12
+ * Tells whether this generator acts as a root.
13
+ */
14
+ private isRoot;
15
+ constructor({ path, isRoot }: {
12
16
  path: string;
17
+ isRoot?: boolean;
13
18
  });
14
19
  /**
15
20
  * Creates a new instance of the exports generator.
16
21
  */
17
22
  static from(path: Types.Path): ExportsGenerator;
23
+ /**
24
+ * Creates a new instance of the exports generator from this folder (i.e. every path is relative to this folder).
25
+ */
26
+ static fromRoot(): ExportsGenerator;
27
+ /**
28
+ * Adds a given file to the collection of files we're exporting everything from.
29
+ */
30
+ exportEverythingFromPath(from: Types.Path): ExportsGenerator;
18
31
  /**
19
32
  * Adds a given file to the collection of files we're exporting everything from.
33
+ *
34
+ * NOTE: This should only be used when the generator is a root generator.
20
35
  */
21
- exportEverythingFrom(from: Types.Path): ExportsGenerator;
36
+ exportEverythingFromFile(file: string): ExportsGenerator;
22
37
  /**
23
38
  * Returns the TypeScript export statements.
24
39
  */
@@ -6,9 +6,10 @@ const file_1 = require("./utils/file");
6
6
  * A utility component that lets you generate TypeScript export statements that export all items from a given file.
7
7
  */
8
8
  class ExportsGenerator {
9
- constructor({ path }) {
9
+ constructor({ path, isRoot }) {
10
10
  this._path = path;
11
11
  this._exports = new Set();
12
+ this.isRoot = isRoot !== null && isRoot !== void 0 ? isRoot : false;
12
13
  }
13
14
  /**
14
15
  * Creates a new instance of the exports generator.
@@ -16,14 +17,35 @@ class ExportsGenerator {
16
17
  static from(path) {
17
18
  return new ExportsGenerator({ path });
18
19
  }
20
+ /**
21
+ * Creates a new instance of the exports generator from this folder (i.e. every path is relative to this folder).
22
+ */
23
+ static fromRoot() {
24
+ return new ExportsGenerator({ path: './', isRoot: true });
25
+ }
19
26
  /**
20
27
  * Adds a given file to the collection of files we're exporting everything from.
21
28
  */
22
- exportEverythingFrom(from) {
29
+ exportEverythingFromPath(from) {
30
+ if (this.isRoot) {
31
+ throw new Error(`Cannot use "exportEverythingFromPath" on a root generator.`);
32
+ }
23
33
  const resolvedPath = (0, file_1.getRelativePath)({ from: this._path, to: from });
24
34
  this._exports.add(resolvedPath);
25
35
  return this;
26
36
  }
37
+ /**
38
+ * Adds a given file to the collection of files we're exporting everything from.
39
+ *
40
+ * NOTE: This should only be used when the generator is a root generator.
41
+ */
42
+ exportEverythingFromFile(file) {
43
+ if (!this.isRoot) {
44
+ throw new Error(`Cannot use "exportEverythingFromFile" on a non-root generator.`);
45
+ }
46
+ this._exports.add(file);
47
+ return this;
48
+ }
27
49
  /**
28
50
  * Returns the TypeScript export statements.
29
51
  */
@@ -68,6 +68,10 @@ export type SchemaMetaData = {
68
68
  */
69
69
  importPath: Types.Path;
70
70
  };
71
+ /**
72
+ * Path to the directory containing migrations.
73
+ */
74
+ migrationsPath: Types.Path;
71
75
  /**
72
76
  * The schema configuration for reference.
73
77
  */
@@ -53,6 +53,7 @@ function getSchemaMetadata({ config }) {
53
53
  indexFilePath: Types.toPath(`${config.paths.modelTypeDefinitionsPath}index`),
54
54
  importPath: Types.toPath(`@${config.project}/types`),
55
55
  },
56
+ migrationsPath: Types.toPath(`${config.paths.migrationsFolderPath}`),
56
57
  config,
57
58
  };
58
59
  }
@@ -69,6 +69,10 @@ export type SchemaConfig = {
69
69
  * Path to the directory containing trpc routes.
70
70
  */
71
71
  trpcRoutesFolderPath: Types.Path;
72
+ /**
73
+ * Path to the directory containing Prisma migrations.
74
+ */
75
+ migrationsFolderPath: Types.Path;
72
76
  };
73
77
  /**
74
78
  * Seed to use for random generation.
@@ -99,10 +103,17 @@ export type ModelCore = {
99
103
  /**
100
104
  * Name of the model in the source, e.g. in the database (e.g. aggregation)
101
105
  *
102
- * Note: Should be only relevant to repository generator
106
+ * Note: Should be only relevant to repository and DB migration generators
103
107
  * @internal
104
108
  */
105
109
  sourceName: string;
110
+ /**
111
+ * Name of the schema in the source, (e.g. Configuration)
112
+ *
113
+ * Note: Should be only relevant to DB migration generator
114
+ * @internal
115
+ */
116
+ sourceSchemaName?: string;
106
117
  /**
107
118
  * Type of the ID field that is specific to this model, e.g. `AggregationId`.
108
119
  */
@@ -382,6 +382,9 @@ class VirtualFS {
382
382
  if (ext === '.ts' || ext === '.tsx') {
383
383
  parser = 'typescript';
384
384
  }
385
+ if (ext === '.sql') {
386
+ return content;
387
+ }
385
388
  try {
386
389
  return prettier.format(content, Object.assign(Object.assign({}, options), { parser }));
387
390
  }
@@ -54,6 +54,7 @@ function getModelAttributes(model) {
54
54
  skipCreate: Object.hasOwn(attributes, 'skipCreate') || Object.hasOwn(attributes, 'customCreate'),
55
55
  skipDelete: Object.hasOwn(attributes, 'skipDelete') || Object.hasOwn(attributes, 'customDelete'),
56
56
  description: attributes.description && (0, remeda_1.isString)(attributes.description) ? attributes.description : undefined,
57
+ databaseSchema: attributes.schema && (0, remeda_1.isString)(attributes.schema) ? attributes.schema : undefined,
57
58
  };
58
59
  }
59
60
  exports.getModelAttributes = getModelAttributes;
@@ -51,6 +51,7 @@ function parseModelCore({ dmmfModel, config, }) {
51
51
  description: attributes.description,
52
52
  typeName: Types.toTypeName((0, string_1.toPascalCase)(dmmfModel.name)),
53
53
  sourceName: dmmfModel.name,
54
+ sourceSchemaName: attributes.databaseSchema,
54
55
  brandedIdType: Types.toTypeName(`${(0, string_1.toPascalCase)(dmmfModel.name)}Id`),
55
56
  attributes,
56
57
  schemaConfig: config,