Skip to content

Commit 61b9735

Browse files
committed
New way to merge interfaces that's variadic and DRY for TS & GQL
1 parent 0a6a003 commit 61b9735

File tree

9 files changed

+152
-48
lines changed

9 files changed

+152
-48
lines changed

src/common/types.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,121 @@ export const IntersectionType = BaseIntersectionType as <
9191
decorator?: ClassDecoratorFactory,
9292
) => Class<A & B, Args>;
9393

94+
export function IntersectTypes<A, Args extends unknown[]>(
95+
type1: AbstractClass<A, Args>,
96+
): IntersectedType<A, Args>;
97+
export function IntersectTypes<A, B, Args extends unknown[]>(
98+
type1: AbstractClass<A, Args>,
99+
type2: AbstractClass<B>,
100+
): IntersectedType<A & B, Args>;
101+
export function IntersectTypes<A, B, C, Args extends unknown[]>(
102+
type1: AbstractClass<A, Args>,
103+
type2: AbstractClass<B>,
104+
type3: AbstractClass<C>,
105+
): IntersectedType<A & B & C, Args>;
106+
export function IntersectTypes<A, B, C, D, Args extends unknown[]>(
107+
type1: AbstractClass<A, Args>,
108+
type2: AbstractClass<B>,
109+
type3: AbstractClass<C>,
110+
type4: AbstractClass<D>,
111+
): IntersectedType<A & B & C & D, Args>;
112+
export function IntersectTypes<A, B, C, D, E, Args extends unknown[]>(
113+
type1: AbstractClass<A, Args>,
114+
type2: AbstractClass<B>,
115+
type3: AbstractClass<C>,
116+
type4: AbstractClass<D>,
117+
type5: AbstractClass<E>,
118+
): IntersectedType<A & B & C & D & E, Args>;
119+
export function IntersectTypes<A, B, C, D, E, F, Args extends unknown[]>(
120+
type1: AbstractClass<A, Args>,
121+
type2: AbstractClass<B>,
122+
type3: AbstractClass<C>,
123+
type4: AbstractClass<D>,
124+
type5: AbstractClass<E>,
125+
type6: AbstractClass<F>,
126+
): IntersectedType<A & B & C & D & E & F, Args>;
127+
export function IntersectTypes<A, B, C, D, E, F, G, Args extends unknown[]>(
128+
type1: AbstractClass<A, Args>,
129+
type2: AbstractClass<B>,
130+
type3: AbstractClass<C>,
131+
type4: AbstractClass<D>,
132+
type5: AbstractClass<E>,
133+
type6: AbstractClass<F>,
134+
type7: AbstractClass<G>,
135+
): IntersectedType<A & B & C & D & E & F & G, Args>;
136+
export function IntersectTypes<A, B, C, D, E, F, G, H, Args extends unknown[]>(
137+
type1: AbstractClass<A, Args>,
138+
type2: AbstractClass<B>,
139+
type3: AbstractClass<C>,
140+
type4: AbstractClass<D>,
141+
type5: AbstractClass<E>,
142+
type6: AbstractClass<F>,
143+
type7: AbstractClass<G>,
144+
type8: AbstractClass<H>,
145+
): IntersectedType<A & B & C & D & E & F & G & H, Args>;
146+
export function IntersectTypes<
147+
A,
148+
B,
149+
C,
150+
D,
151+
E,
152+
F,
153+
G,
154+
H,
155+
I,
156+
Args extends unknown[],
157+
>(
158+
type1: AbstractClass<A, Args>,
159+
type2: AbstractClass<B>,
160+
type3: AbstractClass<C>,
161+
type4: AbstractClass<D>,
162+
type5: AbstractClass<E>,
163+
type6: AbstractClass<F>,
164+
type7: AbstractClass<G>,
165+
type8: AbstractClass<H>,
166+
type9: AbstractClass<I>,
167+
): IntersectedType<A & B & C & D & E & F & G & H & I, Args>;
168+
export function IntersectTypes<
169+
A,
170+
B,
171+
C,
172+
D,
173+
E,
174+
F,
175+
G,
176+
H,
177+
I,
178+
Args extends unknown[],
179+
>(
180+
type1: AbstractClass<A, Args>,
181+
type2: AbstractClass<B>,
182+
type3: AbstractClass<C>,
183+
type4: AbstractClass<D>,
184+
type5: AbstractClass<E>,
185+
type6: AbstractClass<F>,
186+
type7: AbstractClass<G>,
187+
type8: AbstractClass<H>,
188+
type9: AbstractClass<I>,
189+
...types: Array<AbstractClass<any>>
190+
): IntersectedType<unknown, Args>;
191+
export function IntersectTypes<T, Args extends unknown[]>(
192+
...types: Array<AbstractClass<T, Args>>
193+
): IntersectedType<T, Args> {
194+
return Object.assign(
195+
(types as any).reduce(
196+
(a: any, b: any) => (a ? IntersectionType(a, b) : b),
197+
undefined,
198+
),
199+
{
200+
members: types,
201+
},
202+
);
203+
}
204+
205+
type IntersectedType<T, Args extends unknown[]> = Class<T, Args> & {
206+
members: Array<Class<unknown>>;
207+
};
208+
94209
function TODOFn(..._args: any[]) {
95210
throw new NotImplementedException();
96211
}

