Skip to content

Commit 70b5917

Browse files
thomasballingerConvex, Inc.
authored andcommitted
Optimize ApiFromModules type performance (#42762)
Thanks to David Blass working with us on this! GitOrigin-RevId: df40fd785fd88e79fd1bf2bcc4cca150d42e10f7
1 parent 378b362 commit 70b5917

File tree

1 file changed

+62
-29
lines changed
  • npm-packages/convex/src/server

1 file changed

+62
-29
lines changed

npm-packages/convex/src/server/api.ts

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
RegisteredMutation,
77
RegisteredQuery,
88
} from "./registration.js";
9-
import { Expand, UnionToIntersection } from "../type_utils.js";
9+
import type { UnionToIntersection } from "../type_utils.js";
1010
import { PaginationOptions, PaginationResult } from "./pagination.js";
1111
import { functionName } from "./functionName.js";
1212
import { getFunctionAddress } from "./components/paths.js";
@@ -226,7 +226,7 @@ type FunctionReferencesInModule<Module extends Record<string, any>> = {
226226
-readonly [ExportName in keyof Module as Module[ExportName]["isConvexFunction"] extends true
227227
? ExportName
228228
: never]: FunctionReferenceFromExport<Module[ExportName]>;
229-
};
229+
} & unknown;
230230

231231
/**
232232
* Given a path to a module and it's type, generate an API type for this module.
@@ -236,11 +236,17 @@ type FunctionReferencesInModule<Module extends Record<string, any>> = {
236236
type ApiForModule<
237237
ModulePath extends string,
238238
Module extends object,
239-
> = ModulePath extends `${infer First}/${infer Second}`
240-
? {
241-
[_ in First]: ApiForModule<Second, Module>;
242-
}
243-
: { [_ in ModulePath]: FunctionReferencesInModule<Module> };
239+
LastPathWasSegmented extends boolean,
240+
> =
241+
ModulePath extends SegmentedPath<infer First, infer Rest>
242+
? {
243+
[_ in First]: ApiForModule<Rest, Module, true>;
244+
}
245+
: LastPathWasSegmented extends true
246+
? {
247+
[_ in ModulePath]: FunctionReferencesInModule<Module>;
248+
}
249+
: FunctionReferencesInModule<Module>;
244250

245251
/**
246252
* Given the types of all modules in the `convex/` directory, construct the type
@@ -253,40 +259,61 @@ type ApiForModule<
253259
* @public
254260
*/
255261
export type ApiFromModules<AllModules extends Record<string, object>> =
256-
FilterApi<
257-
ApiFromModulesAllowEmptyNodes<AllModules>,
258-
FunctionReference<any, any, any, any>
259-
>;
262+
keyof AllModules & SegmentedPath extends never
263+
? ApiFromNonSegmentedModules<AllModules>
264+
: ApiFromSegmentedModules<AllModules>;
265+
266+
// fast path for cases with no segmented modules
267+
type ApiFromNonSegmentedModules<AllModules extends Record<string, object>> = {
268+
[ModulePath in keyof AllModules as keyof FunctionReferencesInModule<
269+
AllModules[ModulePath]
270+
> extends never
271+
? never
272+
: ModulePath]: FunctionReferencesInModule<AllModules[ModulePath]>;
273+
};
260274

261-
type ApiFromModulesAllowEmptyNodes<AllModules extends Record<string, object>> =
275+
type ApiFromSegmentedModules<AllModules extends Record<string, object>> =
262276
ExpandModulesAndDirs<
263-
UnionToIntersection<
264-
{
265-
[ModulePath in keyof AllModules]: ApiForModule<
266-
ModulePath & string,
267-
AllModules[ModulePath]
268-
>;
269-
}[keyof AllModules]
270-
>
277+
IntersectUnionProperties<{
278+
[ModulePath in keyof AllModules as FirstSegment<ModulePath>]: ModulePath extends SegmentedPath<
279+
string,
280+
infer Rest
281+
>
282+
? ApiForModule<Rest, AllModules[ModulePath], true>
283+
: ApiForModule<ModulePath & string, AllModules[ModulePath], false>;
284+
}>
271285
>;
272286

287+
type IntersectUnionProperties<O extends object> = {
288+
[K in keyof O]: UnionToIntersection<O[K]>;
289+
};
290+
291+
type SegmentedPath<
292+
First extends string = string,
293+
Rest extends string = string,
294+
> = `${First}/${Rest}`;
295+
296+
type FirstSegment<ModulePath> =
297+
ModulePath extends SegmentedPath<infer First> ? First : ModulePath;
298+
273299
/**
274300
* @public
275301
*
276302
* Filter a Convex deployment api object for functions which meet criteria,
277303
* for example all public queries.
278304
*/
279-
export type FilterApi<API, Predicate> = Expand<{
305+
export type FilterApi<API, Predicate> = {
280306
[mod in keyof API as API[mod] extends Predicate
281307
? mod
282-
: API[mod] extends FunctionReference<any, any, any, any>
308+
: API[mod] extends AnyFunctionReference
283309
? never
284-
: FilterApi<API[mod], Predicate> extends Record<string, never>
310+
: {} extends FilterApi<API[mod], Predicate>
285311
? never
286312
: mod]: API[mod] extends Predicate
287313
? API[mod]
288314
: FilterApi<API[mod], Predicate>;
289-
}>;
315+
// equivalent to Expand without requiring another mapped type
316+
} & unknown;
290317

291318
/**
292319
* Given an api of type API and a FunctionReference subtype, return an api object
@@ -365,12 +392,18 @@ export function justSchedulable<API>(
365392
* The differences are:
366393
* 1. This version is recursive.
367394
* 2. This stops recursing when it hits a {@link FunctionReference}.
395+
* 3. This omits empty object properties
368396
*/
369-
type ExpandModulesAndDirs<ObjectType> = ObjectType extends AnyFunctionReference
370-
? ObjectType
371-
: {
372-
[Key in keyof ObjectType]: ExpandModulesAndDirs<ObjectType[Key]>;
373-
};
397+
type ExpandModulesAndDirs<ObjectType> =
398+
ObjectType extends Record<string, AnyFunctionReference>
399+
? ObjectType
400+
: {
401+
[Key in keyof ObjectType as keyof ExpandModulesAndDirs<
402+
ObjectType[Key]
403+
> extends never
404+
? never
405+
: Key]: ExpandModulesAndDirs<ObjectType[Key]>;
406+
};
374407

375408
/**
376409
* A {@link FunctionReference} of any type and any visibility with any

0 commit comments

Comments
 (0)