Skip to content

Commit

Permalink
feat(types): adds array types (#388)
Browse files Browse the repository at this point in the history
* feat(types): adds array types

* feat(arrays): adds a bunch of tests

* fix(tests): fix test issue
  • Loading branch information
goldcaddy77 authored Aug 29, 2020
1 parent a7a504c commit 7f1c40b
Show file tree
Hide file tree
Showing 26 changed files with 3,337 additions and 174 deletions.
12 changes: 12 additions & 0 deletions examples/02-complex-example/generated/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ export interface UserCreateInput {
password?: String | null
writeonlyField?: String | null
apiOnlyField?: String | null
arrayOfStrings?: String[] | String | null
arrayOfInts?: Int[] | Int | null
}

export interface UserUpdateInput {
Expand Down Expand Up @@ -220,6 +222,8 @@ export interface UserUpdateInput {
password?: String | null
writeonlyField?: String | null
apiOnlyField?: String | null
arrayOfStrings?: String[] | String | null
arrayOfInts?: Int[] | Int | null
}

export interface UserWhereInput {
Expand Down Expand Up @@ -413,6 +417,12 @@ export interface UserWhereInput {
apiOnlyField_startsWith?: String | null
apiOnlyField_endsWith?: String | null
apiOnlyField_in?: String[] | String | null
arrayOfStrings_containsAll?: String[] | String | null
arrayOfStrings_containsNone?: String[] | String | null
arrayOfStrings_containsAny?: String[] | String | null
arrayOfInts_containsAll?: Int[] | Int | null
arrayOfInts_containsNone?: Int[] | Int | null
arrayOfInts_containsAny?: Int[] | Int | null
}

export interface UserWhereUniqueInput {
Expand Down Expand Up @@ -518,6 +528,8 @@ export interface User extends BaseGraphQLObject {
doublePrecisionField?: Float | null
readonlyField?: String | null
apiOnlyField?: String | null
arrayOfStrings?: Array<String> | null
arrayOfInts?: Array<Int> | null
}

/*
Expand Down
30 changes: 30 additions & 0 deletions examples/02-complex-example/generated/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,24 @@ export class UserWhereInput {

@TypeGraphQLField(() => [String], { nullable: true })
apiOnlyField_in?: string[];

@TypeGraphQLField(() => [String], { nullable: true })
arrayOfStrings_containsAll?: [string];

@TypeGraphQLField(() => [String], { nullable: true })
arrayOfStrings_containsNone?: [string];

@TypeGraphQLField(() => [String], { nullable: true })
arrayOfStrings_containsAny?: [string];

@TypeGraphQLField(() => [Int], { nullable: true })
arrayOfInts_containsAll?: [number];

@TypeGraphQLField(() => [Int], { nullable: true })
arrayOfInts_containsNone?: [number];

@TypeGraphQLField(() => [Int], { nullable: true })
arrayOfInts_containsAny?: [number];
}

@TypeGraphQLInputType()
Expand Down Expand Up @@ -845,6 +863,12 @@ export class UserCreateInput {

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

@TypeGraphQLField(() => [String], { nullable: true })
arrayOfStrings?: string[];

@TypeGraphQLField(() => [Int], { nullable: true })
arrayOfInts?: number[];
}

@TypeGraphQLInputType()
Expand Down Expand Up @@ -965,6 +989,12 @@ export class UserUpdateInput {

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

@TypeGraphQLField(() => [String], { nullable: true })
arrayOfStrings?: string[];

@TypeGraphQLField(() => [Int], { nullable: true })
arrayOfInts?: number[];
}

@ArgsType()
Expand Down
12 changes: 12 additions & 0 deletions examples/02-complex-example/generated/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ type User implements BaseGraphQLObject {
doublePrecisionField: Float
readonlyField: String
apiOnlyField: String
arrayOfStrings: [String!]
arrayOfInts: [Int!]
}

input UserCreateInput {
Expand Down Expand Up @@ -195,6 +197,8 @@ input UserCreateInput {
password: String
writeonlyField: String
apiOnlyField: String
arrayOfStrings: [String!]
arrayOfInts: [Int!]
}

enum UserOrderByInput {
Expand Down Expand Up @@ -308,6 +312,8 @@ input UserUpdateInput {
password: String
writeonlyField: String
apiOnlyField: String
arrayOfStrings: [String!]
arrayOfInts: [Int!]
}

input UserWhereInput {
Expand Down Expand Up @@ -501,6 +507,12 @@ input UserWhereInput {
apiOnlyField_startsWith: String
apiOnlyField_endsWith: String
apiOnlyField_in: [String!]
arrayOfStrings_containsAll: [String!]
arrayOfStrings_containsNone: [String!]
arrayOfStrings_containsAny: [String!]
arrayOfInts_containsAll: [Int!]
arrayOfInts_containsNone: [Int!]
arrayOfInts_containsAny: [Int!]
}

input UserWhereUniqueInput {
Expand Down
6 changes: 6 additions & 0 deletions examples/02-complex-example/src/modules/user/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,11 @@ export class User extends BaseModel {
@StringField({ dbOnly: true, nullable: true })
dbOnlyField!: string;

@StringField({ array: true, nullable: true })
arrayOfStrings!: string[];

@IntField({ array: true, nullable: true })
arrayOfInts!: number[];

// TODO: ForeignKeyField
}
15 changes: 14 additions & 1 deletion examples/02-complex-example/tools/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ async function seedDatabase() {
const dateOnlyField = new Date().toISOString().substring(0, 10);
const dateTimeField = new Date().toISOString();

const randomCharacter = () => {
return Array.from({ length: 1 }, () => Math.random().toString(36)[2]).join('');
};

const randomInt = (max = 10) => {
return Math.round(Math.random() * max);
};

const arrayOfInts = Array.from({ length: randomInt(4) }, () => randomInt());
const arrayOfStrings = Array.from({ length: randomInt(4) }, () => randomCharacter());

try {
const user = await binding.mutation.createUser(
{
Expand All @@ -61,7 +72,9 @@ async function seedDatabase() {
geometryField: {
type: 'Point',
coordinates: [Faker.random.number(100), Faker.random.number(200)]
}
},
arrayOfInts,
arrayOfStrings
}
},
`{ id emailField createdAt createdById }`
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@types/execa": "^2.0.0",
"@types/faker": "^4.1.7",
"@types/faker": "^4.1.12",
"@types/jest": "^24.0.23",
"@types/supertest": "^2.0.8",
"@typescript-eslint/eslint-plugin": "^2.7.0",
Expand All @@ -125,7 +125,7 @@
"eslint": "^6.6.0",
"eslint-config-prettier": "^6.5.0",
"eslint-plugin-prettier": "^3.1.1",
"faker": "^4.1.0",
"faker": "^5.1.0",
"husky": "^3.0.9",
"jest": "^24.9.0",
"lint-staged": "^9.4.3",
Expand Down
2 changes: 1 addition & 1 deletion src/core/BaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { addQueryBuilderWhereItem } from '../torm';
import { BaseModel } from './';
import { StringMap, WhereInput } from './types';

interface BaseOptions {
export interface BaseOptions {
manager?: EntityManager; // Allows consumers to pass in a TransactionManager
}

Expand Down
8 changes: 6 additions & 2 deletions src/decorators/CustomField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface ExtendedTypeGraphQLOptions extends AdvancedOptions, DecoratorCommonOpt
nullable?: boolean; // Hard code this: it exists in both AdvancedOptions and DecoratorDefaults, but they diverge
}

// Documentation: set to array type by setting db.array
interface CustomFieldOptions {
// nullable?: boolean; // need to decide if we should add this shortcut
api: ExtendedTypeGraphQLOptions;
Expand All @@ -27,12 +28,15 @@ export function CustomField(args: CustomFieldOptions): any {
// const nullableOption = typeof args.nullable !== 'undefined' ? { nullable: args.nullable } : {};
// const dbOptions = { ...nullableOption, ...(args.db || {}) };
const { type, filter, sort, ...typeGraphQLOptions } = args.api;
const warthogOptions = { nullable: args.api.nullable, type, filter, sort };
const warthogOptions = { nullable: args.api.nullable, type, filter, sort, array: args.db.array };
const graphQLType = args.db.array
? [columnTypeToGraphQLType(args.api.type)]
: columnTypeToGraphQLType(args.api.type);

// These are the 2 required decorators to get type-graphql and typeorm working
const factories = [
WarthogField(args.api.type, warthogOptions),
Field(() => columnTypeToGraphQLType(args.api.type), typeGraphQLOptions),
Field(() => graphQLType, typeGraphQLOptions),
Column(args.db) as MethodDecoratorFactory
];

Expand Down
1 change: 1 addition & 0 deletions src/decorators/IntField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface IntFieldOptions extends DecoratorCommonOptions {
dataType?: IntColumnType;
default?: number;
filter?: boolean | IntWhereOperator[];
array?: boolean;
}

export function IntField(options: IntFieldOptions = {}): any {
Expand Down
3 changes: 2 additions & 1 deletion src/decorators/StringField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ interface StringFieldOptions extends DecoratorCommonOptions {
default?: string;
unique?: boolean;
filter?: boolean | StringWhereOperator[];
array?: boolean;
}

export function StringField(options: StringFieldOptions = {}): any {
const maxLenOption = options.maxLength ? { length: options.maxLength } : {};
const uniqueOption = options.unique ? { unique: true } : {};

const factories = getCombinedDecorator({
const factories = getCombinedDecorator<StringFieldOptions>({
fieldType: 'string',
warthogColumnMeta: options,
gqlFieldType: String,
Expand Down
13 changes: 8 additions & 5 deletions src/decorators/getCombinedDecorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import { MethodDecoratorFactory } from '../utils';
import { WarthogField } from './WarthogField';

// Combine TypeORM, TypeGraphQL and Warthog decorators
export interface WarthogCombinedDecoratorOptions {
export interface WarthogCombinedDecoratorOptions<T> {
fieldType: FieldType; // This is the warthog field type
warthogColumnMeta: Partial<ColumnMetadata>; // Warthog options like sort, filter, nullable
warthogColumnMeta: T; // Warthog options like sort, filter, nullable
gqlFieldType?: any; // This is the Type that will land in the GraphQL schmea
dbType?: ColumnType;
dbColumnOptions?: any; // Passed to TypeORM `Column` decorator
}

//
export function getCombinedDecorator({
export function getCombinedDecorator<T extends Partial<ColumnMetadata>>({
fieldType,
warthogColumnMeta,
gqlFieldType = String,
dbType = 'varchar',
dbColumnOptions: columnOptions = {}
}: WarthogCombinedDecoratorOptions) {
}: WarthogCombinedDecoratorOptions<T>) {
const nullableOption = warthogColumnMeta.nullable === true ? { nullable: true } : {};
const defaultOption =
typeof warthogColumnMeta.default !== 'undefined' ? { default: warthogColumnMeta.default } : {};
Expand All @@ -32,6 +32,8 @@ export function getCombinedDecorator({
typeof warthogColumnMeta.description !== 'undefined'
? { description: warthogColumnMeta.description }
: {};
const arrayOption = (warthogColumnMeta as any).array ? { array: true } : {};

// TODO: Enable this when TypeORM is fixed: https://github.com/typeorm/typeorm/issues/5906
// const typeOrmColumnOption =
// typeof warthogColumnMeta.description !== 'undefined'
Expand Down Expand Up @@ -69,7 +71,8 @@ export function getCombinedDecorator({
...nullableOption,
...defaultOption,
...columnOptions,
...uniqueOption
...uniqueOption,
...arrayOption
// ...typeOrmColumnOption: // TODO: Enable this when TypeORM is fixed: https://github.com/typeorm/typeorm/issues/5906
}) as MethodDecoratorFactory
);
Expand Down
1 change: 1 addition & 0 deletions src/metadata/metadata-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface ColumnMetadata extends DecoratorCommonOptions {
enum?: GraphQLEnumType;
enumName?: string;
unique?: boolean;
array?: boolean;
}

export type ColumnOptions = Partial<ColumnMetadata>;
Expand Down
44 changes: 36 additions & 8 deletions src/schema/TypeORMConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,13 @@ export function entityToWhereUniqueInput(model: ModelMetadata): string {
}

const nullable = uniqueFieldsAreNullable ? ', { nullable: true }' : '';
const graphQLDataType = columnToGraphQLDataType(column);
const tsType = columnToTypeScriptType(column);
let graphQLDataType = columnToGraphQLDataType(column);
let tsType = columnToTypeScriptType(column);

if (column.array) {
tsType = tsType.concat('[]');
graphQLDataType = `[${graphQLDataType}]`;
}

fieldsTemplate += `
@TypeGraphQLField(() => ${graphQLDataType}${nullable})
Expand Down Expand Up @@ -151,10 +156,15 @@ export function entityToCreateInput(model: ModelMetadata): string {
if (!column.editable || column.readonly) {
return;
}
const graphQLDataType = columnToGraphQLDataType(column);
let graphQLDataType = columnToGraphQLDataType(column);
const nullable = column.nullable ? '{ nullable: true }' : '';
const tsRequired = column.nullable ? '?' : '!';
const tsType = columnToTypeScriptType(column);
let tsType = columnToTypeScriptType(column);

if (column.array) {
tsType = tsType.concat('[]');
graphQLDataType = `[${graphQLDataType}]`;
}

if (columnRequiresExplicitGQLType(column)) {
fieldTemplates += `
Expand Down Expand Up @@ -195,8 +205,13 @@ export function entityToUpdateInput(model: ModelMetadata): string {

// TODO: also don't allow updated foreign key fields
// Example: photo.userId: String
const graphQLDataType = columnToGraphQLDataType(column);
const tsType = columnToTypeScriptType(column);
let graphQLDataType = columnToGraphQLDataType(column);
let tsType = columnToTypeScriptType(column);

if (column.array) {
tsType = tsType.concat('[]');
graphQLDataType = `[${graphQLDataType}]`;
}

if (columnRequiresExplicitGQLType(column)) {
fieldTemplates += `
Expand Down Expand Up @@ -272,7 +287,18 @@ export function entityToWhereInput(model: ModelMetadata): string {

// TODO: for foreign key fields, only allow the same filters as ID below
// Example: photo.userId: String
if (column.type === 'id') {
if (column.array) {
fieldTemplates += `
@TypeGraphQLField(() => [${graphQLDataType}],{ nullable: true })
${column.propertyName}_containsAll?: [${tsType}];
@TypeGraphQLField(() => [${graphQLDataType}],{ nullable: true })
${column.propertyName}_containsNone?: [${tsType}];
@TypeGraphQLField(() => [${graphQLDataType}],{ nullable: true })
${column.propertyName}_containsAny?: [${tsType}];
`;
} else if (column.type === 'id') {
const graphQlType = 'ID';

if (allowFilter('eq')) {
Expand Down Expand Up @@ -492,7 +518,8 @@ export function entityToOrderByEnum(model: ModelMetadata): string {

// If user says this is not sortable, then don't allow sorting
// Also, if the column is "write only", therefore it cannot be read and shouldn't be sortable
if (column.sort && !column.writeonly) {
// Also, doesn't make sense to sort arrays
if (column.sort && !column.writeonly && !column.array) {
fieldsTemplate += `
${column.propertyName}_ASC = '${column.propertyName}_ASC',
${column.propertyName}_DESC = '${column.propertyName}_DESC',
Expand All @@ -514,6 +541,7 @@ export function entityToOrderByEnum(model: ModelMetadata): string {
function columnRequiresExplicitGQLType(column: ColumnMetadata) {
return (
column.enum ||
column.array ||
column.type === 'json' ||
column.type === 'id' ||
column.type === 'date' ||
Expand Down
Loading

0 comments on commit 7f1c40b

Please sign in to comment.