@postxl/generator 0.13.0 → 0.14.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/dist/generator.js CHANGED
@@ -61,6 +61,8 @@ const meta_1 = require("./lib/meta");
61
61
  const types_1 = require("./lib/schema/types");
62
62
  const client_path_1 = require("./prisma/client-path");
63
63
  const parse_1 = require("./prisma/parse");
64
+ const seed_template_decoder_generator_1 = require("./generators/indices/seed-template-decoder.generator");
65
+ const seed_template_generator_1 = require("./generators/indices/seed-template.generator");
64
66
  const CONFIG_SCHEMA = zod_1.z
65
67
  .object({
66
68
  project: zod_1.z.string(),
@@ -188,6 +190,8 @@ function generate({ models, enums, config, prismaClientPath, logger, }) {
188
190
  }
189
191
  if (!config.disableGenerators.seed) {
190
192
  generated.write(`/${meta.seed.indexFilePath}.ts`, (0, seed_generator_1.generateSeedIndex)({ models, meta }));
193
+ generated.write(`/${meta.seed.templateExcelFilePath}`, yield (0, seed_template_generator_1.generateSeedExcelTemplate)({ models }));
194
+ generated.write(`/${meta.seed.templateDecoderFilePath}.ts`, (0, seed_template_decoder_generator_1.generateSeedTemplateDecoder)({ models, meta }));
191
195
  }
192
196
  if (!config.disableGenerators.trpc) {
193
197
  generated.write(`/${meta.trpc.routesFilePath}.ts`, (0, route_generator_1.generateRoutesIndex)({ models, meta }));
@@ -51,7 +51,7 @@ function generateDataMockModule({ models, meta }) {
51
51
  imports.addImport({ items: [meta.data.mockRepositoryClassName], from: meta.data.mockRepoFilePath });
52
52
  }
53
53
  const providers = mm
54
- .map(({ meta, model }) => `{
54
+ .map(({ meta }) => `{
55
55
  provide: ${meta.data.repositoryClassName},
56
56
  useFactory: async () => {
57
57
  const repository = new ${meta.data.mockRepositoryClassName}()
@@ -0,0 +1,9 @@
1
+ import { SchemaMetaData } from '../../lib/meta';
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Creates a decoder for the Seed Excel template
5
+ */
6
+ export declare function generateSeedTemplateDecoder({ models, meta }: {
7
+ models: Model[];
8
+ meta: SchemaMetaData;
9
+ }): string;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSeedTemplateDecoder = void 0;
4
+ const imports_1 = require("../../lib/imports");
5
+ const meta_1 = require("../../lib/meta");
6
+ const types_1 = require("../../lib/types");
7
+ /**
8
+ * Creates a decoder for the Seed Excel template
9
+ */
10
+ function generateSeedTemplateDecoder({ models, meta }) {
11
+ const imports = imports_1.ImportsGenerator.from(meta.seed.templateDecoderFilePath);
12
+ const decoders = [];
13
+ const tableDecoders = [];
14
+ const renameTransforms = [];
15
+ for (const model of [...models].sort((a, b) => a.name.localeCompare(b.name))) {
16
+ const modelMeta = (0, meta_1.getModelMetadata)({ model });
17
+ decoders.push(generateTableDecoder({ model, meta: modelMeta, imports }));
18
+ tableDecoders.push(`${modelMeta.seed.excel.tableName}: z.array(${modelMeta.seed.decoder.schemaName})`);
19
+ renameTransforms.push(`${modelMeta.seed.decoder.dataName}: item['${modelMeta.seed.excel.tableName}']`);
20
+ }
21
+ return `
22
+ import * as z from 'zod'
23
+ import { excelStringDecoder, excelStringNullableDecoder, excelBooleanDecoder, excelDateNullableDecoder, excelDateDecoder } from '@pxl/common'
24
+
25
+ ${imports.generate()}
26
+
27
+ ${decoders.join('\n')}
28
+
29
+ export const seedTemplateDecoder = z
30
+ .object({${tableDecoders.join(',\n')}})
31
+ .transform((item) => ({${renameTransforms.join(',\n')}}))
32
+ `;
33
+ }
34
+ exports.generateSeedTemplateDecoder = generateSeedTemplateDecoder;
35
+ function generateTableDecoder({ model, meta, imports, }) {
36
+ const fieldDecoders = [];
37
+ const renameTransforms = [];
38
+ for (const field of model.fields) {
39
+ const fieldMeta = (0, meta_1.getFieldMetadata)({ field });
40
+ renameTransforms.push(`${field.name}: item['${fieldMeta.excelColumnName}']`);
41
+ switch (field.kind) {
42
+ case 'id': {
43
+ imports.addImport({
44
+ items: [meta.types.toBrandedIdTypeFnName],
45
+ from: meta.types.filePath,
46
+ });
47
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: ${toExcelDecoder({
48
+ typeName: field.unbrandedTypeName,
49
+ nullable: false,
50
+ })}.transform((id: ${field.unbrandedTypeName}) => ${meta.types.toBrandedIdTypeFnName}(id))`);
51
+ break;
52
+ }
53
+ case 'scalar': {
54
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: ${toExcelDecoder({
55
+ typeName: field.typeName,
56
+ nullable: !field.isRequired,
57
+ })}`);
58
+ break;
59
+ }
60
+ case 'relation': {
61
+ const refModel = field.relationToModel;
62
+ const refMeta = (0, meta_1.getModelMetadata)({ model: refModel });
63
+ imports.addImport({
64
+ items: [refMeta.types.toBrandedIdTypeFnName],
65
+ from: refMeta.types.filePath,
66
+ });
67
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: ${toExcelDecoder({
68
+ typeName: field.unbrandedTypeName,
69
+ nullable: !field.isRequired,
70
+ })}.transform((id: ${field.unbrandedTypeName}${field.isRequired ? '' : '| null'}) => ${field.isRequired ? '' : ' id === null ? null : '}${refMeta.types.toBrandedIdTypeFnName}(id))`);
71
+ break;
72
+ }
73
+ case 'enum': {
74
+ const refEnumMeta = (0, meta_1.getEnumMetadata)({ enumerator: field.enumerator });
75
+ imports.addImport({
76
+ items: [field.enumerator.typeName],
77
+ from: refEnumMeta.types.filePath,
78
+ });
79
+ fieldDecoders.push(`${fieldMeta.excelColumnName}: z.enum([${field.enumerator.values.map((v) => `'${v}'`).join(', ')}])${field.isRequired ? '' : '.nullable()'}`);
80
+ break;
81
+ }
82
+ default: {
83
+ throw new types_1.ExhaustiveSwitchCheck(field);
84
+ }
85
+ }
86
+ }
87
+ return `
88
+ /**
89
+ * The schema for the ${model.name} table
90
+ */
91
+ const ${meta.seed.decoder.schemaName} = z
92
+ .object({${fieldDecoders.join(',\n')}})
93
+ .transform((item) => ({${renameTransforms.join(',\n')}}))
94
+
95
+ /**
96
+ * The type of the ${model.name} table
97
+ */
98
+ export type ${meta.seed.decoder.decoderTypeName} = z.infer<typeof ${meta.seed.decoder.schemaName}>
99
+
100
+ export const ${meta.seed.decoder.decoderName} = z.array(${meta.seed.decoder.schemaName})
101
+ `;
102
+ }
103
+ function toExcelDecoder({ typeName, nullable }) {
104
+ switch (typeName) {
105
+ case 'string':
106
+ return nullable ? 'excelStringNullableDecoder' : 'excelStringDecoder';
107
+ case 'boolean':
108
+ return 'excelBooleanDecoder';
109
+ case 'number':
110
+ return `z.number()${nullable ? '.nullable()' : ''}`;
111
+ case 'Date':
112
+ return nullable ? 'excelDateNullableDecoder' : 'excelDateDecoder';
113
+ default:
114
+ throw new Error('Unknown type');
115
+ }
116
+ }
@@ -0,0 +1,9 @@
1
+ /// <reference types="node" />
2
+ import { Model } from '../../lib/schema/schema';
3
+ /**
4
+ * Creates an Excel template file that can be used to manage Seed data.
5
+ * The template file contains a sheet for each model, with the column for each field.
6
+ */
7
+ export declare function generateSeedExcelTemplate({ models }: {
8
+ models: Model[];
9
+ }): Promise<Buffer>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.generateSeedExcelTemplate = void 0;
36
+ const Excel = __importStar(require("exceljs"));
37
+ const meta_1 = require("../../lib/meta");
38
+ /**
39
+ * Creates an Excel template file that can be used to manage Seed data.
40
+ * The template file contains a sheet for each model, with the column for each field.
41
+ */
42
+ function generateSeedExcelTemplate({ models }) {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ const wb = initializeWorkbook();
45
+ for (const model of [...models].sort((a, b) => a.name.localeCompare(b.name))) {
46
+ const meta = (0, meta_1.getModelMetadata)({ model });
47
+ addModel({ model, meta, wb });
48
+ }
49
+ const buffer = Buffer.from(yield wb.xlsx.writeBuffer());
50
+ return buffer;
51
+ });
52
+ }
53
+ exports.generateSeedExcelTemplate = generateSeedExcelTemplate;
54
+ function initializeWorkbook() {
55
+ const wb = new Excel.Workbook();
56
+ wb.creator = 'XLPort';
57
+ wb.lastModifiedBy = 'XLPort';
58
+ wb.created = new Date();
59
+ wb.modified = new Date();
60
+ return wb;
61
+ }
62
+ function addModel({ model, meta, wb }) {
63
+ const ws = wb.addWorksheet(model.name);
64
+ const table = ws.addTable({
65
+ name: meta.seed.excel.tableName,
66
+ ref: 'A1',
67
+ headerRow: true,
68
+ totalsRow: false,
69
+ style: {
70
+ theme: 'TableStyleMedium2',
71
+ showRowStripes: true,
72
+ },
73
+ columns: model.fields.map((field) => ({
74
+ name: (0, meta_1.getFieldMetadata)({ field }).excelColumnName,
75
+ })),
76
+ rows: [],
77
+ });
78
+ table.addRow(model.fields.map(() => null));
79
+ table.commit();
80
+ }
@@ -56,13 +56,14 @@ function generateModelLibraryComponents({ model, meta }) {
56
56
  /**
57
57
  * Show a single ${model.name} entry.
58
58
  */
59
- export const ${components.cardComponentName} = ({ item }: { item: ${model.typeName} }) => {
59
+ export const ${components.cardComponentName} = React.forwardRef<HTMLLIElement, { item: ${model.typeName} }>(({ item }, forwardedRef) => {
60
60
  const [isEditModalOpen, setIsEditModalOpen] = useState(false)
61
61
  const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false)
62
62
 
63
63
  return (
64
64
  <>
65
65
  <Card
66
+ ref={forwardedRef}
66
67
  title={item.name}
67
68
  actions={[
68
69
  {
@@ -96,7 +97,9 @@ function generateModelLibraryComponents({ model, meta }) {
96
97
  />
97
98
  </>
98
99
  )
99
- }
100
+ })
101
+
102
+ ${components.cardComponentName}.displayName = '${components.cardComponentName}'
100
103
  `;
101
104
  }
102
105
  exports.generateModelLibraryComponents = generateModelLibraryComponents;
@@ -391,7 +391,7 @@ function getFormFieldComponents({ model }) {
391
391
  case 'scalar': {
392
392
  // Each scalar field has a different generic component based on the provided type.
393
393
  scalar: switch (field.typeName) {
394
- case 'string':
394
+ case 'string': {
395
395
  form.append(`
396
396
  <div>
397
397
  <Label>${label}</Label>
@@ -399,7 +399,8 @@ function getFormFieldComponents({ model }) {
399
399
  </div>
400
400
  `);
401
401
  break scalar;
402
- case 'number':
402
+ }
403
+ case 'number': {
403
404
  let decimals = 0;
404
405
  if (((_a = field.validation) === null || _a === void 0 ? void 0 : _a.type) === 'float') {
405
406
  decimals = 2;
@@ -411,7 +412,8 @@ function getFormFieldComponents({ model }) {
411
412
  </div>
412
413
  `);
413
414
  break scalar;
414
- case 'boolean':
415
+ }
416
+ case 'boolean': {
415
417
  form.append(`
416
418
  <div>
417
419
  <Label>Is ${label}</Label>
@@ -419,12 +421,15 @@ function getFormFieldComponents({ model }) {
419
421
  </div>
420
422
  `);
421
423
  break scalar;
422
- case 'Date':
424
+ }
425
+ case 'Date': {
423
426
  form.append(`{/* Skipped date field ${field.name}. */}`);
424
427
  break scalar;
425
- default:
428
+ }
429
+ default: {
426
430
  console.warn(`Unknown scalar type ${field.typeName} for field ${model.name}.${field.name}.`);
427
431
  break scalar;
432
+ }
428
433
  }
429
434
  break;
430
435
  }
@@ -41,14 +41,16 @@ function generateRepository({ model, meta }) {
41
41
  };
42
42
  };
43
43
  const indexes = model.attributes.index ? [getIndexDefinition(model.attributes.index)] : [];
44
- const defaultValueInitFn = `
44
+ const defaultValueInitFn = model.defaultField
45
+ ? `
45
46
  if (item.${(_a = model.defaultField) === null || _a === void 0 ? void 0 : _a.name}) {
46
47
  if (this.defaultValue) {
47
48
  console.warn(\`More than one default ${meta.userFriendlyName} found! \${this.defaultValue.id} and \${item.id}\`)
48
49
  }
49
50
  this.defaultValue = item
50
51
  }
51
- `;
52
+ `
53
+ : '';
52
54
  const defaultValueInitCheckFn = `
53
55
  if (!this.db.isCLI && !this.defaultValue) {
54
56
  throw new Error('No default ${meta.userFriendlyName} found!')
@@ -405,17 +407,22 @@ export class ${meta.data.repositoryClassName} implements Repository<
405
407
  }
406
408
  `)
407
409
  .join('\n')}
408
- ${maxLengthStringFields.map((f) => {
409
- return `
410
- /**
411
- * Utility function that ensures that the ${f.name} field has a max length of ${f.attributes.maxLength}
412
- */
413
- private ${getEnsureMaxLengthFnName(f)}(item: { ${f.name}?: string }) {
414
- if (!item.${f.name}) return
415
- if (item.${f.name}.length <= ${f.attributes.maxLength}) return
416
- item.${f.name} = item.${f.name}.substring(0, ${f.attributes.maxLength - 4}) + \`...\`
417
- }`;
418
- })}
410
+
411
+ ${maxLengthStringFields
412
+ .map((f) => `
413
+ /**
414
+ * Utility function that ensures that the ${f.name} field has a max length of ${f.attributes.maxLength}
415
+ */
416
+ private ${getEnsureMaxLengthFnName(f)}(item: { ${f.name}?: string }) {
417
+ if (!item.${f.name}) {
418
+ return
419
+ }
420
+ if (item.${f.name}.length <= ${f.attributes.maxLength}) {
421
+ return
422
+ }
423
+ item.${f.name} = item.${f.name}.substring(0, ${f.attributes.maxLength - 4}) + \`...\`
424
+ }`)
425
+ .join('\n')}
419
426
 
420
427
  ${uniqueStringFields
421
428
  .map((f) => {
@@ -95,39 +95,57 @@ function generateFieldData({ field, model, index, exampleMode, }) {
95
95
  throw new types_1.ExhaustiveSwitchCheck(field);
96
96
  }
97
97
  }
98
- function generateFieldDataId({ field, model, index }) {
98
+ function generateFieldDataId({ field, index }) {
99
99
  const idModelMeta = (0, meta_1.getModelMetadata)({ model: field.model });
100
100
  return `${idModelMeta.types.toBrandedIdTypeFnName}(${field.unbrandedTypeName === 'string' ? `'${index}'` : index})`;
101
101
  }
102
- function quoteSingleQuote(str) {
103
- return str !== null ? str.replace(/'/g, "\\'") : null;
104
- }
105
102
  function generateFieldDataScalar({ field, model, index, exampleMode, }) {
106
103
  const { hasExample, example } = getFieldExample({ field, model, index, exampleMode });
107
104
  switch (field.typeName) {
108
- case 'string':
109
- const result = hasExample ? example : generateFieldDataString({ field, model, index });
110
- if (result === null)
105
+ case 'string': {
106
+ if (hasExample) {
107
+ return `${example}`;
108
+ }
109
+ const result = generateFieldDataString({ field, model, index });
110
+ if (result === null) {
111
111
  return 'null';
112
- if (result === undefined)
112
+ }
113
+ if (result === undefined) {
113
114
  return 'undefined';
114
- return `'${quoteSingleQuote(result)}'`;
115
- case 'number':
116
- return hasExample ? example : generateFieldDataNumber({ field, model, index });
117
- case 'boolean':
118
- return hasExample ? example : generateFieldDataBoolean({ field, model, index });
119
- case 'Date':
120
- return hasExample ? example : generateFieldDataDate({ field, model, index });
121
- default:
115
+ }
116
+ return `'${result.replace(/'/g, "\\'")}'`;
117
+ }
118
+ case 'number': {
119
+ if (hasExample) {
120
+ return `${example}`;
121
+ }
122
+ return generateFieldDataNumber();
123
+ }
124
+ case 'boolean': {
125
+ if (hasExample) {
126
+ return `${example}`;
127
+ }
128
+ return generateFieldDataBoolean();
129
+ }
130
+ case 'Date': {
131
+ if (hasExample) {
132
+ // TODO: Maybe we should parse the date example to correct format?
133
+ return `${example}`;
134
+ }
135
+ return generateFieldDataDate();
136
+ }
137
+ default: {
122
138
  console.warn(`Unknown scalar type: ${field.typeName}`);
123
139
  return '';
140
+ }
124
141
  }
125
142
  }
126
- function getFieldExample({ field, model, index, exampleMode, }) {
127
- if (exampleMode.mode === 'NoExamples')
128
- return { hasExample: false, example: undefined };
143
+ function getFieldExample({ field, index, exampleMode, }) {
144
+ if (exampleMode.mode === 'NoExamples') {
145
+ return { hasExample: false };
146
+ }
129
147
  if (!field.attributes.examples || field.attributes.examples.length === 0) {
130
- return { hasExample: false, example: undefined };
148
+ return { hasExample: false };
131
149
  }
132
150
  switch (exampleMode.mode) {
133
151
  case 'Permutations': {
@@ -138,30 +156,33 @@ function getFieldExample({ field, model, index, exampleMode, }) {
138
156
  const example = field.attributes.examples[(index - 1) % field.attributes.examples.length];
139
157
  return { hasExample: true, example };
140
158
  }
141
- default:
159
+ default: {
142
160
  throw new types_1.ExhaustiveSwitchCheck(exampleMode);
161
+ }
143
162
  }
144
163
  }
145
164
  function generateFieldDataString({ field, model, index }) {
146
- if (field.name === 'name')
165
+ if (field.name === 'name') {
147
166
  return `${(0, string_1.toPascalCase)(model.name)} ${index}`;
148
- if (field.name === 'email')
167
+ }
168
+ if (field.name === 'email') {
149
169
  return faker_1.faker.internet.email();
170
+ }
150
171
  return faker_1.faker.lorem.words(3);
151
172
  }
152
- function generateFieldDataNumber({}) {
173
+ function generateFieldDataNumber() {
153
174
  return faker_1.faker.datatype.float({ precision: 0.1, min: 0, max: 1 }).toString();
154
175
  }
155
- function generateFieldDataBoolean({}) {
176
+ function generateFieldDataBoolean() {
156
177
  return faker_1.faker.datatype.boolean().toString();
157
178
  }
158
- function generateFieldDataDate({ field, model, index }) {
179
+ function generateFieldDataDate() {
159
180
  const d = faker_1.faker.date.past(3, '2023-04-01T00:00:00.000Z');
160
181
  //set time to midnight UTC
161
182
  d.setUTCHours(0, 0, 0, 0);
162
183
  return `new Date('${d.toISOString()}')`;
163
184
  }
164
- function generateFieldDataRelation({ field, model, index, itemCount, }) {
185
+ function generateFieldDataRelation({ field, itemCount, }) {
165
186
  const referenceId = faker_1.faker.datatype.number({ min: 1, max: itemCount });
166
187
  const refModelMeta = (0, meta_1.getModelMetadata)({ model: field.relationToModel });
167
188
  const brandingFn = refModelMeta.types.toBrandedIdTypeFnName;
@@ -53,18 +53,23 @@ function getAssigmentStatementModel({ fields }) {
53
53
  return `${f.name}: null`;
54
54
  }
55
55
  switch (f.kind) {
56
- case 'scalar':
56
+ case 'scalar': {
57
57
  return `${f.name}: ${(0, fields_1.getDefaultValueForType)(f.typeName)}`;
58
- case 'id':
58
+ }
59
+ case 'id': {
59
60
  const idRefMeta = (0, meta_1.getModelMetadata)({ model: f.model });
60
61
  return `${f.name}: ${idRefMeta.types.toBrandedIdTypeFnName}(${(0, fields_1.getDefaultValueForType)(f.unbrandedTypeName)})`;
61
- case 'relation':
62
+ }
63
+ case 'relation': {
62
64
  const refModelMeta = (0, meta_1.getModelMetadata)({ model: f.relationToModel });
63
65
  return `${f.name}: ${refModelMeta.types.toBrandedIdTypeFnName}(${(0, fields_1.getDefaultValueForType)(f.unbrandedTypeName)})`;
64
- case 'enum':
66
+ }
67
+ case 'enum': {
65
68
  return `${f.name}: "${f.enumerator.values[0]}"`;
66
- default:
69
+ }
70
+ default: {
67
71
  throw new types_1.ExhaustiveSwitchCheck(f);
72
+ }
68
73
  }
69
74
  })
70
75
  .join(',\n');
@@ -90,8 +90,9 @@ function getFieldComment(f) {
90
90
  ${comment}*/`;
91
91
  }
92
92
  function getFieldExamples(f) {
93
- if (!f.attributes.examples)
93
+ if (!f.attributes.examples) {
94
94
  return undefined;
95
+ }
95
96
  return `Examples: ${f.attributes.examples.map((e) => `"${e}"`).join(', ')}`;
96
97
  }
97
98
  /**
@@ -51,7 +51,7 @@ export type FieldAttributes = {
51
51
  /**
52
52
  * Schema tag: ´@@Examples("Example1", "Example2")`
53
53
  */
54
- examples?: unknown[];
54
+ examples?: (string | number | boolean)[];
55
55
  /**
56
56
  * Schema tag: ´@@DefaultField()`
57
57
  * The property of the model that identifies the default row.
@@ -25,7 +25,7 @@ export declare class ImportsGenerator {
25
25
  * NOTE: You should never add no items from a given path.
26
26
  */
27
27
  addImport({ items, from, }: {
28
- items: (Types.Function | Types.ClassName | Types.TypeName | Types.VariableName)[];
28
+ items: (Types.Fnction | Types.ClassName | Types.TypeName | Types.VariableName)[];
29
29
  from: Types.Path;
30
30
  }): ImportsGenerator;
31
31
  /**
@@ -39,6 +39,14 @@ export type SchemaMetaData = {
39
39
  * Path to the index file for the seed package.
40
40
  */
41
41
  indexFilePath: Types.Path;
42
+ /**
43
+ * Path to seed Excel template file.
44
+ */
45
+ templateExcelFilePath: Types.Path;
46
+ /**
47
+ * Path to template decoder file.
48
+ */
49
+ templateDecoderFilePath: Types.Path;
42
50
  /**
43
51
  * Path that may be used in the import statement.
44
52
  */
@@ -116,7 +124,7 @@ export type ModelMetaData = {
116
124
  /**
117
125
  * The name of the function that adds missing values to the partially populated model value.
118
126
  */
119
- stubGenerationFnName: Types.Function;
127
+ stubGenerationFnName: Types.Fnction;
120
128
  /**
121
129
  * Name of the file containing the repository definition for this model.
122
130
  */
@@ -153,11 +161,11 @@ export type ModelMetaData = {
153
161
  /**
154
162
  * The name of the function that decodes a source (database) object to a fully typed object, e.g. `toAggregation`.
155
163
  */
156
- decoderFnName: Types.Function;
164
+ decoderFnName: Types.Fnction;
157
165
  /**
158
166
  * The name of the method that should be used to get objects from the database, e.g. `aggregations`.
159
167
  */
160
- getMethodFnName: Types.Function;
168
+ getMethodFnName: Types.Fnction;
161
169
  };
162
170
  };
163
171
  seed: {
@@ -173,6 +181,30 @@ export type ModelMetaData = {
173
181
  * The file or package name that contains the seed data for this model (e.g. `@d2i/aggregations`).
174
182
  */
175
183
  importPath: Types.Path;
184
+ excel: {
185
+ /**
186
+ * The name of the Excel table, e.g. `Aggregations`.
187
+ */
188
+ tableName: string;
189
+ };
190
+ decoder: {
191
+ /**
192
+ * The name of the Zod schema, e.g. `aggregationSchema`.
193
+ */
194
+ schemaName: Types.VariableName;
195
+ /**
196
+ * The name of the decoder, e.g. `aggregationDecoder`.
197
+ */
198
+ decoderName: Types.VariableName;
199
+ /**
200
+ * The name of the inferred decoder type, e.g. `AggregationType`.
201
+ */
202
+ decoderTypeName: Types.TypeName;
203
+ /**
204
+ * The name of the resulting field in the seed data
205
+ */
206
+ dataName: Types.VariableName;
207
+ };
176
208
  };
177
209
  react: {
178
210
  /**
@@ -187,11 +219,11 @@ export type ModelMetaData = {
187
219
  /**
188
220
  * Name of the function that should be used as React hook (e.g. `useAggregationContext`).
189
221
  */
190
- hookFnName: Types.Function;
222
+ hookFnName: Types.Fnction;
191
223
  /**
192
224
  * Name of the function that may be used to get a single instance of this model (e.g. `useAggregation`).
193
225
  */
194
- instanceGetterHookFnName: Types.Function;
226
+ instanceGetterHookFnName: Types.Fnction;
195
227
  /**
196
228
  * Name of the context wrapper.
197
229
  */
@@ -253,7 +285,7 @@ export type ModelMetaData = {
253
285
  /**
254
286
  * The full method name of the React Query method (e.g. `aggregations.getMap`).
255
287
  */
256
- reactQueryMethod: Types.Function;
288
+ reactQueryMethod: Types.Fnction;
257
289
  };
258
290
  create: {
259
291
  /**
@@ -263,7 +295,7 @@ export type ModelMetaData = {
263
295
  /**
264
296
  * The full method name of the React Query method (e.g. `aggregations.create`).
265
297
  */
266
- reactQueryMethod: Types.Function;
298
+ reactQueryMethod: Types.Fnction;
267
299
  };
268
300
  update: {
269
301
  /**
@@ -273,7 +305,7 @@ export type ModelMetaData = {
273
305
  /**
274
306
  * The full method name of the React Query method (e.g. `aggregations.update`).
275
307
  */
276
- reactQueryMethod: Types.Function;
308
+ reactQueryMethod: Types.Fnction;
277
309
  };
278
310
  delete: {
279
311
  /**
@@ -283,7 +315,7 @@ export type ModelMetaData = {
283
315
  /**
284
316
  * The full method name of the React Query method (e.g. `aggregations.delete`).
285
317
  */
286
- reactQueryMethod: Types.Function;
318
+ reactQueryMethod: Types.Fnction;
287
319
  };
288
320
  };
289
321
  types: {
@@ -303,11 +335,11 @@ export type ModelMetaData = {
303
335
  * Function that may be used to convert a scalar value to a branded ID type,
304
336
  * e.g. `toAggregationId`.
305
337
  */
306
- toBrandedIdTypeFnName: Types.Function;
338
+ toBrandedIdTypeFnName: Types.Fnction;
307
339
  /**
308
340
  * The name of the function that decodes a source (database) object to a fully typed object, e.g. `aggregationDecoder`.
309
341
  */
310
- zodDecoderFnName: Types.Function;
342
+ zodDecoderFnName: Types.Fnction;
311
343
  /**
312
344
  * The name of the file containing type definitions (e.g. `aggregation.type`).
313
345
  */
@@ -332,11 +364,15 @@ export type FieldMetaData = {
332
364
  /**
333
365
  * The name of the method that should be used to get all child objects for a given item, e.g. `getItemsForAggregation`.
334
366
  */
335
- getByForeignKeyMethodFnName: Types.Function;
367
+ getByForeignKeyMethodFnName: Types.Fnction;
336
368
  /**
337
369
  * The name of the method that should be used to get all child Ids for a given item, e.g. `getIdsForAggregation`.
338
370
  */
339
- getByForeignKeyIdsMethodFnName: Types.Function;
371
+ getByForeignKeyIdsMethodFnName: Types.Fnction;
372
+ /**
373
+ * The name of the column in the seed Excel table, e.g. `Aggregation`.
374
+ */
375
+ excelColumnName: string;
340
376
  };
341
377
  /**
342
378
  * A collection of hardcoded values shared across multiple generators related to the given field in the model.
package/dist/lib/meta.js CHANGED
@@ -46,6 +46,8 @@ function getSchemaMetadata({ config }) {
46
46
  },
47
47
  seed: {
48
48
  indexFilePath: Types.toPath(`${config.paths.seedPath}index`),
49
+ templateExcelFilePath: Types.toPath(`${config.paths.seedPath}template.xlsx`),
50
+ templateDecoderFilePath: Types.toPath(`${config.paths.seedPath}seed-decoder`),
49
51
  importPath: Types.toPath(`@${config.project}/seed`),
50
52
  randomSeed: config.randomSeed,
51
53
  },
@@ -63,7 +65,7 @@ exports.getSchemaMetadata = getSchemaMetadata;
63
65
  */
64
66
  function getModelMetadata({ model }) {
65
67
  const { name, schemaConfig: config } = model;
66
- const { PascalCase, camelCase, pluralized, uncapitalizedPlural } = (0, string_1.conjugateNames)(name);
68
+ const { PascalCase, camelCase, pluralized, uncapitalizedPlural, capitalizedPlural } = (0, string_1.conjugateNames)(name);
67
69
  return {
68
70
  userFriendlyName: PascalCase,
69
71
  data: {
@@ -89,6 +91,15 @@ function getModelMetadata({ model }) {
89
91
  filePath: Types.toPath(`${config.paths.seedPath}${uncapitalizedPlural}`),
90
92
  constantName: Types.toVariableName(`${uncapitalizedPlural}`),
91
93
  importPath: Types.toPath(`@${config.project}/seed`),
94
+ excel: {
95
+ tableName: `${capitalizedPlural}`,
96
+ },
97
+ decoder: {
98
+ schemaName: Types.toVariableName(`${camelCase}Schema`),
99
+ decoderTypeName: Types.toTypeName(`${PascalCase}DecoderType`),
100
+ decoderName: Types.toVariableName(`${camelCase}Decoder`),
101
+ dataName: Types.toVariableName(`${uncapitalizedPlural}`),
102
+ },
92
103
  },
93
104
  react: {
94
105
  folderName: Types.toFolderName(`${PascalCase}`),
@@ -161,6 +172,7 @@ function getFieldMetadata({ field }) {
161
172
  tsFieldName: Types.toVariableName((0, string_1.toCamelCase)(field.name)),
162
173
  getByForeignKeyMethodFnName: Types.toFunction(`getItemsFor${PascalCase}`),
163
174
  getByForeignKeyIdsMethodFnName: Types.toFunction(`getIdsFor${PascalCase}`),
175
+ excelColumnName: (0, string_1.toPascalCase)(field.name),
164
176
  };
165
177
  // switch (field.kind) {
166
178
  // case 'id':
@@ -11,13 +11,13 @@ export declare const toTypeName: (t: string) => TypeName;
11
11
  /**
12
12
  * The name of a function (e.g. "toAggregation").
13
13
  */
14
- export type Function = string & {
14
+ export type Fnction = string & {
15
15
  readonly ___type: 'FunctionName';
16
16
  };
17
17
  /**
18
18
  * Converts a string to a branded function name.
19
19
  */
20
- export declare const toFunction: (t: string) => Function;
20
+ export declare const toFunction: (t: string) => Fnction;
21
21
  /**
22
22
  * The name of a class, e.g. "AggregationRepository".
23
23
  */
@@ -40,6 +40,8 @@ function _resolvePath(path) {
40
40
  const chunks = path.split('/');
41
41
  const resolved = [];
42
42
  while (chunks.length > 0) {
43
+ // NOTE: We can safely force unwrap here because we check the length of the array.
44
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
43
45
  const chunk = chunks.shift();
44
46
  if (chunk === '..') {
45
47
  resolved.pop();
@@ -3,9 +3,9 @@
3
3
  * Compatible with the `@prisma/internals` logger and console.
4
4
  */
5
5
  export interface Logger {
6
- log(...data: any[]): void;
7
- warn(message: any, ...optionalParams: any[]): void;
8
- info(message: any, ...optionalParams: any[]): void;
9
- error(message: any, ...optionalParams: any[]): void;
10
- query(message: any, ...optionalParams: any[]): void;
6
+ log(...data: unknown[]): void;
7
+ warn(message: unknown, ...optionalParams: unknown[]): void;
8
+ info(message: unknown, ...optionalParams: unknown[]): void;
9
+ error(message: unknown, ...optionalParams: unknown[]): void;
10
+ query(message: unknown, ...optionalParams: unknown[]): void;
11
11
  }
@@ -2,6 +2,10 @@
2
2
  * Returns the same string with a lowercase first letter.
3
3
  */
4
4
  export declare const uncapitalize: (str: string) => string;
5
+ /**
6
+ * Returns the same string with an uppercase first letter.
7
+ */
8
+ export declare const capitalize: (str: string) => string;
5
9
  /**
6
10
  * Returns the camelCase version of the given string.
7
11
  */
@@ -26,4 +30,5 @@ export declare const conjugateNames: (name: string) => {
26
30
  camelCase: string;
27
31
  pluralized: string;
28
32
  uncapitalizedPlural: string;
33
+ capitalizedPlural: string;
29
34
  };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.conjugateNames = exports.commentLines = exports.pluralize = exports.toPascalCase = exports.toCamelCase = exports.uncapitalize = void 0;
3
+ exports.conjugateNames = exports.commentLines = exports.pluralize = exports.toPascalCase = exports.toCamelCase = exports.capitalize = exports.uncapitalize = void 0;
4
4
  /**
5
5
  * Returns the same string with a lowercase first letter.
6
6
  */
@@ -8,6 +8,13 @@ const uncapitalize = (str) => {
8
8
  return str.charAt(0).toLowerCase() + str.slice(1);
9
9
  };
10
10
  exports.uncapitalize = uncapitalize;
11
+ /**
12
+ * Returns the same string with an uppercase first letter.
13
+ */
14
+ const capitalize = (str) => {
15
+ return str.charAt(0).toUpperCase() + str.slice(1);
16
+ };
17
+ exports.capitalize = capitalize;
11
18
  /**
12
19
  * Returns the camelCase version of the given string.
13
20
  */
@@ -102,7 +109,8 @@ exports.commentLines = commentLines;
102
109
  const conjugateNames = (name) => ({
103
110
  PascalCase: (0, exports.toPascalCase)(name),
104
111
  camelCase: (0, exports.toCamelCase)(name),
105
- pluralized: (0, exports.pluralize)(name),
112
+ pluralized: (0, exports.capitalize)((0, exports.pluralize)(name)),
106
113
  uncapitalizedPlural: (0, exports.uncapitalize)((0, exports.pluralize)(name)),
114
+ capitalizedPlural: (0, exports.capitalize)((0, exports.pluralize)(name)),
107
115
  });
108
116
  exports.conjugateNames = conjugateNames;
@@ -30,26 +30,33 @@ function parseAttributesFromDocumentation({ documentation }) {
30
30
  }
31
31
  exports.parseAttributesFromDocumentation = parseAttributesFromDocumentation;
32
32
  function parseArgumentToStringOrStringArray(str) {
33
- if (str === '')
33
+ if (str === '') {
34
34
  return '';
35
+ }
35
36
  try {
36
37
  return JSON.parse(str);
37
38
  }
38
- catch (_a) { }
39
+ catch (_a) {
40
+ // ignore
41
+ }
39
42
  try {
40
43
  return JSON.parse(`[${str}]`);
41
44
  }
42
- catch (_b) { }
45
+ catch (_b) {
46
+ // ignore
47
+ }
43
48
  try {
44
49
  return JSON.parse(`"${str}"`);
45
50
  }
46
- catch (_c) { }
51
+ catch (_c) {
52
+ // ignore
53
+ }
47
54
  throw new Error(`Could not parse attribute argument: ${str}`);
48
55
  }
49
56
  exports.parseArgumentToStringOrStringArray = parseArgumentToStringOrStringArray;
50
57
  const blankStringBooleanDecoder = zod_1.default
51
58
  .string()
52
- .transform((str) => true)
59
+ .transform(() => true)
53
60
  .or(zod_1.default.boolean())
54
61
  .optional()
55
62
  .default(false);
@@ -84,9 +91,10 @@ function getModelAttributes(model) {
84
91
  }));
85
92
  const result = decoder.safeParse(attributes);
86
93
  if (!result.success) {
87
- throw new Error(`Model ${model.name} has invalid model attributes: ${result.error}`);
94
+ throw new Error(`Model ${model.name} has invalid model attributes: ${result.error.toString()}`);
88
95
  }
89
96
  if (result.data.description === 'The calculated metric value for a dataset.') {
97
+ // TODO: What is this supposed to do?
90
98
  }
91
99
  return result.data;
92
100
  }
@@ -99,10 +107,13 @@ function getFieldAttributes(field) {
99
107
  if (attributes.examples === undefined && attributes.example !== undefined) {
100
108
  attributes.examples = attributes.example;
101
109
  }
110
+ // Prisma also has an "@ignore" attribute - see https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#ignore
111
+ // we handle this the same way as our custom "ignore" attribute
112
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
113
+ const isPrismaIgnored = field.isIgnored === true;
114
+ const isPXLIgnored = Object.hasOwn(attributes, 'ignore');
102
115
  return {
103
- // Prisma also has an "@ignore" attribute - see https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#ignore
104
- // we handle this the same way as our custom "ignore" attribute
105
- ignore: field.isIgnored || Object.hasOwn(attributes, 'ignore'),
116
+ ignore: isPrismaIgnored || isPXLIgnored,
106
117
  description: attributes.description && (0, remeda_1.isString)(attributes.description) ? attributes.description : undefined,
107
118
  isDefaultField: Object.hasOwn(attributes, 'isDefault'),
108
119
  isLabel: Object.hasOwn(attributes, 'isLabel'),
@@ -237,6 +237,7 @@ function getTsTypeForScalar(field) {
237
237
  case 'JSON':
238
238
  case 'Bytes':
239
239
  (0, error_1.throwError)('Not implemented yet');
240
+ break;
240
241
  default:
241
242
  // return field.type
242
243
  (0, error_1.throwError)(`Investigate: 'default' case in getTypescriptType for field ${field.name} of type ${field.type}`);
@@ -273,6 +274,7 @@ function getTsTypeForEnum(field) {
273
274
  case 'Float':
274
275
  case 'Int':
275
276
  (0, error_1.throwError)(`The enum field ${field.name} is of type ${field.type} - but only String fields are supported for enums so far.`);
277
+ break;
276
278
  default:
277
279
  return Types.toTypeName(field.type);
278
280
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {
@@ -17,11 +17,12 @@
17
17
  "@faker-js/faker": "7.6.0",
18
18
  "@prisma/generator-helper": "4.12.0",
19
19
  "@prisma/internals": "^4.12.0",
20
+ "exceljs": "^4.3.0",
20
21
  "fast-glob": "^3.2.12",
21
22
  "prettier": "^2.8.7",
22
23
  "remeda": "1.9.4",
23
24
  "zod": "3.21.4",
24
- "@postxl/lock": "0.3.1"
25
+ "@postxl/lock": "0.3.2"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@prisma/client": "4.12.0",