Skip to content

Commit 17d1892

Browse files
authored
Merge pull request #3075 from SeedCompany/edgedb/resource-association-v2
2 parents 39d6a79 + 7a376aa commit 17d1892

File tree

30 files changed

+162
-117
lines changed

30 files changed

+162
-117
lines changed

src/common/resource.dto.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { LazyGetter as Once } from 'lazy-get-decorator';
44
import { DateTime } from 'luxon';
55
import { keys as keysOf } from 'ts-transformer-keys';
66
import { inspect } from 'util';
7-
import { $, abstractType, e } from '~/core/edgedb/reexports';
7+
import type { ResourceDBMap, ResourceMap } from '~/core';
8+
import { $ } from '~/core/edgedb/reexports';
89
import { ScopedRole } from '../components/authorization';
910
import { CalculatedSymbol } from './calculated.decorator';
1011
import { DataObject } from './data-object';
@@ -36,7 +37,6 @@ export const resolveByTypename =
3637
})
3738
@DbLabel('BaseNode')
3839
export abstract class Resource extends DataObject {
39-
static readonly DB = abstractType(e.Resource);
4040
static readonly Props: string[] = keysOf<Resource>();
4141
static readonly SecuredProps: string[] = [];
4242

@@ -61,7 +61,6 @@ export abstract class Resource extends DataObject {
6161
type Thunk<T> = T | (() => T);
6262

6363
export type ResourceShape<T> = AbstractClassType<T> & {
64-
DB?: $.$expr_PathNode;
6564
Props: string[];
6665
SecuredProps: string[];
6766
// An optional list of props that exist on the BaseNode in the DB.
@@ -84,6 +83,9 @@ export type ResourceRelationsShape = ResourceShape<any>['Relations'];
8483
* A helper class to query the static info of a resource in a typed way.
8584
*/
8685
export class EnhancedResource<T extends ResourceShape<any>> {
86+
/** @internal */
87+
static readonly dbTypes = new WeakMap<ResourceShape<any>, $.$expr_PathNode>();
88+
8789
private constructor(readonly type: T) {}
8890
private static readonly refs = new WeakMap<
8991
ResourceShape<any>,
@@ -128,8 +130,8 @@ export class EnhancedResource<T extends ResourceShape<any>> {
128130
: undefined;
129131
}
130132

131-
get name() {
132-
return this.type.name;
133+
get name(): ResourceName<T> {
134+
return this.type.name as any;
133135
}
134136

135137
/**
@@ -239,16 +241,16 @@ export class EnhancedResource<T extends ResourceShape<any>> {
239241
return new Set(props);
240242
}
241243

242-
get db(): T['DB'] & {} {
243-
const type = this.type.DB;
244+
get db(): DBType<T> {
245+
const type = EnhancedResource.dbTypes.get(this.type);
244246
if (!type) {
245247
throw new ServerException(`No DB type defined for ${this.name}`);
246248
}
247-
return type;
249+
return type as any;
248250
}
249251

250-
get dbFQN(): (T['DB'] & {})['__element__']['__name__'] {
251-
return this.db.__element__.__name__;
252+
get dbFQN(): DBType<T>['__element__']['__name__'] {
253+
return this.db.__element__.__name__ as any;
252254
}
253255

254256
@Once()
@@ -282,6 +284,25 @@ export const isResourceClass = <T>(
282284
): cls is ResourceShape<T> =>
283285
'Props' in cls && Array.isArray(cls.Props) && cls.Props.length > 0;
284286

287+
export type ResourceName<TResourceStatic extends ResourceShape<any>> =
288+
ResourceShape<any> extends TResourceStatic
289+
? string // short-circuit non-specific types
290+
: {
291+
[Name in keyof ResourceMap]: ResourceMap[Name] extends TResourceStatic // Only self or subclasses
292+
? TResourceStatic extends ResourceMap[Name] // Exclude subclasses
293+
? Name
294+
: never
295+
: never;
296+
}[keyof ResourceMap] &
297+
string;
298+
299+
export type DBType<TResourceStatic extends ResourceShape<any>> =
300+
ResourceName<TResourceStatic> extends keyof ResourceDBMap
301+
? ResourceDBMap[ResourceName<TResourceStatic>] extends infer T extends $.$expr_PathNode
302+
? T
303+
: never
304+
: never;
305+
285306
export type MaybeUnsecuredInstance<TResourceStatic extends ResourceShape<any>> =
286307
MaybeSecured<InstanceType<TResourceStatic>>;
287308

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ import { ScopedRole } from '../../authorization';
1818
import { ChangesetAware } from '../../changeset/dto';
1919
import { Budget } from './budget.dto';
2020

21-
@RegisterResource()
21+
@RegisterResource({ db: e.Budget.Record })
2222
@ObjectType({
2323
implements: [Resource, ChangesetAware],
2424
})
2525
export class BudgetRecord extends IntersectionType(ChangesetAware, Resource) {
26-
static readonly DB = e.Budget.Record;
2726
static readonly Props = keysOf<BudgetRecord>();
2827
static readonly SecuredProps = keysOf<SecuredProps<BudgetRecord>>();
2928
static readonly Parent = import('./budget.dto').then((m) => m.Budget);
@@ -53,4 +52,7 @@ declare module '~/core/resources/map' {
5352
interface ResourceMap {
5453
BudgetRecord: typeof BudgetRecord;
5554
}
55+
interface ResourceDBMap {
56+
BudgetRecord: typeof e.Budget.Record;
57+
}
5658
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@ import { IProject } from '../../project/dto';
2020
import { BudgetRecord } from './budget-record.dto';
2121
import { BudgetStatus } from './budget-status.enum';
2222

23-
@RegisterResource()
23+
@RegisterResource({ db: e.Budget })
2424
@ObjectType({
2525
implements: [Resource, ChangesetAware],
2626
})
2727
export class Budget extends IntersectionType(ChangesetAware, Resource) {
28-
static readonly DB = e.Budget;
2928
static readonly Props = keysOf<Budget>();
3029
static readonly SecuredProps = keysOf<SecuredProps<Budget>>();
3130
static readonly Relations = {
@@ -64,4 +63,7 @@ declare module '~/core/resources/map' {
6463
interface ResourceMap {
6564
Budget: typeof Budget;
6665
}
66+
interface ResourceDBMap {
67+
Budget: typeof e.default.Budget;
68+
}
6769
}

src/components/ceremony/dto/ceremony.dto.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ import {
1414
} from '../../../common';
1515
import { CeremonyType } from './ceremony-type.enum';
1616

17-
@RegisterResource()
17+
@RegisterResource({ db: e.Engagement.Ceremony })
1818
@Calculated()
1919
@ObjectType({
2020
implements: [Resource],
2121
})
2222
export class Ceremony extends Resource {
23-
static readonly DB = e.Engagement.Ceremony;
2423
static readonly Props = keysOf<Ceremony>();
2524
static readonly SecuredProps = keysOf<SecuredProps<Ceremony>>();
2625
static readonly Parent = import('../../engagement/dto').then(
@@ -54,4 +53,7 @@ declare module '~/core/resources/map' {
5453
interface ResourceMap {
5554
Ceremony: typeof Ceremony;
5655
}
56+
interface ResourceDBMap {
57+
Ceremony: typeof e.Engagement.Ceremony;
58+
}
5759
}

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { DateTime } from 'luxon';
44
import { keys as keysOf } from 'ts-transformer-keys';
55
import { MergeExclusive } from 'type-fest';
66
import { BaseNode } from '~/core/database/results';
7-
import { abstractType, e } from '~/core/edgedb';
7+
import { e } from '~/core/edgedb';
88
import { RegisterResource } from '~/core/resources';
99
import {
1010
Calculated,
@@ -56,7 +56,7 @@ export const resolveEngagementType = (val: Pick<AnyEngagement, '__typename'>) =>
5656
? LanguageEngagement
5757
: InternshipEngagement;
5858

59-
@RegisterResource()
59+
@RegisterResource({ db: e.Engagement })
6060
@InterfaceType({
6161
resolveType: resolveEngagementType,
6262
implements: [Resource, ChangesetAware],
@@ -65,7 +65,6 @@ export const resolveEngagementType = (val: Pick<AnyEngagement, '__typename'>) =>
6565
* This should be used for GraphQL but never for TypeScript types.
6666
*/
6767
class Engagement extends ChangesetAwareResource {
68-
static readonly DB: any = abstractType(e.Engagement);
6968
static readonly Props: string[] = keysOf<Engagement>();
7069
static readonly SecuredProps: string[] = keysOf<SecuredProps<Engagement>>();
7170
static readonly Parent = import('../../project/dto').then((m) => m.IProject);
@@ -149,12 +148,11 @@ class Engagement extends ChangesetAwareResource {
149148
// export as different names to maintain compatibility with our codebase.
150149
export { Engagement as IEngagement, AnyEngagement as Engagement };
151150

152-
@RegisterResource()
151+
@RegisterResource({ db: e.LanguageEngagement })
153152
@ObjectType({
154153
implements: [Engagement],
155154
})
156155
export class LanguageEngagement extends Engagement {
157-
static readonly DB = e.LanguageEngagement;
158156
static readonly Props = keysOf<LanguageEngagement>();
159157
static readonly SecuredProps = keysOf<SecuredProps<LanguageEngagement>>();
160158
static readonly Relations = {
@@ -195,12 +193,11 @@ export class LanguageEngagement extends Engagement {
195193
readonly historicGoal: SecuredString;
196194
}
197195

198-
@RegisterResource()
196+
@RegisterResource({ db: e.InternshipEngagement })
199197
@ObjectType({
200198
implements: [Engagement],
201199
})
202200
export class InternshipEngagement extends Engagement {
203-
static readonly DB = e.InternshipEngagement;
204201
static readonly Props = keysOf<InternshipEngagement>();
205202
static readonly SecuredProps = keysOf<SecuredProps<InternshipEngagement>>();
206203
static readonly Parent = import('../../project/dto').then(
@@ -238,4 +235,9 @@ declare module '~/core/resources/map' {
238235
InternshipEngagement: typeof InternshipEngagement;
239236
LanguageEngagement: typeof LanguageEngagement;
240237
}
238+
interface ResourceDBMap {
239+
Engagement: typeof e.default.Engagement;
240+
InternshipEngagement: typeof e.default.InternshipEngagement;
241+
LanguageEngagement: typeof e.default.LanguageEngagement;
242+
}
241243
}

src/components/ethno-art/dto/ethno-art.dto.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ declare module '../../product/dto/producible.dto' {
1717
}
1818
}
1919

20-
@RegisterResource()
20+
@RegisterResource({ db: e.EthnoArt })
2121
@ObjectType({
2222
implements: [Producible, Resource],
2323
})
2424
export class EthnoArt extends Producible {
25-
static readonly DB = e.EthnoArt;
2625
static readonly Props = keysOf<EthnoArt>();
2726
static readonly SecuredProps = keysOf<SecuredProps<EthnoArt>>();
2827

@@ -35,4 +34,7 @@ declare module '~/core/resources/map' {
3534
interface ResourceMap {
3635
EthnoArt: typeof EthnoArt;
3736
}
37+
interface ResourceDBMap {
38+
EthnoArt: typeof e.default.EthnoArt;
39+
}
3840
}

src/components/field-region/dto/field-region.dto.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ import {
1313
import { e } from '~/core/edgedb';
1414
import { LinkTo, RegisterResource } from '~/core/resources';
1515

16-
@RegisterResource()
16+
@RegisterResource({ db: e.FieldRegion })
1717
@ObjectType({
1818
implements: [Resource],
1919
})
2020
export class FieldRegion extends Resource {
21-
static readonly DB = e.FieldRegion;
2221
static readonly Props = keysOf<FieldRegion>();
2322
static readonly SecuredProps = keysOf<SecuredProps<FieldRegion>>();
2423

@@ -45,4 +44,7 @@ declare module '~/core/resources/map' {
4544
interface ResourceMap {
4645
FieldRegion: typeof FieldRegion;
4746
}
47+
interface ResourceDBMap {
48+
FieldRegion: typeof e.default.FieldRegion;
49+
}
4850
}

src/components/field-zone/dto/field-zone.dto.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ import {
1212
SecuredString,
1313
} from '../../../common';
1414

15-
@RegisterResource()
15+
@RegisterResource({ db: e.FieldZone })
1616
@ObjectType({
1717
implements: [Resource],
1818
})
1919
export class FieldZone extends Resource {
20-
static readonly DB = e.FieldZone;
2120
static readonly Props = keysOf<FieldZone>();
2221
static readonly SecuredProps = keysOf<SecuredProps<FieldZone>>();
2322

@@ -37,4 +36,7 @@ declare module '~/core/resources/map' {
3736
interface ResourceMap {
3837
FieldZone: typeof FieldZone;
3938
}
39+
interface ResourceDBMap {
40+
FieldZone: typeof e.default.FieldZone;
41+
}
4042
}

src/components/file/dto/node.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Readable } from 'stream';
66
import { keys as keysOf } from 'ts-transformer-keys';
77
import { MergeExclusive, Opaque } from 'type-fest';
88
import { BaseNode } from '~/core/database/results';
9-
import { abstractType, e } from '~/core/edgedb';
9+
import { e } from '~/core/edgedb';
1010
import { RegisterResource } from '~/core/resources';
1111
import {
1212
DateTimeField,
@@ -43,15 +43,14 @@ export const resolveFileNode = (val: AnyFileNode) => {
4343
return type;
4444
};
4545

46-
@RegisterResource()
46+
@RegisterResource({ db: e.File.Node })
4747
@InterfaceType({
4848
resolveType: resolveFileNode,
4949
})
5050
/**
5151
* This should be used for GraphQL but never for TypeScript types.
5252
*/
5353
abstract class FileNode extends Resource {
54-
static readonly DB = abstractType(e.File.Node);
5554
static readonly Props: string[] = keysOf<FileNode>();
5655
static readonly SecuredProps: string[] = keysOf<SecuredProps<FileNode>>();
5756

@@ -101,24 +100,22 @@ abstract class BaseFile extends FileNode {
101100
readonly size: number;
102101
}
103102

104-
@RegisterResource()
103+
@RegisterResource({ db: e.File.Version })
105104
@ObjectType({
106105
implements: [FileNode, Resource],
107106
})
108107
export class FileVersion extends BaseFile {
109-
static readonly DB = e.File.Version;
110108
static readonly Props = keysOf<FileVersion>();
111109
static readonly SecuredProps = keysOf<SecuredProps<FileVersion>>();
112110

113111
declare readonly type: 'FileVersion';
114112
}
115113

116-
@RegisterResource()
114+
@RegisterResource({ db: e.File })
117115
@ObjectType({
118116
implements: [FileNode, Resource],
119117
})
120118
export class File extends BaseFile {
121-
static readonly DB = e.File;
122119
static readonly Props = keysOf<File>();
123120
static readonly SecuredProps = keysOf<SecuredProps<File>>();
124121

@@ -132,12 +129,11 @@ export class File extends BaseFile {
132129
readonly modifiedAt: DateTime;
133130
}
134131

135-
@RegisterResource()
132+
@RegisterResource({ db: e.Directory })
136133
@ObjectType({
137134
implements: [FileNode, Resource],
138135
})
139136
export class Directory extends FileNode {
140-
static readonly DB = e.Directory;
141137
static readonly Props = keysOf<Directory>();
142138
static readonly SecuredProps = keysOf<SecuredProps<Directory>>();
143139

@@ -218,4 +214,10 @@ declare module '~/core/resources/map' {
218214
FileNode: typeof FileNode;
219215
FileVersion: typeof FileVersion;
220216
}
217+
interface ResourceDBMap {
218+
Directory: typeof e.Directory;
219+
File: typeof e.default.File;
220+
FileNode: typeof e.File.Node;
221+
FileVersion: typeof e.File.Version;
222+
}
221223
}

src/components/film/dto/film.dto.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ declare module '../../product/dto/producible.dto' {
1717
}
1818
}
1919

20-
@RegisterResource()
20+
@RegisterResource({ db: e.Film })
2121
@ObjectType({
2222
implements: [Producible, Resource],
2323
})
2424
export class Film extends Producible {
25-
static readonly DB = e.Film;
2625
static readonly Props = keysOf<Film>();
2726
static readonly SecuredProps = keysOf<SecuredProps<Film>>();
2827

@@ -35,4 +34,7 @@ declare module '~/core/resources/map' {
3534
interface ResourceMap {
3635
Film: typeof Film;
3736
}
37+
interface ResourceDBMap {
38+
Film: typeof e.default.Film;
39+
}
3840
}

0 commit comments

Comments
 (0)