Skip to content

Fix inheritance on ID/ResourceName types #3434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/project/dto/project.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ export { Project as IProject, type AnyProject as Project };
resolveType: resolveProjectType,
implements: [Project],
})
export class TranslationProject extends Project {}
export class TranslationProject extends Project {
declare readonly type: 'MultiplicationTranslation' | 'MomentumTranslation';
}

@RegisterResource({ db: e.MomentumTranslationProject })
@ObjectType({
Expand Down
4 changes: 3 additions & 1 deletion src/components/user/dto/actor.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { e } from '~/core/gel';
resolveType: resolveByTypename(Actor.name),
})
export class Actor extends DataObject {
declare readonly __typename: 'User' | 'SystemAgent';

@IdField()
readonly id: ID;
}
Expand All @@ -31,7 +33,7 @@ export class Actor extends DataObject {
implements: [Actor],
})
export abstract class SystemAgent extends Actor {
__typename?: 'SystemAgent';
declare readonly __typename: 'SystemAgent';

@NameField()
readonly name: string;
Expand Down
2 changes: 2 additions & 0 deletions src/components/user/dto/user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export class User extends Interfaces {
...Commentable.Relations,
} satisfies ResourceRelationsShape);

declare readonly __typename: 'User';

@Field()
@DbUnique('EmailAddress')
email: SecuredStringNullable;
Expand Down
22 changes: 11 additions & 11 deletions src/components/user/system-agent.gel.repository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { type Role } from '~/common';
import { disableAccessPolicies, edgeql, Gel } from '~/core/gel';
import { disableAccessPolicies, e, Gel } from '~/core/gel';
import { SystemAgentRepository } from './system-agent.repository';

@Injectable()
Expand All @@ -12,15 +12,15 @@ export class SystemAgentGelRepository extends SystemAgentRepository {
}

protected async upsertAgent(name: string, roles?: readonly Role[]) {
const query = edgeql(`
select (
(select SystemAgent filter .name = <str>$name) ??
(insert SystemAgent {
name := <str>$name,
roles := array_unpack(<optional array<Role>>$roles)
})
) {*}
`);
return await this.db.run(query, { name, roles });
const upserted = e.op(
e.select(e.SystemAgent, () => ({ filter_single: { name } })),
'??',
e.insert(e.SystemAgent, { name, roles }),
);
const query = e.select(upserted, (agent) => ({
__typename: e.str('SystemAgent' as const),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to rewrite this query to use the query builder, because EdgeQL doesn't understand string literals, so even though I could write the constant "SystemAgent" it would still be widened to a TS string, by the EdgeQL server.

...agent['*'],
}));
return await this.db.run(query);
}
}
8 changes: 5 additions & 3 deletions src/components/user/system-agent.neo4j.repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Injectable } from '@nestjs/common';
import { node } from 'cypher-query-builder';
import { type ID, type Role } from '~/common';
import { type Role } from '~/common';
import { DatabaseService } from '~/core/database';
import { merge } from '~/core/database/query';
import { type SystemAgent } from './dto';
import { SystemAgentRepository } from './system-agent.repository';

@Injectable()
Expand All @@ -22,8 +24,8 @@ export class SystemAgentNeo4jRepository extends SystemAgentRepository {
'agent.roles': roles ?? [],
},
})
.return<{ agent: { id: ID; name: string; roles: readonly Role[] } }>(
'apoc.convert.toMap(agent) AS agent',
.return<{ agent: SystemAgent }>(
merge('agent', { __typename: '"SystemAgent"' }).as('agent'),
)
.first();
return res!.agent;
Expand Down
20 changes: 13 additions & 7 deletions src/core/resources/resource-name.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,21 @@ type ResourceNameFromStatic<
IncludeSubclasses extends boolean = false,
> = ResourceShape<any> extends TResourceStatic
? string // short-circuit non-specific types
: {
[Name in keyof ResourceMap]: ResourceMap[Name] extends TResourceStatic // Only self or subclasses
? IncludeSubclasses extends true
? Name
: TResourceStatic extends ResourceMap[Name] // Exclude subclasses
? Name
: InstanceType<TResourceStatic> extends infer TResource
? {
[Name in keyof ResourceMap]: InstanceType<
ResourceMap[Name]
> extends infer Other
? Other extends TResource // Only self or subclasses
? IncludeSubclasses extends true
? Name
: TResource extends Other // Exclude subclasses
? Name
: never
: never
: never;
}[keyof ResourceMap];
}[keyof ResourceMap]
: never;

type ResourceNameFromDBName<Name extends AllResourceDBNames> =
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down