Skip to content

Commit ad69843

Browse files
authored
Merge pull request #3545 from SeedCompany/resource-relations
2 parents 9e86e8b + 8f700c3 commit ad69843

File tree

14 files changed

+94
-61
lines changed

14 files changed

+94
-61
lines changed

src/common/resource.dto.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { createMetadataDecorator } from '@seedcompany/nest';
1212
import { LazyGetter as Once } from 'lazy-get-decorator';
1313
import { DateTime } from 'luxon';
14+
import { type OmitIndexSignature } from 'type-fest';
1415
import type {
1516
ResourceDBMap,
1617
ResourceLike,
@@ -29,6 +30,12 @@ import { getParentTypes } from './parent-types';
2930
import { type MaybeSecured, type SecuredProps } from './secured-property';
3031
import { type AbstractClassType } from './types';
3132

33+
// Merge with this to declare Relations types for Resources.
34+
// Be sure to patch at runtime too.
35+
// Don't reference this type directly, other than to declaration merge.
36+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
37+
export interface DeclareResourceRelations {}
38+
3239
const GqlClassType = createMetadataDecorator({
3340
key: CLASS_TYPE_METADATA,
3441
setter: (type: ClassTypeVal) => type,
@@ -53,6 +60,14 @@ export const resolveByTypename =
5360
})
5461
@DbLabel('BaseNode')
5562
export abstract class Resource extends DataObject {
63+
// eslint-disable-next-line @typescript-eslint/naming-convention
64+
static readonly Relations =
65+
(): OmitIndexSignature<DeclareResourceRelations> => {
66+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
67+
// @ts-ignore -- runtime needs to be patched in.
68+
return {};
69+
};
70+
5671
readonly __typename?: string;
5772

5873
@IdField()
@@ -80,7 +95,10 @@ export type ResourceShape<T> = AbstractClassType<T> & {
8095
// Default should probably be considered the props on Resource class.
8196
BaseNodeProps?: string[];
8297
Relations?: Thunk<
83-
Record<string, ResourceShape<any> | [ResourceShape<any>] | undefined>
98+
Record<
99+
string,
100+
ResourceShape<any> | readonly [ResourceShape<any>] | undefined
101+
>
84102
>;
85103
/**
86104
* Define this resource as being a child of another.
@@ -397,7 +415,7 @@ export type SecuredPropsPlusExtraKey<
397415
/* eslint-disable @typescript-eslint/ban-types -- {} is used to mean non-nullable, it's not an empty interface */
398416

399417
export type ExtraPropsFromRelationsKey<T extends ResourceShape<any>> = {
400-
[R in RelKey<T>]: RelOf<T>[R] extends Array<infer U>
418+
[R in RelKey<T>]: RelOf<T>[R] extends ReadonlyArray<infer U>
401419
? U extends ResourceShape<any>
402420
? U['Parent'] extends {}
403421
? never
@@ -411,7 +429,7 @@ export type ExtraPropsFromRelationsKey<T extends ResourceShape<any>> = {
411429
}[RelKey<T>];
412430

413431
export type ChildSinglesKey<T extends ResourceShape<any>> = {
414-
[R in RelKey<T>]: RelOf<T>[R] extends any[]
432+
[R in RelKey<T>]: RelOf<T>[R] extends readonly any[]
415433
? never
416434
: RelOf<T>[R] extends ResourceShape<any>
417435
? RelOf<T>[R]['Parent'] extends {}
@@ -421,7 +439,7 @@ export type ChildSinglesKey<T extends ResourceShape<any>> = {
421439
}[RelKey<T>];
422440

423441
export type ChildListsKey<T extends ResourceShape<any>> = {
424-
[R in RelKey<T>]: RelOf<T>[R] extends Array<infer U>
442+
[R in RelKey<T>]: RelOf<T>[R] extends ReadonlyArray<infer U>
425443
? U extends ResourceShape<any>
426444
? U['Parent'] extends {}
427445
? R

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const Interfaces = IntersectTypes(Resource, ChangesetAware);
2727
})
2828
export class Budget extends Interfaces {
2929
static readonly Relations = (() => ({
30+
...Resource.Relations(),
3031
records: [BudgetRecord],
3132
})) satisfies ResourceRelationsShape;
3233
static readonly Parent = () =>

src/components/comments/dto/comment-thread.dto.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import { Comment } from './comment.dto';
1616
implements: [Resource],
1717
})
1818
export class CommentThread extends Resource {
19-
static readonly Relations = {
19+
static readonly Relations = (() => ({
20+
...Resource.Relations(),
2021
comments: [Comment],
21-
} satisfies ResourceRelationsShape;
22+
})) satisfies ResourceRelationsShape;
2223
static readonly Parent = 'dynamic';
2324

2425
@Field(() => Comment)

src/components/comments/dto/commentable.dto.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import { CommentThread } from './comment-thread.dto';
1515
resolveType: resolveByTypename(Commentable.name),
1616
})
1717
export abstract class Commentable extends Resource {
18-
static readonly Relations = {
18+
static readonly Relations = (() => ({
19+
...Resource.Relations(),
1920
commentThreads: [CommentThread],
20-
} satisfies ResourceRelationsShape;
21+
})) satisfies ResourceRelationsShape;
2122

2223
declare __typename: string;
2324
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ const RequiredWhenNotInDev = RequiredWhen(() => Engagement)({
6969
* This should be used for GraphQL but never for TypeScript types.
7070
*/
7171
class Engagement extends Interfaces {
72-
static readonly Relations = {
73-
...Commentable.Relations,
74-
} satisfies ResourceRelationsShape;
72+
static readonly Relations = (() => ({
73+
...Resource.Relations(),
74+
...Commentable.Relations(),
75+
})) satisfies ResourceRelationsShape;
7576
static readonly Parent = () =>
7677
import('../../project/dto').then((m) => m.IProject);
7778
static readonly resolve = resolveEngagementType;
@@ -163,11 +164,11 @@ export { Engagement as IEngagement, type AnyEngagement as Engagement };
163164
implements: [Engagement],
164165
})
165166
export class LanguageEngagement extends Engagement {
166-
static readonly Relations = {
167-
...Engagement.Relations,
167+
static readonly Relations = (() => ({
168+
...Engagement.Relations(),
168169
// why is this singular?
169170
product: [Product],
170-
} satisfies ResourceRelationsShape;
171+
})) satisfies ResourceRelationsShape;
171172
static readonly Parent = () =>
172173
import('../../project/dto').then((m) => m.TranslationProject);
173174

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,13 @@ const Interfaces = IntersectTypes(Resource, Pinnable, Postable, Commentable);
8282
implements: Interfaces.members,
8383
})
8484
export class Language extends Interfaces {
85-
static readonly Relations = {
85+
static readonly Relations = (() => ({
86+
...Resource.Relations(),
8687
ethnologue: EthnologueLanguage,
8788
locations: [Location], // a child list but not creating deleting...does it still count?
88-
...Postable.Relations,
89-
...Commentable.Relations,
90-
} satisfies ResourceRelationsShape;
89+
...Postable.Relations(),
90+
...Commentable.Relations(),
91+
})) satisfies ResourceRelationsShape;
9192

9293
@NameField({
9394
description: `The real language name`,

src/components/organization/dto/organization.dto.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import { SecuredOrganizationTypes } from './organization-type.dto';
2121
implements: Resource,
2222
})
2323
export class Organization extends Resource {
24-
static readonly Relations = {
24+
static readonly Relations = (() => ({
25+
...Resource.Relations(),
2526
locations: [Location],
26-
} satisfies ResourceRelationsShape;
27+
})) satisfies ResourceRelationsShape;
2728

2829
@NameField()
2930
@DbUnique('OrgName')

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ const Interfaces = IntersectTypes(Resource, Pinnable, Postable, Commentable);
3030
implements: Interfaces.members,
3131
})
3232
export class Partner extends Interfaces {
33-
static readonly Relations = () =>
34-
({
35-
projects: [IProject],
36-
...Postable.Relations,
37-
...Commentable.Relations,
38-
} satisfies ResourceRelationsShape);
33+
static readonly Relations = (() => ({
34+
...Resource.Relations(),
35+
projects: [IProject],
36+
...Postable.Relations(),
37+
...Commentable.Relations(),
38+
})) satisfies ResourceRelationsShape;
3939

4040
readonly organization: Secured<LinkTo<'Organization'>>;
4141

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ const Interfaces = IntersectTypes(Resource, ChangesetAware);
2828
implements: Interfaces.members,
2929
})
3030
export class Partnership extends Interfaces {
31-
static readonly Relations = {
31+
static readonly Relations = (() => ({
32+
...Resource.Relations(),
3233
// why is this here? We have a relation to partner, not org...
3334
organization: Organization,
34-
} satisfies ResourceRelationsShape;
35+
})) satisfies ResourceRelationsShape;
3536
static readonly Parent = () =>
3637
import('../../project/dto').then((m) => m.IProject);
3738

src/components/post/dto/postable.dto.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { InterfaceType } from '@nestjs/graphql';
22
import { stripIndent } from 'common-tags';
3-
import { type ID, IdField, type ResourceRelationsShape } from '~/common';
3+
import {
4+
type ID,
5+
IdField,
6+
Resource,
7+
type ResourceRelationsShape,
8+
} from '~/common';
49
import { e } from '~/core/gel';
510
import { RegisterResource } from '~/core/resources';
611
import { Post } from './post.dto';
@@ -12,9 +17,10 @@ import { Post } from './post.dto';
1217
`,
1318
})
1419
export abstract class Postable {
15-
static readonly Relations = {
20+
static readonly Relations = (() => ({
21+
...Resource.Relations(),
1622
posts: [Post],
17-
} satisfies ResourceRelationsShape;
23+
})) satisfies ResourceRelationsShape;
1824
static readonly Parent = 'dynamic';
1925

2026
@IdField({

0 commit comments

Comments
 (0)