From 118dd952718738c52d16bcbfc47e7bc7b7ef2505 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Sat, 13 Jul 2024 10:18:56 -0700 Subject: [PATCH] fix: add ClearAll dataloader method Signed-off-by: Matt Krick --- .../server/billing/helpers/adjustUserCount.ts | 2 +- packages/server/dataloader/RootDataLoader.ts | 106 ++++++++--------- .../server/dataloader/customLoaderMakers.ts | 109 ++++++++++-------- .../dataloader/foreignKeyLoaderMaker.ts | 16 ++- .../server/dataloader/normalizeResults.ts | 6 +- .../dataloader/primaryKeyLoaderMakers.ts | 11 ++ .../dataloader/rethinkForeignKeyLoader.ts | 2 +- packages/server/graphql/DataLoaderCache.ts | 55 +++------ packages/server/graphql/getDataLoader.ts | 4 +- 9 files changed, 154 insertions(+), 157 deletions(-) diff --git a/packages/server/billing/helpers/adjustUserCount.ts b/packages/server/billing/helpers/adjustUserCount.ts index 360a72df49d..3c72a43873e 100644 --- a/packages/server/billing/helpers/adjustUserCount.ts +++ b/packages/server/billing/helpers/adjustUserCount.ts @@ -82,7 +82,6 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork dataLoader.get('organizations').loadMany(orgIds), dataLoader.get('organizationUsersByUserId').load(userId) ]) - dataLoader.get('organizationUsersByUserId').clear(userId) const organizations = rawOrganizations.filter(isValid) const docs = orgIds.map((orgId) => { const oldOrganizationUser = organizationUsers.find( @@ -97,6 +96,7 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork tier: organization.tier }) }) + dataLoader.clearAll('organizationUsers') await getKysely() .insertInto('OrganizationUser') .values(docs) diff --git a/packages/server/dataloader/RootDataLoader.ts b/packages/server/dataloader/RootDataLoader.ts index 1b2546ec1f8..146ae62e1e5 100644 --- a/packages/server/dataloader/RootDataLoader.ts +++ b/packages/server/dataloader/RootDataLoader.ts @@ -1,8 +1,6 @@ import DataLoader from 'dataloader' -import {DBType} from '../database/rethinkDriver' import RethinkForeignKeyLoaderMaker from './RethinkForeignKeyLoaderMaker' import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' -import UpdatableCacheDataLoader from './UpdatableCacheDataLoader' import * as atlassianLoaders from './atlassianLoaders' import * as azureDevOpsLoaders from './azureDevOpsLoaders' import * as customLoaderMakers from './customLoaderMakers' @@ -41,79 +39,81 @@ const loaderMakers = { ...azureDevOpsLoaders } as const -type LoaderMakers = typeof loaderMakers -export type Loaders = keyof LoaderMakers +export type Loaders = keyof typeof loaderMakers -type PrimaryLoaderMakers = typeof rethinkPrimaryKeyLoaderMakers -type PrimaryLoaders = keyof PrimaryLoaderMakers -type Unprimary = T extends RethinkPrimaryKeyLoaderMaker ? DBType[U] : never -type TypeFromPrimary = Unprimary +export type AllPrimaryLoaders = + | keyof typeof primaryKeyLoaderMakers + | keyof typeof rethinkPrimaryKeyLoaderMakers +export type RegisterDependsOn = (primaryLoaders: AllPrimaryLoaders | AllPrimaryLoaders[]) => void -type ForeignLoaderMakers = typeof rethinkForeignKeyLoaderMakers -type ForeignLoaders = keyof ForeignLoaderMakers -type Unforeign = T extends RethinkForeignKeyLoaderMaker ? U : never -type TypeFromForeign = TypeFromPrimary> - -/** - * When adding a new loaders file like {@link atlassianLoaders} or {@link githubLoaders} - * this type has to include a typeof of newly added loaders - */ -type CustomLoaderMakers = typeof customLoaderMakers & - typeof atlassianLoaders & - typeof jiraServerLoaders & - typeof pollLoaders & - typeof integrationAuthLoaders & - typeof primaryKeyLoaderMakers & - typeof foreignKeyLoaderMakers & - typeof azureDevOpsLoaders & - typeof gitlabLoaders & - typeof gcalLoaders -type CustomLoaders = keyof CustomLoaderMakers -type Uncustom = T extends (parent: RootDataLoader) => infer U ? U : never -type TypeFromCustom = Uncustom - -// Use this if you don't need the dataloader to be shareable -export interface DataLoaderInstance { - get(loaderName: LoaderName): TypedDataLoader +// The RethinkDB logic is a leaky abstraction! It will be gone soon & this will be generic enough to put in its own package +interface GenericDataLoader { + clearAll(pkLoaderName: TPrimaryLoaderNames): void + get( + loaderName: LoaderName + ): Loader extends (...args: any[]) => any + ? ReturnType + : // can delete below this line after RethinkDB is gone + Loader extends RethinkPrimaryKeyLoaderMaker + ? ReturnType> + : Loader extends RethinkForeignKeyLoaderMaker + ? ReturnType< + typeof rethinkForeignKeyLoader< + (typeof rethinkPrimaryKeyLoaderMakers)[U] extends RethinkPrimaryKeyLoaderMaker< + infer V + > + ? V + : never + > + > + : never } -export type TypedDataLoader = LoaderName extends CustomLoaders - ? TypeFromCustom - : UpdatableCacheDataLoader< - string, - LoaderName extends ForeignLoaders - ? TypeFromForeign[] - : LoaderName extends PrimaryLoaders - ? TypeFromPrimary - : never - > +export type DataLoaderInstance = GenericDataLoader /** * This is the main dataloader */ -export default class RootDataLoader implements DataLoaderInstance { - dataLoaderOptions: DataLoader.Options +export default class RootDataLoader< + O extends DataLoader.Options = DataLoader.Options +> { + dataLoaderOptions: O // casted to any because access to the loaders will results in a creation if needed loaders: LoaderDict = {} as any - constructor(dataLoaderOptions: DataLoader.Options = {}) { + dependentLoaders: Record = {} + constructor(dataLoaderOptions: O = {} as O) { this.dataLoaderOptions = dataLoaderOptions } - get(loaderName: LoaderName): TypedDataLoader { + clearAll: DataLoaderInstance['clearAll'] = (pkLoaderName) => { + const dependencies = [pkLoaderName, ...(this.dependentLoaders[pkLoaderName] ?? [])] + dependencies.forEach((loaderName) => { + this.loaders[loaderName]?.clearAll() + }) + } + get: DataLoaderInstance['get'] = (loaderName) => { let loader = this.loaders[loaderName] - if (loader) return loader as TypedDataLoader - const loaderMaker = loaderMakers[loaderName] + if (loader) return loader + const loaderMaker = loaderMakers[loaderName as keyof typeof loaderMakers] if (loaderMaker instanceof RethinkPrimaryKeyLoaderMaker) { const {table} = loaderMaker loader = rethinkPrimaryKeyLoader(this.dataLoaderOptions, table) } else if (loaderMaker instanceof RethinkForeignKeyLoaderMaker) { const {fetch, field, pk} = loaderMaker - const basePkLoader = this.get(pk as PrimaryLoaders) + const basePkLoader = this.get(pk) as any loader = rethinkForeignKeyLoader(basePkLoader, this.dataLoaderOptions, field, fetch) } else { - loader = (loaderMaker as any)(this) + const dependsOn: RegisterDependsOn = (inPrimaryLoaders) => { + const primaryLoaders = Array.isArray(inPrimaryLoaders) + ? inPrimaryLoaders + : [inPrimaryLoaders] + primaryLoaders.forEach((primaryLoader) => { + ;(this.dependentLoaders[primaryLoader] ??= []).push(loaderName) + }) + } + loader = (loaderMaker as any)(this, dependsOn) } this.loaders[loaderName] = loader! - return loader as TypedDataLoader + return loader as any } } diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 4de0beb1abb..4f54a5c857b 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -34,7 +34,7 @@ import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' import isUserVerified from '../utils/isUserVerified' import NullableDataLoader from './NullableDataLoader' -import RootDataLoader from './RootDataLoader' +import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import normalizeArrayResults from './normalizeArrayResults' import normalizeResults from './normalizeResults' import {selectTeams} from './primaryKeyLoaderMakers' @@ -85,7 +85,11 @@ export const serializeUserTasksKey = (key: UserTasksKey) => { return parts.join(':') } -export const commentCountByDiscussionId = (parent: RootDataLoader) => { +export const commentCountByDiscussionId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('comments') return new DataLoader( async (discussionIds) => { const r = await getRethink() @@ -143,7 +147,8 @@ export const meetingTaskEstimates = (parent: RootDataLoader) => { ) } -export const reactables = (parent: RootDataLoader) => { +export const reactables = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn(reactableLoaders.map((a) => a.loader)) return new DataLoader( async (keys) => { const reactableResults = (await Promise.all( @@ -164,7 +169,8 @@ export const reactables = (parent: RootDataLoader) => { ) } -export const userTasks = (parent: RootDataLoader) => { +export const userTasks = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('tasks') return new DataLoader( async (keys) => { const r = await getRethink() @@ -310,7 +316,8 @@ export const githubDimensionFieldMaps = (parent: RootDataLoader) => { ) } -export const meetingSettingsByType = (parent: RootDataLoader) => { +export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingSettings') return new DataLoader( async (keys) => { const r = await getRethink() @@ -390,7 +397,11 @@ export const organizationApprovedDomains = (parent: RootDataLoader) => { ) } -export const organizationUsersByUserIdOrgId = (parent: RootDataLoader) => { +export const organizationUsersByUserIdOrgId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('organizationUsers') return new DataLoader<{orgId: string; userId: string}, OrganizationUser | null, string>( async (keys) => { const r = await getRethink() @@ -415,7 +426,8 @@ export const organizationUsersByUserIdOrgId = (parent: RootDataLoader) => { ) } -export const meetingTemplatesByType = (parent: RootDataLoader) => { +export const meetingTemplatesByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingTemplates') return new DataLoader( async (keys) => { const types = {} as Record @@ -451,7 +463,6 @@ export const meetingTemplatesByType = (parent: RootDataLoader) => { ) } -// :TODO:(jmtaber129): Generalize this to all meeting types if needed. export const teamMeetingTemplateByTeamId = (parent: RootDataLoader) => { return new DataLoader[], string>( async (teamIds) => { @@ -461,11 +472,7 @@ export const teamMeetingTemplateByTeamId = (parent: RootDataLoader) => { .selectAll() .where('teamId', 'in', teamIds) .execute() - return teamIds.map((teamId) => { - return teamMeetingTemplates.filter( - (teamMeetingTemplate) => teamMeetingTemplate.teamId === teamId - ) - }) + return normalizeArrayResults(teamIds, teamMeetingTemplates, 'teamId') }, { ...parent.dataLoaderOptions @@ -473,7 +480,8 @@ export const teamMeetingTemplateByTeamId = (parent: RootDataLoader) => { ) } -export const meetingTemplatesByOrgId = (parent: RootDataLoader) => { +export const meetingTemplatesByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingTemplates') return new DataLoader( async (orgIds) => { const pg = getKysely() @@ -499,7 +507,8 @@ export const meetingTemplatesByOrgId = (parent: RootDataLoader) => { ) } -export const meetingTemplatesByTeamId = (parent: RootDataLoader) => { +export const meetingTemplatesByTeamId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingTemplates') return new DataLoader( async (teamIds) => { const pg = getKysely() @@ -522,7 +531,8 @@ type MeetingStat = { meetingType: MeetingTypeEnum createdAt: Date } -export const meetingStatsByOrgId = (parent: RootDataLoader) => { +export const meetingStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('newMeetings') return new DataLoader( async (orgIds) => { const r = await getRethink() @@ -553,7 +563,8 @@ export const meetingStatsByOrgId = (parent: RootDataLoader) => { ) } -export const teamStatsByOrgId = (parent: RootDataLoader) => { +export const teamStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('teams') return new DataLoader( async (orgIds) => { const teamStatsByOrgId = await Promise.all( @@ -573,7 +584,11 @@ export const teamStatsByOrgId = (parent: RootDataLoader) => { ) } -export const taskIdsByTeamAndGitHubRepo = (parent: RootDataLoader) => { +export const taskIdsByTeamAndGitHubRepo = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('tasks') return new DataLoader<{teamId: string; nameWithOwner: string}, string[], string>( async (keys) => { const r = await getRethink() @@ -611,7 +626,11 @@ export const meetingHighlightedTaskId = (parent: RootDataLoader) => { ) } -export const activeMeetingsByMeetingSeriesId = (parent: RootDataLoader) => { +export const activeMeetingsByMeetingSeriesId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('newMeetings') return new DataLoader( async (keys) => { const r = await getRethink() @@ -629,7 +648,11 @@ export const activeMeetingsByMeetingSeriesId = (parent: RootDataLoader) => { ) } -export const lastMeetingByMeetingSeriesId = (parent: RootDataLoader) => { +export const lastMeetingByMeetingSeriesId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('newMeetings') return new DataLoader( async (keys) => tracer.trace('lastMeetingByMeetingSeriesId', async () => { @@ -653,7 +676,8 @@ export const lastMeetingByMeetingSeriesId = (parent: RootDataLoader) => { ) } -export const billingLeadersIdsByOrgId = (parent: RootDataLoader) => { +export const billingLeadersIdsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('organizationUsers') return new DataLoader( async (keys) => { const r = await getRethink() @@ -676,27 +700,8 @@ export const billingLeadersIdsByOrgId = (parent: RootDataLoader) => { ) } -export const saml = (parent: RootDataLoader) => { - return new NullableDataLoader( - async (samlIds) => { - const pg = getKysely() - const res = await pg - .selectFrom('SAMLDomain') - .innerJoin('SAML', 'SAML.id', 'SAMLDomain.samlId') - .where('SAML.id', 'in', samlIds) - .groupBy('SAML.id') - .selectAll('SAML') - .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) - .execute() - return samlIds.map((samlId) => res.find((row) => row.id === samlId)) - }, - { - ...parent.dataLoaderOptions - } - ) -} - -export const samlByDomain = (parent: RootDataLoader) => { +export const samlByDomain = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('saml') return new NullableDataLoader( async (domains) => { const pg = getKysely() @@ -708,15 +713,15 @@ export const samlByDomain = (parent: RootDataLoader) => { .selectAll('SAML') .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) .execute() - return domains.map((domain) => res.find((row) => row.domains.includes(domain))) + return normalizeResults(domains, res) }, { ...parent.dataLoaderOptions } ) } - -export const samlByOrgId = (parent: RootDataLoader) => { +export const samlByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('saml') return new NullableDataLoader( async (orgIds) => { const pg = getKysely() @@ -728,7 +733,7 @@ export const samlByOrgId = (parent: RootDataLoader) => { .selectAll('SAML') .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) .execute() - return orgIds.map((orgId) => res.find((row) => row.orgId === orgId)) + return normalizeResults(orgIds, res) }, { ...parent.dataLoaderOptions @@ -737,7 +742,8 @@ export const samlByOrgId = (parent: RootDataLoader) => { } // Check if the org has a founder or billing lead with a verified email and their email domain is the same as the org domain -export const isOrgVerified = (parent: RootDataLoader) => { +export const isOrgVerified = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('organizationUsers') return new DataLoader( async (orgIds) => { const orgUsersRes = await parent.get('organizationUsersByOrgId').loadMany(orgIds) @@ -766,7 +772,8 @@ export const isOrgVerified = (parent: RootDataLoader) => { ) } -export const autoJoinTeamsByOrgId = (parent: RootDataLoader) => { +export const autoJoinTeamsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('teams') return new DataLoader( async (orgIds) => { const verificationResults = await parent.get('isOrgVerified').loadMany(orgIds) @@ -811,7 +818,8 @@ export const isCompanyDomain = (parent: RootDataLoader) => { ) } -export const favoriteTemplateIds = (parent: RootDataLoader) => { +export const favoriteTemplateIds = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('users') return new DataLoader( async (userIds) => { const pg = getKysely() @@ -859,7 +867,8 @@ export const fileStoreAsset = (parent: RootDataLoader) => { ) } -export const meetingCount = (parent: RootDataLoader) => { +export const meetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('selectTeams') return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( async (keys) => { const r = await getRethink() diff --git a/packages/server/dataloader/foreignKeyLoaderMaker.ts b/packages/server/dataloader/foreignKeyLoaderMaker.ts index 19c91cadfd1..88594e3d8e2 100644 --- a/packages/server/dataloader/foreignKeyLoaderMaker.ts +++ b/packages/server/dataloader/foreignKeyLoaderMaker.ts @@ -1,5 +1,5 @@ import DataLoader from 'dataloader' -import RootDataLoader from './RootDataLoader' +import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import UpdatableCacheDataLoader from './UpdatableCacheDataLoader' import * as primaryKeyLoaderMakers from './primaryKeyLoaderMakers' @@ -18,19 +18,17 @@ type LoaderType = */ export function foreignKeyLoaderMaker< LoaderName extends LoaderKeys, - KeyName extends keyof LoaderType + T extends LoaderType, + KeyName extends keyof T >( primaryLoaderKey: LoaderName, foreignKey: KeyName, - fetchFn: ( - keys: readonly LoaderType[KeyName][] - ) => Promise<(LoaderType & {id: string | number})[]> + fetchFn: (keys: readonly T[KeyName][]) => Promise<(T & {id?: string | number})[]> ) { - type T = LoaderType - type PrimaryKeyT = string | number type KeyValue = T[KeyName] - return (parent: RootDataLoader) => { - const primaryLoader = parent.get(primaryLoaderKey) as DataLoader + return (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn(primaryLoaderKey) + const primaryLoader = parent.get(primaryLoaderKey) as DataLoader return new UpdatableCacheDataLoader( async (ids) => { const items = await fetchFn(ids) diff --git a/packages/server/dataloader/normalizeResults.ts b/packages/server/dataloader/normalizeResults.ts index 4824d090d9c..5981b95ecb0 100644 --- a/packages/server/dataloader/normalizeResults.ts +++ b/packages/server/dataloader/normalizeResults.ts @@ -7,11 +7,7 @@ export const normalizeResults = { map[result[key]] = result }) - const mappedResults = [] as T[] - keys.forEach((key) => { - mappedResults.push(map[key]) - }) - return mappedResults + return keys.map((key) => map[key] as T) } export default normalizeResults diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index dd10a3f0d75..985a06ba429 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -131,3 +131,14 @@ export const selectOrganizations = () => export const organizations = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectOrganizations().where('id', 'in', ids).execute() }) + +export const saml = primaryKeyLoaderMaker((ids: readonly string[]) => { + return getKysely() + .selectFrom('SAMLDomain') + .innerJoin('SAML', 'SAML.id', 'SAMLDomain.samlId') + .where('SAML.id', 'in', ids) + .groupBy('SAML.id') + .selectAll('SAML') + .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) + .execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoader.ts b/packages/server/dataloader/rethinkForeignKeyLoader.ts index a9b61cd888a..147c868d2d2 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoader.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoader.ts @@ -15,7 +15,7 @@ const rethinkForeignKeyLoader = ( }) return ids.map((id) => items.filter((item) => item[field] === id)) } - return new UpdatableCacheDataLoader(batchFn, options) + return new UpdatableCacheDataLoader(batchFn, options) } export default rethinkForeignKeyLoader diff --git a/packages/server/graphql/DataLoaderCache.ts b/packages/server/graphql/DataLoaderCache.ts index 458cbfd8e1e..184fbb745dc 100644 --- a/packages/server/graphql/DataLoaderCache.ts +++ b/packages/server/graphql/DataLoaderCache.ts @@ -1,41 +1,19 @@ -import getLoaderNameByTable from '../dataloader/getLoaderNameByTable' -import {Loaders, TypedDataLoader} from '../dataloader/RootDataLoader' - -export interface DataLoaderBase { - get: (loaderName: any) => unknown - loaders: { - [key: string]: { - clear(id: string): void - } - } -} - -export class CacheWorker { - cache: DataLoaderCache - dataLoaderBase: T +export class CacheWorker void; get: (id: any) => any}> { + cache: DataLoaderCache + dataLoaderWorker: T did: string disposeId: NodeJS.Timeout | undefined shared = false - - constructor(dataLoaderBase: T, did: string, cache: DataLoaderCache) { - this.dataLoaderBase = dataLoaderBase + get: T['get'] + clearAll: T['clearAll'] + constructor(dataLoaderWorker: T, did: string, cache: DataLoaderCache) { + this.dataLoaderWorker = dataLoaderWorker this.did = did this.cache = cache + this.get = this.dataLoaderWorker.get + this.clearAll = this.dataLoaderWorker.clearAll } - get = (dataLoaderName: LoaderName) => { - // Using Loaders here breaks the abstraction. - // However, when using T['get'], typescript control flow analysis breaks - // e.g. loader.load() // null | any[], but doesn't catch the null! - // I'm OK with breaking the abstraction because we only have 1 RootDataLoader - // Given more time, could probably use a refactor here - return this.dataLoaderBase.get(dataLoaderName) as TypedDataLoader - } - - clear = (table: string, id: string) => { - const loaderName = getLoaderNameByTable(table) - this.dataLoaderBase.loaders[loaderName]?.clear(id) - } dispose(force?: boolean) { const ttl = force || !this.shared ? 0 : this.cache.ttl clearTimeout(this.disposeId!) @@ -53,16 +31,21 @@ export class CacheWorker { /** * A cache of dataloaders, see {@link getDataLoader} for usage */ -export default class DataLoaderCache { +export default class DataLoaderCache< + T extends new (...args: any) => any = new (...args: any) => any +> { ttl: number - workers: {[did: string]: CacheWorker} = {} + workers: {[did: string]: CacheWorker>} = {} nextId = 0 - constructor({ttl} = {ttl: 500}) { + DataLoaderWorkerConstructor: T + constructor(DataLoaderWorkerConstructor: T, {ttl} = {ttl: 500}) { + this.DataLoaderWorkerConstructor = DataLoaderWorkerConstructor this.ttl = ttl } - add(did: string, dataLoaderBase: T) { - this.workers[did] = new CacheWorker(dataLoaderBase, did, this) + add(did: string) { + const dataLoaderWorker = new this.DataLoaderWorkerConstructor() + this.workers[did] = new CacheWorker(dataLoaderWorker, did, this) return this.workers[did]! } diff --git a/packages/server/graphql/getDataLoader.ts b/packages/server/graphql/getDataLoader.ts index 8066c9a1f50..a796ecb9ffb 100644 --- a/packages/server/graphql/getDataLoader.ts +++ b/packages/server/graphql/getDataLoader.ts @@ -3,7 +3,7 @@ import numToBase64 from '../utils/numToBase64' import DataLoaderCache from './DataLoaderCache' import getNodeId from './getNodeId' -const dataLoaderCache = new DataLoaderCache() +const dataLoaderCache = new DataLoaderCache(RootDataLoader) const NODE_ID = getNodeId() let nextId = 0 @@ -18,7 +18,7 @@ const getDataLoader = (did?: string) => { // if the viewer is logged in, give them their own dataloader that they can quickly reuse for subsequent requests or share with others // if not logged in, just make a new anonymous loader. const id = did || `${NODE_ID}:${numToBase64(nextId++)}` - return dataLoaderCache.use(id) || dataLoaderCache.add(id, new RootDataLoader()) + return dataLoaderCache.use(id) || dataLoaderCache.add(id) } export default getDataLoader