Skip to content

Commit

Permalink
Merge pull request #412 from goldcaddy77/411-typed-jsonb-fields
Browse files Browse the repository at this point in the history
feat(decorators): allow concrete type on JSONField
  • Loading branch information
goldcaddy77 authored Apr 26, 2021
2 parents f0fbd68 + 3b32b4e commit 5e137ce
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 59 deletions.
27 changes: 25 additions & 2 deletions examples/02-complex-example/generated/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ export interface BaseWhereInput {
deletedById_eq?: String | null
}

export interface EventObjectInput {
params: Array<EventParamInput>
}

export interface EventParamInput {
type: String
name: String
value: JSONObject
}

export interface UserCreateInput {
booleanField?: Boolean | null
dateField?: DateTime | null
Expand All @@ -153,7 +163,8 @@ export interface UserCreateInput {
bigIntField?: Float | null
jsonField?: JSONObject | null
jsonFieldNoFilter?: JSONObject | null
stringField: String
typedJsonField?: EventObjectInput | null
stringField?: String | null
noFilterField?: String | null
noSortField?: String | null
noFilterOrSortField?: String | null
Expand Down Expand Up @@ -197,6 +208,7 @@ export interface UserUpdateInput {
bigIntField?: Float | null
jsonField?: JSONObject | null
jsonFieldNoFilter?: JSONObject | null
typedJsonField?: EventObjectInput | null
stringField?: String | null
noFilterField?: String | null
noSortField?: String | null
Expand Down Expand Up @@ -470,6 +482,16 @@ export interface BaseModelUUID extends BaseGraphQLObject {
version: Int
}

export interface EventObject {
params: Array<EventParam>
}

export interface EventParam {
type: String
name: String
value: JSONObject
}

export interface PageInfo {
hasNextPage: Boolean
hasPreviousPage: Boolean
Expand Down Expand Up @@ -504,7 +526,8 @@ export interface User extends BaseGraphQLObject {
bigIntField?: Int | null
jsonField?: JSONObject | null
jsonFieldNoFilter?: JSONObject | null
stringField: String
typedJsonField?: EventObject | null
stringField?: String | null
noFilterField?: String | null
noSortField?: String | null
noFilterOrSortField?: String | null
Expand Down
14 changes: 12 additions & 2 deletions examples/02-complex-example/generated/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import { BaseWhereInput, JsonObject, PaginationArgs, DateOnlyString, DateTimeStr

import { StringEnum } from "../src/modules/user/user.model";

// @ts-ignore
import { EventParam } from "../src/modules/user/user.model";
// @ts-ignore
import { EventObject } from "../src/modules/user/user.model";
// @ts-ignore
import { User } from "../src/modules/user/user.model";

Expand Down Expand Up @@ -787,8 +791,11 @@ export class UserCreateInput {
@TypeGraphQLField(() => GraphQLJSONObject, { nullable: true })
jsonFieldNoFilter?: JsonObject;

@TypeGraphQLField()
stringField!: string;
@TypeGraphQLField(() => EventObject, { nullable: true })
typedJsonField?: EventObject;

@TypeGraphQLField({ nullable: true })
stringField?: string;

@TypeGraphQLField({ nullable: true })
noFilterField?: string;
Expand Down Expand Up @@ -913,6 +920,9 @@ export class UserUpdateInput {
@TypeGraphQLField(() => GraphQLJSONObject, { nullable: true })
jsonFieldNoFilter?: JsonObject;

@TypeGraphQLField(() => EventObject, { nullable: true })
typedJsonField?: EventObject;

@TypeGraphQLField({ nullable: true })
stringField?: string;

Expand Down
27 changes: 25 additions & 2 deletions examples/02-complex-example/generated/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@ interface DeleteResponse {
id: ID!
}

type EventObject {
params: [EventParam!]!
}

input EventObjectInput {
params: [EventParamInput!]!
}

type EventParam {
type: String!
name: String!
value: JSONObject!
}

input EventParamInput {
type: String!
name: String!
value: JSONObject!
}

"""
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
Expand Down Expand Up @@ -126,9 +146,10 @@ type User implements BaseGraphQLObject {
bigIntField: Int
jsonField: JSONObject
jsonFieldNoFilter: JSONObject
typedJsonField: EventObject

"""This is a string field"""
stringField: String!
stringField: String
noFilterField: String
noSortField: String
noFilterOrSortField: String
Expand Down Expand Up @@ -171,7 +192,8 @@ input UserCreateInput {
bigIntField: Float
jsonField: JSONObject
jsonFieldNoFilter: JSONObject
stringField: String!
typedJsonField: EventObjectInput
stringField: String
noFilterField: String
noSortField: String
noFilterOrSortField: String
Expand Down Expand Up @@ -286,6 +308,7 @@ input UserUpdateInput {
bigIntField: Float
jsonField: JSONObject
jsonFieldNoFilter: JSONObject
typedJsonField: EventObjectInput
stringField: String
noFilterField: String
noSortField: String
Expand Down
29 changes: 28 additions & 1 deletion examples/02-complex-example/src/modules/user/user.model.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { GraphQLJSONObject } = require('graphql-type-json');
import { Field, InputType } from 'type-graphql';
import { Column, Unique } from 'typeorm';
import {
BaseModel,
Expand All @@ -16,6 +19,7 @@ import {
JsonObject,
Model,
NumericField,
ObjectType,
StringField,
FloatField
} from '../../../../../src';
Expand All @@ -27,6 +31,26 @@ export enum StringEnum {
BAR = 'BAR'
}

@InputType('EventParamInput')
@ObjectType()
export class EventParam {
@Field()
type!: string;

@Field()
name?: string;

@Field(() => GraphQLJSONObject)
value!: JsonObject;
}

@InputType('EventObjectInput')
@ObjectType()
export class EventObject {
@Field(() => [EventParam])
params!: EventParam[];
}

@Model()
@Unique(['stringField', 'enumField'])
export class User extends BaseModel {
Expand Down Expand Up @@ -81,10 +105,13 @@ export class User extends BaseModel {
@JSONField({ filter: false, nullable: true })
jsonFieldNoFilter?: JsonObject;

@JSONField({ filter: false, nullable: true, gqlFieldType: EventObject })
typedJsonField?: EventObject;

@StringField({
maxLength: 50,
minLength: 2,
nullable: false,
nullable: true,
description: 'This is a string field'
})
stringField: string;
Expand Down
12 changes: 12 additions & 0 deletions examples/02-complex-example/tools/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ async function seedDatabase() {
emailField,
stringField,
jsonField,
typedJsonField: {
params: [
{
name: 'Foo',
type: 'Bar',
value: {
one: 1,
two: 'TWO'
}
}
]
},
dateField,
dateOnlyField,
dateTimeField,
Expand Down
4 changes: 3 additions & 1 deletion src/decorators/JSONField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@
const { GraphQLJSONObject } = require('graphql-type-json');

import { composeMethodDecorators } from '../utils';
import { ClassType } from '../core/types';

import { getCombinedDecorator } from './getCombinedDecorator';

interface JSONFieldOptions {
nullable?: boolean;
filter?: boolean;
gqlFieldType?: ClassType;
}

export function JSONField(options: JSONFieldOptions = {}): any {
const factories = getCombinedDecorator({
fieldType: 'json',
warthogColumnMeta: options,
gqlFieldType: GraphQLJSONObject,
gqlFieldType: options.gqlFieldType ?? GraphQLJSONObject,
dbType: 'jsonb'
});

Expand Down
36 changes: 36 additions & 0 deletions src/decorators/ObjectType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const caller = require('caller'); // eslint-disable-line @typescript-eslint/no-var-requires
import * as path from 'path';
import { ObjectType as TypeGraphQLObjectType } from 'type-graphql';
import { ObjectOptions } from 'type-graphql/dist/decorators/ObjectType.d';

import { ClassType } from '../core';
import { getMetadataStorage } from '../metadata';
import { ClassDecoratorFactory, composeClassDecorators, generatedFolderPath } from '../utils/';

// Allow default TypeORM and TypeGraphQL options to be used
// export function Model({ api = {}, db = {}, apiOnly = false, dbOnly = false }: ModelOptions = {}) {
export function ObjectType(options: ObjectOptions = {}) {
// In order to use the enums in the generated classes file, we need to
// save their locations and import them in the generated file
const modelFileName = caller();

// Use relative paths when linking source files so that we can check the generated code in
// and it will work in any directory structure
const relativeFilePath = path.relative(generatedFolderPath(), modelFileName);

const registerModelWithWarthog = (target: ClassType): void => {
// Save off where the model is located so that we can import it in the generated classes
getMetadataStorage().addClass(target.name, target, relativeFilePath);
};

const factories: any[] = [];

// We add our own Warthog decorator regardless of dbOnly and apiOnly
factories.push(registerModelWithWarthog as ClassDecoratorFactory);

// We shouldn't add this as it creates the GraphQL type, but there is a
// bug if we don't add it because we end up adding the Field decorators in the models
factories.push(TypeGraphQLObjectType(options as ObjectOptions) as ClassDecoratorFactory);

return composeClassDecorators(...factories);
}
1 change: 1 addition & 0 deletions src/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './ManyToManyJoin';
export * from './ManyToOne';
export * from './Model';
export * from './NumericField';
export * from './ObjectType';
export * from './OneToMany';
export * from './StringField';
export * from './UserId';
Expand Down
12 changes: 12 additions & 0 deletions src/metadata/metadata-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface ColumnMetadata extends DecoratorCommonOptions {
propertyName: string;
dataType?: ColumnType; // int16, jsonb, etc...
default?: any;
gqlFieldType?: Function;
enum?: GraphQLEnumType;
enumName?: string;
unique?: boolean;
Expand Down Expand Up @@ -159,6 +160,17 @@ export class MetadataStorage {
];
}

// Adds a class so that we can import it into classes.ts
// This is typically used when adding a strongly typed JSON column
// using JSONField with a gqlFieldType
addClass(name: string, klass: any, filename: string) {
this.classMap[name] = {
filename,
klass,
name
};
}

addModel(name: string, klass: any, filename: string, options: Partial<ModelMetadata> = {}) {
if (this.interfaces.indexOf(name) > -1) {
return; // Don't add interface types to model list
Expand Down
16 changes: 4 additions & 12 deletions src/schema/TypeORMConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { ColumnMetadata, getMetadataStorage, ModelMetadata } from '../metadata';

import {
columnToGraphQLType,
columnTypeToGraphQLDataType,
columnInfoToTypeScriptType
columnToGraphQLDataType,
columnToTypeScriptType
} from './type-conversion';
import { WhereOperator } from '../torm';

Expand Down Expand Up @@ -36,14 +36,6 @@ export function filenameToImportPath(filename: string): string {
return filename.replace(/\.(j|t)s$/, '').replace(/\\/g, '/');
}

export function columnToGraphQLDataType(column: ColumnMetadata): string {
return columnTypeToGraphQLDataType(column.type, column.enumName);
}

export function columnToTypeScriptType(column: ColumnMetadata): string {
return columnInfoToTypeScriptType(column.type, column.enumName);
}

export function generateEnumMapImports(): string[] {
const imports: string[] = [];
const enumMap = getMetadataStorage().enumMap;
Expand Down Expand Up @@ -261,7 +253,7 @@ export function entityToUpdateInputArgs(model: ModelMetadata): string {
}

function columnToTypes(column: ColumnMetadata) {
const graphqlType = columnToGraphQLType(column.type, column.enumName);
const graphqlType = columnToGraphQLType(column);
const tsType = columnToTypeScriptType(column);

return { graphqlType, tsType };
Expand Down Expand Up @@ -469,7 +461,7 @@ export function entityToWhereInput(model: ModelMetadata): string {
}
} else if (column.type === 'json') {
fieldTemplates += `
@TypeGraphQLField(() => GraphQLJSONObject, { nullable: true })
@TypeGraphQLField(() => ${graphQLDataType}, { nullable: true })
${column.propertyName}_json?: JsonObject;
`;
}
Expand Down
Loading

0 comments on commit 5e137ce

Please sign in to comment.