Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Lahaye <mail@chrislahaye.com>
  • Loading branch information
ChrisLahaye committed Aug 8, 2022
0 parents commit 81d1f78
Show file tree
Hide file tree
Showing 7 changed files with 1,148 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/no-explicit-any": [0],
"no-prototype-builtins": [0]
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
lib/**/*.d.ts
lib/**/*.js
node_modules
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# type-graphql-utils
[![npm version](https://img.shields.io/npm/v/type-graphql-utils)](https://www.npmjs.com/package/type-graphql-utils)
[![npm downloads](https://img.shields.io/npm/dm/type-graphql-utils.svg)](https://www.npmjs.com/package/type-graphql-utils)
[![Known Vulnerabilities](https://snyk.io/test/github/chrislahaye/type-graphql-utils/badge.svg)](https://snyk.io/test/github/chrislahaye/type-graphql-utils)

This module provides utilities to transform [type-graphql](https://www.npmjs.com/package/type-graphql) types.

## Install

```shell
yarn install type-graphql-utils
```

## Usage

```ts
import { Field, InputType, ObjectType } from 'type-graphql';
import { Pick, Partial } from 'type-graphql-utils';

@ObjectType()
class User {
@Field()
id!: number;

@Field()
name!: string;

@Field()
email!: string;
}
```

```
type User {
id: String!
name: String!
email: String!
}
```

```ts
@InputType()
class UserInput1 extends Partial(User) {
// extra fields
}
```

```
input UserInput1 {
id: String
name: String
email: String
}
```

```ts
@InputType()
class UserInput2 extends Pick(User, { name: 1 }) { }
```

```
input UserInput2 {
name: String!
}
```

```ts
@InputType()
class UserInput3 extends Required(Partial(User), { id: 1 }) {}
```

```
input UserInput3 {
id: String!
name: String
email: String
}
```

## API

**`BaseClass`**: The type to transform. A class decorated with `@InputType()` or `@ObjectType()`.

**`names`**: The fields to transform. A potentially optional object containing the names of the fields as key, e.g. `{ id: 1, name: 1 }`. The TypeScript type enforces all values to be *1*, but the value isn't actually used. We just need the names as object to determine if a name is included in constant time.

### `Pick(BaseClass, names)`

Constructs a type by picking the keys of `names` from `BaseClass`.

### `Omit(BaseClass, names)`

Constructs a type by picking all fields from `BaseClass` and then removing the keys of `names`.

### `Partial(BaseClass, [names])`

Constructs a type by picking all fields from `BaseClass` and then setting the keys of `names` to optional. The opposite of Required. By default, `names` contains all names.

### `Required(BaseClass, [names])`

Constructs a type by picking all fields from `BaseClass` and then setting the keys of `names` to required. The opposite of Partial. By default, `names` contains all names.
65 changes: 65 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ClassType, getMetadataStorage, InputType, registerEnumType, ObjectType } from 'type-graphql';
import type { FieldMetadata } from 'type-graphql/dist/metadata/definitions';

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

const metadata = getMetadataStorage();

export function buildEnum<T extends ClassType>(
Class: T,
name: string,
): Record<keyof InstanceType<T>, keyof InstanceType<T>> {
const enumObj = {} as Record<keyof InstanceType<T>, keyof InstanceType<T>>;

metadata.fields.forEach((f) => {
if (f.target !== Class && !f.target.isPrototypeOf(Class)) return;

enumObj[f.name as keyof InstanceType<T>] = f.name;
});

registerEnumType(enumObj, { name });

return enumObj;
}

export function buildType(
BaseClass: ClassType,
buildFn: (f: FieldMetadata) => FieldMetadata | FieldMetadata[] | undefined,
): any {
@InputType({ isAbstract: true })
@ObjectType({ isAbstract: true })
class ChildClass { }

metadata.fields.forEach((f) => {
if (f.target !== BaseClass && !f.target.isPrototypeOf(BaseClass)) return;

const field = buildFn(f);

if (field instanceof Array) {
field.forEach((field) =>
metadata.fields.push({ ...field, target: ChildClass })
);
} else if (field) {
metadata.fields.push({ ...field, target: ChildClass })
}
});

return ChildClass;
}

export function Pick<T extends ClassType, K extends keyof InstanceType<T>>(BaseClass: T, names: Record<K, 1>): ClassType<Pick<InstanceType<T>, K>> {
return buildType(BaseClass, (f) => f.name in names ? f : undefined);
}

export function Omit<T extends ClassType, K extends keyof InstanceType<T>>(BaseClass: T, names: Record<K, 1>): ClassType<Omit<InstanceType<T>, K>> {
return buildType(BaseClass, (f) => f.name in names ? undefined : f);
}

export function Partial<T extends ClassType, K extends keyof InstanceType<T> = keyof InstanceType<T>>(BaseClass: T, names?: Record<K, 1>): ClassType<PartialBy<InstanceType<T>, K>> {
return buildType(BaseClass, (f) => !names || f.name in names ? { ...f, typeOptions: { ...f.typeOptions, nullable: true } } : f);
}

export function Required<T extends ClassType, K extends keyof InstanceType<T> = keyof InstanceType<T>>(BaseClass: T, names?: Record<K, 1>): ClassType<RequiredBy<InstanceType<T>, K>> {
return buildType(BaseClass, (f) => !names || f.name in names ? { ...f, typeOptions: { ...f.typeOptions, nullable: false } } : f);
}
38 changes: 38 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "type-graphql-utils",
"version": "1.0.0",
"description": "utilities to transform type-graphql types",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib/**/*"
],
"scripts": {
"prepare": "install-peers && tsc",
"build": "tsc",
"watch": "tsc -w"
},
"repository": {
"type": "git",
"url": "github:ChrisLahaye/type-graphql-utils"
},
"keywords": [
"type-graphql"
],
"author": "Chris Lahaye <mail@chrislahaye.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/ChrisLahaye/type-graphql-utils/issues"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"eslint": "^8.21.0",
"install-peers-cli": "^2.2.0",
"typescript": "^4.7.4"
},
"peerDependencies": {
"graphql": "^15",
"type-graphql": "^1"
}
}
11 changes: 11 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"declaration": true,
"lib": ["ES2020"],
"module": "commonjs",
"target": "ES2020",
"skipLibCheck": true,
"strict": true,
"experimentalDecorators": true
}
}

0 comments on commit 81d1f78

Please sign in to comment.