Skip to content

Commit

Permalink
feat: Custom property mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
unlight committed Nov 15, 2020
1 parent 8ebf3c2 commit f8cc54d
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 35 deletions.
11 changes: 10 additions & 1 deletion src/generate-model/generate-model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import assert from 'assert';
import { expect } from 'chai';
import { Project, QuoteKind, SourceFile } from 'ts-morph';

import { createConfig } from '../generate';
import { generateModel } from '../generate-model';
import { generatorOptions, stringContains } from '../testing';
import { GeneratorConfiguration } from '../types';

describe('generate models', () => {
let sourceFile: SourceFile;
Expand All @@ -19,13 +21,20 @@ describe('generate models', () => {
manipulationSettings: { quoteKind: QuoteKind.Single },
});
const {
generator,
prismaClientDmmf: {
datamodel: { models },
},
} = await generatorOptions(schema, options);
const [model] = models;
sourceFile = project.createSourceFile('_.ts', sourceFileText);
generateModel({ model, sourceFile, projectFilePath: () => '_.ts' });
const config = createConfig(generator.config);
generateModel({
model,
sourceFile,
config,
projectFilePath: () => '_.ts',
});
sourceText = sourceFile.getText();
imports = sourceFile.getImportDeclarations().flatMap((d) =>
d.getNamedImports().map((index) => ({
Expand Down
6 changes: 4 additions & 2 deletions src/generate-model/generate-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import { SourceFile } from 'ts-morph';
import { generateClass } from '../generate-class';
import { generateImport } from '../generate-import';
import { generateProperty } from '../generate-property';
import { PrismaDMMF } from '../types';
import { GeneratorConfiguration, PrismaDMMF } from '../types';

type GenerateModelArgs = {
model: PrismaDMMF.Model;
sourceFile: SourceFile;
projectFilePath(data: { name: string; type: string }): string;
config: GeneratorConfiguration;
};

/**
* Generate model (class).
*/
export function generateModel(args: GenerateModelArgs) {
const { model, sourceFile, projectFilePath } = args;
const { model, sourceFile, projectFilePath, config } = args;
const classDeclaration = generateClass({
decorator: {
name: 'ObjectType',
Expand All @@ -33,6 +34,7 @@ export function generateModel(args: GenerateModelArgs) {
className: model.name,
classType: 'model',
projectFilePath,
config,
});
});
}
5 changes: 4 additions & 1 deletion src/generate-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ import { SourceFile } from 'ts-morph';
import { generateClass } from './generate-class';
import { generateImport } from './generate-import';
import { generateProperty, Model } from './generate-property';
import { GeneratorConfiguration } from './types';

type GenerateObjectArgs = {
sourceFile: SourceFile;
projectFilePath(data: { name: string; type: string }): string;
model: Model;
classType: string;
config: GeneratorConfiguration;
};

/**
* Generate object type (class).
*/
export function generateObject(args: GenerateObjectArgs) {
const { model, classType, sourceFile, projectFilePath } = args;
const { model, classType, sourceFile, projectFilePath, config } = args;
const classDeclaration = generateClass({
decorator: {
name: 'ObjectType',
Expand All @@ -32,6 +34,7 @@ export function generateObject(args: GenerateObjectArgs) {
className: model.name,
classType,
projectFilePath,
config,
});
});
}
24 changes: 22 additions & 2 deletions src/generate-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ClassDeclaration, SourceFile } from 'ts-morph';
import { generateClassProperty } from './generate-class';
import { generateDecorator } from './generate-decorator';
import { generateImport, generateProjectImport } from './generate-import';
import { GeneratorConfiguration } from './types';
import { toGraphqlImportType, toPropertyType } from './utils';

export type Field = {
Expand All @@ -28,14 +29,33 @@ type GeneratePropertyArgs = {
className: string;
field: Field;
classType: string;
config: GeneratorConfiguration;
};

/**
* Generate property for class.
*/
export function generateProperty(args: GeneratePropertyArgs) {
const { field, className, classDeclaration, sourceFile, projectFilePath, classType } = args;
const propertyType = toPropertyType(field);
const {
field,
className,
classDeclaration,
sourceFile,
projectFilePath,
classType,
config,
} = args;
const customType = config.customPropertyTypes[field.type] as
| { name: string; specifier: string }
| undefined;
if (customType) {
generateImport({
sourceFile,
name: customType.name,
moduleSpecifier: customType.specifier,
});
}
const propertyType = customType?.name || toPropertyType(field);
let fieldType = field.type;
if (field.isId || field.kind === 'scalar') {
fieldType = generateImport({
Expand Down
48 changes: 33 additions & 15 deletions src/generate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { PropertyDeclaration, SourceFile } from 'ts-morph';

import { generate } from './generate';
import { generatorOptions, getImportDeclarations, stringContains } from './testing';
import { GeneratorConfigurationOptions } from './types';

describe('main generate', () => {
let property: PropertyDeclaration | undefined;
let sourceFile: SourceFile | undefined;
let sourceFiles: SourceFile[];
let sourceText: string;
async function getResult(args: { schema: string } & Record<string, any>) {
async function getResult(args: { schema: string } & GeneratorConfigurationOptions) {
const { schema, ...options } = args;
const generateOptions = {
...(await generatorOptions(schema, options)),
Expand Down Expand Up @@ -218,7 +219,7 @@ describe('main generate', () => {

it('get rid of atomic number operations', async () => {
await getResult({
atomicNumberOperations: false,
atomicNumberOperations: 'false',
schema: `
model User {
id String @id
Expand Down Expand Up @@ -278,7 +279,7 @@ describe('main generate', () => {

it('user args type', async () => {
await getResult({
atomicNumberOperations: false,
atomicNumberOperations: 'false',
schema: `
model User {
id String @id
Expand Down Expand Up @@ -326,12 +327,8 @@ describe('main generate', () => {
decoratorArguments = struct.decorators?.[0].arguments;
assert.strictEqual(decoratorArguments?.[0], '() => UserMaxAggregateInput');

const imports = sourceFile.getImportDeclarations().flatMap((d) =>
d.getNamedImports().map((index) => ({
name: index.getName(),
specifier: d.getModuleSpecifierValue(),
})),
);
const imports = getImportDeclarations(sourceFile);

assert(imports.find((x) => x.name === 'UserAvgAggregateInput'));
assert(imports.find((x) => x.name === 'UserSumAggregateInput'));
assert(imports.find((x) => x.name === 'UserMinAggregateInput'));
Expand All @@ -340,7 +337,7 @@ describe('main generate', () => {

it('aggregate output types', async () => {
await getResult({
atomicNumberOperations: false,
atomicNumberOperations: 'false',
schema: `
model User {
id String @id
Expand Down Expand Up @@ -376,7 +373,7 @@ describe('main generate', () => {
date DateTime?
}
`,
combineScalarFilters: false,
combineScalarFilters: 'false',
});
const filePaths = sourceFiles.map((s) => String(s.getFilePath()));
const userWhereInput = sourceFiles.find((s) =>
Expand Down Expand Up @@ -410,7 +407,7 @@ describe('main generate', () => {
USER
}
`,
combineScalarFilters: true,
combineScalarFilters: 'true',
});
const filePaths = sourceFiles.map((s) => String(s.getFilePath()));
for (const filePath of filePaths) {
Expand Down Expand Up @@ -445,13 +442,13 @@ describe('main generate', () => {
USER
}
`,
atomicNumberOperations: false,
atomicNumberOperations: 'false',
});
expect(sourceFiles.length).to.be.greaterThan(0);
for (const sourceFile of sourceFiles) {
sourceFile.getClasses().forEach((classDeclaration) => {
if (classDeclaration.getName()?.endsWith('FieldUpdateOperationsInput')) {
assert.fail(`Class should not exists ${classDeclaration.getName()!}`);
expect.fail(`Class should not exists ${classDeclaration.getName()!}`);
}
});
}
Expand All @@ -469,8 +466,29 @@ describe('main generate', () => {
}))
.forEach((struct) => {
if (struct.types.find((s) => s.endsWith('FieldUpdateOperationsInput'))) {
expect.fail(`Property ${struct.name} typed ${struct.type}`);
expect.fail(`Property ${struct.name} typed ${String(struct.type)}`);
}
});
});

it('custom property mapping', async () => {
await getResult({
schema: `
model User {
id String @id
d Decimal
}
`,
customPropertyTypes: 'Decimal:MyDec:decimal.js',
});
const sourceFile = sourceFiles.find((s) => s.getFilePath().endsWith('user.model.ts'));
assert(sourceFile);
const property = sourceFile.getClasses()[0]?.getProperty('d')?.getStructure();
expect(property?.type).to.equal('MyDec');
const imports = getImportDeclarations(sourceFile);
expect(imports).to.deep.contain({
name: 'MyDec',
specifier: 'decimal.js',
});
});
});
40 changes: 28 additions & 12 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { generateInput } from './generate-input';
import { generateModel } from './generate-model';
import { generateObject } from './generate-object';
import { mutateFilters } from './mutate-filters';
import { PrismaDMMF } from './types';
import { GeneratorConfiguration, PrismaDMMF } from './types';
import {
featureName,
getOutputTypeName,
Expand All @@ -19,15 +19,34 @@ import {
} from './utils';
import { generateFileName } from './utils/generate-file-name';

export function createConfig(data: Record<string, string | undefined>): GeneratorConfiguration {
return {
outputFilePattern: data.outputFilePattern || `{feature}/{dasherizedName}.{type}.ts`,
combineScalarFilters: ['true', '1'].includes(data.combineScalarFilters ?? 'true'),
atomicNumberOperations: ['true', '1'].includes(data.atomicNumberOperations ?? 'false'),
customPropertyTypes: Object.fromEntries(
(data.customPropertyTypes || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map((kv) => kv.split(':'))
.map(({ 0: key, 1: name, 2: specifier }) => [
key,
{ name: name || key, specifier },
]),
),
};
}

type GenerateArgs = GeneratorOptions & {
prismaClientDmmf?: PrismaDMMF.Document;
fileExistsSync?: typeof existsSync;
};

export async function generate(args: GenerateArgs) {
const { generator, otherGenerators } = args;
const output = generator.output;
assert(output, 'generator.output is empty');
const config = createConfig(generator.config);
assert(generator.output, 'generator.output is empty');
const fileExistsSync = args.fileExistsSync ?? existsSync;
const prismaClientOutput = otherGenerators.find((x) => x.provider === 'prisma-client-js')
?.output;
Expand All @@ -43,7 +62,7 @@ export async function generate(args: GenerateArgs) {
const projectFilePath = (args: { name: string; type: string; feature?: string }) => {
return generateFileName({
...args,
template: generator.config.outputFilePattern,
template: config.outputFilePattern,
models,
});
};
Expand All @@ -55,7 +74,7 @@ export async function generate(args: GenerateArgs) {
return sourceFile;
}
let sourceFileText = '';
const localFilePath = path.join(output, filePath);
const localFilePath = path.join(generator.output!, filePath);
if (fileExistsSync(localFilePath)) {
sourceFileText = await fs.readFile(localFilePath, { encoding: 'utf8' });
}
Expand All @@ -78,18 +97,14 @@ export async function generate(args: GenerateArgs) {
// Generate models
for (const model of prismaClientDmmf.datamodel.models) {
const sourceFile = await createSourceFile({ type: 'model', name: model.name });
generateModel({ model, sourceFile, projectFilePath });
generateModel({ model, sourceFile, projectFilePath, config });
}
// Generate inputs
let inputTypes = prismaClientDmmf.schema.inputTypes;
inputTypes = inputTypes.filter(
mutateFilters(inputTypes, {
combineScalarFilters: JSON.parse(
(generator.config.combineScalarFilters as string | undefined) ?? 'true',
) as boolean,
atomicNumberOperations: JSON.parse(
(generator.config.atomicNumberOperations as string | undefined) ?? 'false',
) as boolean,
combineScalarFilters: config.combineScalarFilters,
atomicNumberOperations: config.atomicNumberOperations,
}),
);
// Create aggregate inputs
Expand Down Expand Up @@ -149,6 +164,7 @@ export async function generate(args: GenerateArgs) {
sourceFile,
projectFilePath,
model,
config,
});
}

Expand Down
6 changes: 4 additions & 2 deletions src/testing/generator-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import crypto from 'crypto';
import findCacheDir from 'find-cache-dir';
import fs from 'fs';

import { PrismaDMMF } from '../types';
import { GeneratorConfigurationOptions, PrismaDMMF } from '../types';

const {
dependencies: { '@prisma/generator-helper': generatorVersion },
Expand All @@ -17,7 +17,7 @@ const cachePath: string = findCacheDir({ name: 'createGeneratorOptions', create:
*/
export async function generatorOptions(
schema: string,
options?: Record<string, any>,
options?: GeneratorConfigurationOptions,
): Promise<GeneratorOptions & { prismaClientDmmf: PrismaDMMF.Document }> {
// eslint-disable-next-line prefer-rest-params
const data = JSON.stringify([generatorVersion, arguments]);
Expand All @@ -32,6 +32,7 @@ export async function generatorOptions(
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["nativeTypes"]
}
generator proxy {
provider = "node -r ts-node/register/transpile-only src/testing/proxy-generator.ts"
Expand All @@ -40,6 +41,7 @@ export async function generatorOptions(
outputFilePattern = "${options?.outputFilePattern || ''}"
combineScalarFilters = ${JSON.stringify(options?.combineScalarFilters ?? true)}
atomicNumberOperations = ${JSON.stringify(options?.atomicNumberOperations ?? false)}
customPropertyTypes = "${options?.customPropertyTypes || ''}"
}
${schema}
`;
Expand Down
Loading

0 comments on commit f8cc54d

Please sign in to comment.