Skip to content

Commit 25715a4

Browse files
authored
Merge pull request #3234 from SeedCompany/interface-cleanup
New way to merge interfaces that's variadic and DRY for TS & GQL
2 parents 73eb669 + 61b9735 commit 25715a4

File tree

12 files changed

+188
-84
lines changed

12 files changed

+188
-84
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: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
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,
1110
SecuredBoolean,
1211
SecuredDateNullable,
13-
SecuredEnumList,
1412
SecuredProperty,
1513
SecuredProps,
1614
SecuredStringNullable,
@@ -19,27 +17,17 @@ import {
1917
} from '~/common';
2018
import { e } from '~/core/edgedb';
2119
import { LinkTo, RegisterResource } from '~/core/resources';
22-
import { FinancialReportingType } from '../../partnership/dto';
20+
import { SecuredFinancialReportingTypes } from '../../partnership/dto';
2321
import { Pinnable } from '../../pin/dto';
2422
import { Postable } from '../../post/dto';
2523
import { IProject } from '../../project/dto';
2624
import { SecuredPartnerTypes } from './partner-type.enum';
2725

28-
const Interfaces: Type<Resource & Pinnable & Postable> = IntersectionType(
29-
Resource,
30-
IntersectionType(Pinnable, Postable),
31-
);
32-
33-
@ObjectType({
34-
description: SecuredEnumList.descriptionFor('financial reporting types'),
35-
})
36-
export abstract class SecuredFinancialReportingTypes extends SecuredEnumList(
37-
FinancialReportingType,
38-
) {}
26+
const Interfaces = IntersectTypes(Resource, Pinnable, Postable);
3927

4028
@RegisterResource({ db: e.Partner })
4129
@ObjectType({
42-
implements: [Resource, Pinnable, Postable],
30+
implements: Interfaces.members,
4331
})
4432
export class Partner extends Interfaces {
4533
static readonly Props = keysOf<Partner>();
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1-
import { EnumType, makeEnum } from '~/common';
1+
import { ObjectType } from '@nestjs/graphql';
2+
import { EnumType, makeEnum, SecuredEnum, SecuredEnumList } from '~/common';
23

34
export type FinancialReportingType = EnumType<typeof FinancialReportingType>;
45
export const FinancialReportingType = makeEnum({
56
name: 'FinancialReportingType',
67
values: ['Funded', 'FieldEngaged', 'Hybrid'],
78
});
9+
10+
@ObjectType({
11+
description: SecuredEnumList.descriptionFor('financial reporting types'),
12+
})
13+
export abstract class SecuredFinancialReportingTypes extends SecuredEnumList(
14+
FinancialReportingType,
15+
) {}
16+
17+
@ObjectType({
18+
description: SecuredEnum.descriptionFor('financial reporting type'),
19+
})
20+
export abstract class SecuredFinancialReportingType extends SecuredEnum(
21+
FinancialReportingType,
22+
{ nullable: true },
23+
) {}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { EnumType, makeEnum } from '~/common';
1+
import { ObjectType } from '@nestjs/graphql';
2+
import { EnumType, makeEnum, SecuredEnum } from '~/common';
23

34
export type PartnershipAgreementStatus = EnumType<
45
typeof PartnershipAgreementStatus
@@ -7,3 +8,10 @@ export const PartnershipAgreementStatus = makeEnum({
78
name: 'PartnershipAgreementStatus',
89
values: ['NotAttached', 'AwaitingSignature', 'Signed'],
910
});
11+
12+
@ObjectType({
13+
description: SecuredEnum.descriptionFor('a partnership agreement status'),
14+
})
15+
export abstract class SecuredPartnershipAgreementStatus extends SecuredEnum(
16+
PartnershipAgreementStatus,
17+
) {}

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

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ 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,
99
SecuredBoolean,
1010
SecuredDateNullable,
11-
SecuredEnum,
1211
SecuredProps,
1312
Sensitivity,
1413
SensitivityField,
@@ -20,29 +19,16 @@ import { ChangesetAware } from '../../changeset/dto';
2019
import { Organization } from '../../organization/dto';
2120
import { SecuredPartnerTypes } from '../../partner/dto';
2221
import { IProject } from '../../project/dto';
23-
import { FinancialReportingType } from './financial-reporting-type.enum';
24-
import { PartnershipAgreementStatus } from './partnership-agreement-status.enum';
22+
import { SecuredFinancialReportingType } from './financial-reporting-type.enum';
23+
import { SecuredPartnershipAgreementStatus } from './partnership-agreement-status.enum';
2524

26-
@ObjectType({
27-
description: SecuredEnum.descriptionFor('a partnership agreement status'),
28-
})
29-
export abstract class SecuredPartnershipAgreementStatus extends SecuredEnum(
30-
PartnershipAgreementStatus,
31-
) {}
32-
33-
@ObjectType({
34-
description: SecuredEnum.descriptionFor('partnership funding type'),
35-
})
36-
export abstract class SecuredFinancialReportingType extends SecuredEnum(
37-
FinancialReportingType,
38-
{ nullable: true },
39-
) {}
25+
const Interfaces = IntersectTypes(Resource, ChangesetAware);
4026

4127
@RegisterResource({ db: e.Partnership })
4228
@ObjectType({
43-
implements: [Resource, ChangesetAware],
29+
implements: Interfaces.members,
4430
})
45-
export class Partnership extends IntersectionType(ChangesetAware, Resource) {
31+
export class Partnership extends Interfaces {
4632
static readonly Props = keysOf<Partnership>();
4733
static readonly SecuredProps = keysOf<SecuredProps<Partnership>>();
4834
static readonly Relations = {

0 commit comments

Comments
 (0)