src/components/budget/dto/budget-record.dto.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { keys as keysOf } from 'ts-transformer-keys';
33
import {
44
Calculated,
55
ID,
6-
IntersectionType,
6+
IntersectTypes,
77
Resource,
88
Secured,
99
SecuredFloatNullable,
@@ -19,12 +19,14 @@ import { ChangesetAware } from '../../changeset/dto';
1919
import { BudgetStatus } from './budget-status.enum';
2020
import { Budget } from './budget.dto';
2121

22+
const Interfaces = IntersectTypes(Resource, ChangesetAware);
23+
2224
@Calculated()
2325
@RegisterResource({ db: e.Budget.Record })
2426
@ObjectType({
25-
implements: [Resource, ChangesetAware],
27+
implements: Interfaces.members,
2628
})
27-
export class BudgetRecord extends IntersectionType(ChangesetAware, Resource) {
29+
export class BudgetRecord extends Interfaces {
2830
static readonly Props = keysOf<BudgetRecord>();
2931
static readonly SecuredProps = keysOf<SecuredProps<BudgetRecord>>();
3032
static readonly Parent = import('./budget.dto').then((m) => m.Budget);

src/components/budget/dto/budget.dto.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { keys as keysOf } from 'ts-transformer-keys';
33
import {
44
Calculated,
55
DbLabel,
6-
IntersectionType,
6+
IntersectTypes,
77
Resource,
88
ResourceRelationsShape,
99
SecuredProperty,
@@ -20,12 +20,14 @@ import { IProject } from '../../project/dto';
2020
import { BudgetRecord } from './budget-record.dto';
2121
import { BudgetStatus } from './budget-status.enum';
2222

23+
const Interfaces = IntersectTypes(Resource, ChangesetAware);
24+
2325
@Calculated()
2426
@RegisterResource({ db: e.Budget })
2527
@ObjectType({
26-
implements: [Resource, ChangesetAware],
28+
implements: Interfaces.members,
2729
})
28-
export class Budget extends IntersectionType(ChangesetAware, Resource) {
30+
export class Budget extends Interfaces {
2931
static readonly Props = keysOf<Budget>();
3032
static readonly SecuredProps = keysOf<SecuredProps<Budget>>();
3133
static readonly Relations = (() => ({

src/components/engagement/dto/engagement.dto.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Type } from '@nestjs/common';
21
import { Field, InterfaceType, ObjectType } from '@nestjs/graphql';
32
import { DateTime } from 'luxon';
43
import { keys as keysOf } from 'ts-transformer-keys';
@@ -9,7 +8,7 @@ import {
98
DateTimeField,
109
DbLabel,
1110
ID,
12-
IntersectionType,
11+
IntersectTypes,
1312
parentIdMiddleware,
1413
Resource,
1514
ResourceRelationsShape,
@@ -48,8 +47,7 @@ export type AnyEngagement = MergeExclusive<
4847
InternshipEngagement
4948
>;
5049

51-
const ChangesetAwareResource: Type<Resource & ChangesetAware> =
52-
IntersectionType(Resource, ChangesetAware);
50+
const Interfaces = IntersectTypes(Resource, ChangesetAware);
5351

5452
export const resolveEngagementType = (val: Pick<AnyEngagement, '__typename'>) =>
5553
val.__typename === 'LanguageEngagement'
@@ -59,12 +57,12 @@ export const resolveEngagementType = (val: Pick<AnyEngagement, '__typename'>) =>
5957
@RegisterResource({ db: e.Engagement })
6058
@InterfaceType({
6159
resolveType: resolveEngagementType,
62-
implements: [Resource, ChangesetAware],
60+
implements: Interfaces.members,
6361
})
6462
/**
6563
* This should be used for GraphQL but never for TypeScript types.
6664
*/
67-
class Engagement extends ChangesetAwareResource {
65+
class Engagement extends Interfaces {
6866
static readonly Props: string[] = keysOf<Engagement>();
6967
static readonly SecuredProps: string[] = keysOf<SecuredProps<Engagement>>();
7068
static readonly Parent = import('../../project/dto').then((m) => m.IProject);

src/components/language/dto/language.dto.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Type } from '@nestjs/common';
21
import { Field, ObjectType } from '@nestjs/graphql';
32
import { stripIndent } from 'common-tags';
43
import { GraphQLString } from 'graphql';
@@ -8,7 +7,7 @@ import {
87
DbLabel,
98
DbUnique,
109
ID,
11-
IntersectionType,
10+
IntersectTypes,
1211
NameField,
1312
Resource,
1413
ResourceRelationsShape,
@@ -33,11 +32,6 @@ import { Pinnable } from '../../pin/dto';
3332
import { Postable } from '../../post/dto';
3433
import { UpdateEthnologueLanguage } from './update-language.dto';
3534

36-
const Interfaces: Type<Resource & Pinnable & Postable> = IntersectionType(
37-
Resource,
38-
IntersectionType(Pinnable, Postable),
39-
);
40-
4135
@ObjectType({
4236
description: SecuredPropertyList.descriptionFor('tags'),
4337
})
@@ -85,9 +79,11 @@ export class EthnologueLanguage {
8579
readonly sensitivity: Sensitivity;
8680
}
8781

82+
const Interfaces = IntersectTypes(Resource, Pinnable, Postable);
83+
8884
@RegisterResource({ db: e.Language })
8985
@ObjectType({
90-
implements: [Resource, Pinnable, Postable],
86+
implements: Interfaces.members,
9187
})
9288
export class Language extends Interfaces {
9389
static readonly Props = keysOf<Language>();

src/components/partner/dto/partner.dto.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { Type } from '@nestjs/common';
21
import { Field, ObjectType } from '@nestjs/graphql';
32
import { DateTime } from 'luxon';
43
import { keys as keysOf } from 'ts-transformer-keys';
54
import {
65
DateTimeField,
7-
IntersectionType,
6+
IntersectTypes,
87
Resource,
98
ResourceRelationsShape,
109
Secured,
@@ -24,14 +23,11 @@ import { Postable } from '../../post/dto';
2423
import { IProject } from '../../project/dto';
2524
import { SecuredPartnerTypes } from './partner-type.enum';
2625

27-
const Interfaces: Type<Resource & Pinnable & Postable> = IntersectionType(
28-
Resource,
29-
IntersectionType(Pinnable, Postable),
30-
);
26+
const Interfaces = IntersectTypes(Resource, Pinnable, Postable);
3127

3228
@RegisterResource({ db: e.Partner })
3329
@ObjectType({
34-
implements: [Resource, Pinnable, Postable],
30+
implements: Interfaces.members,
3531
})
3632
export class Partner extends Interfaces {
3733
static readonly Props = keysOf<Partner>();

src/components/partnership/dto/partnership.dto.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql';
22
import { keys as keysOf } from 'ts-transformer-keys';
33
import {
44
Calculated,
5-
IntersectionType,
5+
IntersectTypes,
66
Resource,
77
ResourceRelationsShape,
88
Secured,
@@ -22,11 +22,13 @@ import { IProject } from '../../project/dto';
2222
import { SecuredFinancialReportingType } from './financial-reporting-type.enum';
2323
import { SecuredPartnershipAgreementStatus } from './partnership-agreement-status.enum';
2424

25+
const Interfaces = IntersectTypes(Resource, ChangesetAware);
26+
2527
@RegisterResource({ db: e.Partnership })
2628
@ObjectType({
27-
implements: [Resource, ChangesetAware],
29+
implements: Interfaces.members,
2830
})
29-
export class Partnership extends IntersectionType(ChangesetAware, Resource) {
31+
export class Partnership extends Interfaces {
3032
static readonly Props = keysOf<Partnership>();
3133
static readonly SecuredProps = keysOf<SecuredProps<Partnership>>();
3234
static readonly Relations = {

src/components/project/dto/project.dto.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Type } from '@nestjs/common';
21
import { Field, InterfaceType, ObjectType } from '@nestjs/graphql';
32
import { simpleSwitch } from '@seedcompany/common';
43
import { stripIndent } from 'common-tags';
@@ -11,7 +10,7 @@ import {
1110
DbLabel,
1211
DbSort,
1312
DbUnique,
14-
IntersectionType,
13+
IntersectTypes,
1514
NameField,
1615
parentIdMiddleware,
1716
Resource,
@@ -54,14 +53,12 @@ type AnyProject = MergeExclusive<
5453
MergeExclusive<MultiplicationTranslationProject, InternshipProject>
5554
>;
5655

57-
const Interfaces: Type<
58-
Resource & Postable & ChangesetAware & Pinnable & Commentable
59-
> = IntersectionType(
56+
const Interfaces = IntersectTypes(
6057
Resource,
61-
IntersectionType(
62-
Commentable,
63-
IntersectionType(Postable, IntersectionType(ChangesetAware, Pinnable)),
64-
),
58+
ChangesetAware,
59+
Pinnable,
60+
Postable,
61+
Commentable,
6562
);
6663

6764
export const resolveProjectType = (val: Pick<AnyProject, 'type'>) => {
@@ -75,7 +72,7 @@ export const resolveProjectType = (val: Pick<AnyProject, 'type'>) => {
7572
@RegisterResource({ db: e.Project })
7673
@InterfaceType({
7774
resolveType: resolveProjectType,
78-
implements: [Resource, Pinnable, Postable, ChangesetAware, Commentable],
75+
implements: Interfaces.members,
7976
})
8077
class Project extends Interfaces {
8178
static readonly Props: string[] = keysOf<Project>();

src/components/user/dto/user.dto.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { Type } from '@nestjs/common';
21
import { Field, ObjectType } from '@nestjs/graphql';
32
import { keys as keysOf } from 'ts-transformer-keys';
43
import {
54
DbUnique,
6-
IntersectionType,
5+
IntersectTypes,
76
NameField,
87
Resource,
98
ResourceRelationsShape,
@@ -25,16 +24,13 @@ import { Unavailability } from '../unavailability/dto';
2524
import { KnownLanguage } from './known-language.dto';
2625
import { SecuredUserStatus } from './user-status.enum';
2726

28-
const PinnableResource: Type<Resource & Pinnable> = IntersectionType(
29-
Resource,
30-
Pinnable,
31-
);
27+
const Interfaces = IntersectTypes(Resource, Pinnable);
3228

3329
@RegisterResource({ db: e.User })
3430
@ObjectType({
35-
implements: [Resource, Pinnable],
31+
implements: Interfaces.members,
3632
})
37-
export class User extends PinnableResource {
33+
export class User extends Interfaces {
3834
static readonly Props = keysOf<User>();
3935
static readonly SecuredProps = keysOf<SecuredProps<User>>();
4036
static readonly Relations = () =>

0 commit comments

Comments
 (0)