Skip to content

Commit 9b2acf9

Browse files
committed
Automatically convert IDs that are aliases to their target IDs
1 parent edcf5f7 commit 9b2acf9

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { isUUID } from 'class-validator';
3+
import DataLoader from 'dataloader';
4+
import LRU from 'lru-cache';
5+
import { ID, NotFoundException } from '~/common';
6+
import { IdResolver } from '~/common/validators/short-id.validator';
7+
import { ILogger, Logger } from '~/core/logger';
8+
import { EdgeDB } from './edgedb.service';
9+
import { e } from './reexports';
10+
11+
@Injectable()
12+
export class AliasIdResolver implements IdResolver {
13+
private readonly loader: DataLoader<ID, ID>;
14+
15+
constructor(
16+
private readonly db: EdgeDB,
17+
@Logger('alias-resolver') private readonly logger: ILogger,
18+
) {
19+
this.loader = new DataLoader((x) => this.loadMany(x), {
20+
// Since this loader exists for the lifetime of the process
21+
// and there's no cache invalidation, we'll just use an LRU cache
22+
cacheMap: new LRU({
23+
max: 10_000,
24+
}),
25+
});
26+
}
27+
28+
async resolve(value: ID): Promise<ID> {
29+
try {
30+
return await this.loader.load(value);
31+
} catch (e) {
32+
if (e instanceof NotFoundException) {
33+
this.loader.clear(value); // maybe it'll be there next request
34+
return value; // assume valid or defer error
35+
}
36+
throw e;
37+
}
38+
}
39+
40+
async loadMany(ids: readonly ID[]): Promise<ReadonlyArray<ID | Error>> {
41+
const aliases = ids.filter((id) => {
42+
if (isUUID(id)) {
43+
return false;
44+
}
45+
return true;
46+
});
47+
if (aliases.length === 0) {
48+
return ids;
49+
}
50+
51+
this.logger.info('Resolving aliases', { ids: aliases });
52+
const foundList = await this.db.run(this.query, { aliasList: aliases });
53+
return ids.map((id) => {
54+
const found = foundList.find((f) => f.name === id);
55+
return found
56+
? found.targetId
57+
: !aliases.includes(id)
58+
? id
59+
: new NotFoundException();
60+
});
61+
}
62+
63+
private readonly query = e.params(
64+
{ aliasList: e.array(e.str) },
65+
({ aliasList }) =>
66+
e.select(e.Alias, (alias) => ({
67+
filter: e.op(alias.name, 'in', e.array_unpack(aliasList)),
68+
name: true,
69+
targetId: alias.target.id,
70+
})),
71+
);
72+
}

src/core/edgedb/edgedb.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { Module, OnModuleDestroy } from '@nestjs/common';
22
import { APP_INTERCEPTOR } from '@nestjs/core';
33
import { createClient, Duration } from 'edgedb';
4+
import { IdResolver } from '~/common/validators/short-id.validator';
45
import type { ConfigService } from '~/core';
6+
import { splitDb } from '../database/split-db.provider';
7+
import { AliasIdResolver } from './alias-id-resolver';
58
import { codecs, registerCustomScalarCodecs } from './codecs';
69
import { EdgeDBTransactionalMutationsInterceptor } from './edgedb-transactional-mutations.interceptor';
710
import { EdgeDB } from './edgedb.service';
@@ -49,8 +52,9 @@ import { TransactionContext } from './transaction.context';
4952
provide: APP_INTERCEPTOR,
5053
useClass: EdgeDBTransactionalMutationsInterceptor,
5154
},
55+
splitDb(IdResolver, AliasIdResolver),
5256
],
53-
exports: [EdgeDB, Client],
57+
exports: [EdgeDB, Client, IdResolver],
5458
})
5559
export class EdgeDBModule implements OnModuleDestroy {
5660
constructor(private readonly client: Client) {}

src/core/validation/validation.module.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Module } from '@nestjs/common';
22
import { APP_PIPE } from '@nestjs/core';
33
import { Validator } from 'class-validator';
44
import {
5-
IdResolver,
65
ValidateIdPipe,
76
ValidIdConstraint,
87
} from '~/common/validators/short-id.validator';
@@ -15,8 +14,7 @@ import { ValidationPipe } from './validation.pipe';
1514
{ provide: APP_PIPE, useExisting: ValidationPipe },
1615
ValidIdConstraint,
1716
ValidateIdPipe,
18-
IdResolver,
1917
],
20-
exports: [ValidationPipe, ValidateIdPipe, IdResolver],
18+
exports: [ValidationPipe, ValidateIdPipe],
2119
})
2220
export class ValidationModule {}

0 commit comments

Comments
 (0)