Skip to content

Commit 55ed0cf

Browse files
committed
feat(code-gen): inline query builder types
This seems to improve things a bit by skipping the generated .d.ts files from the JSDoc reexports in store/index.js
1 parent 284aa4c commit 55ed0cf

1 file changed

Lines changed: 230 additions & 6 deletions

File tree

packages/code-gen/src/processors/model-query.js

Lines changed: 230 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from "../builders/index.js";
99
import { databaseIsEnabled } from "../database/generator.js";
1010
import { fileWriteRaw } from "../file/write.js";
11-
import { TypescriptImportCollector } from "../target/typescript.js";
1211
import { typesTypescriptResolveFile } from "../types/typescript.js";
1312
import { upperCaseFirst } from "../utils.js";
1413
import {
@@ -215,13 +214,238 @@ export function modelQueryRawTypes(generateContext) {
215214
const file = typesTypescriptResolveFile(generateContext);
216215

217216
if (generateContext.options.targetLanguage === "ts") {
218-
const typeImports = TypescriptImportCollector.getImportCollector(
217+
fileWriteRaw(
219218
file,
220-
true,
219+
`
220+
/// ============================
221+
/// Query builder resolver types
222+
223+
/**
224+
* Utility type to resolve the full type instead of showing things like \`Omit<Type,
225+
* SomeNesting<...>> & ...\` in the type popups and errors.
226+
*/
227+
type _ResolveType<T> = { [K in keyof T]: T[K] } & {};
228+
229+
/**
230+
* Utility type to resolve the base + expansion of an entity.
231+
*/
232+
export type QueryBuilderDefinition<Base, Expansion> = {
233+
base: Base;
234+
expansion: Expansion;
235+
};
236+
237+
type PickKeysThatExtend<T, Select> = {
238+
[K in keyof T as T[K] extends Select ? K : never]: T[K];
239+
};
240+
241+
type OmitKeysThatExtend<T, Select> = {
242+
[K in keyof T as T[K] extends Select ? never : K]: T[K];
243+
};
244+
245+
/**
246+
* Omit never values
247+
*/
248+
type OmitNever<T> = OmitKeysThatExtend<T, never>;
249+
250+
/**
251+
* Apply Partial if the type can be undefined.
252+
*/
253+
type ConvertUndefinedToPartial<T> = {
254+
[K in keyof T as undefined extends T[K] ? K : never]?: T[K];
255+
} & {
256+
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
257+
};
258+
259+
type QueryBuilderSpecialKeys =
260+
| "offset"
261+
| "limit"
262+
| "orderBy"
263+
| "orderBySpec"
264+
| "select"
265+
| "where";
266+
267+
/**
268+
* Max value for which optional joins are resolved.
269+
*/
270+
type ResolveJoinDepth = 4;
271+
272+
type InferExpansionForOptionalJoins<Expansion> = Exclude<Expansion extends {
273+
expansion: infer Result;
274+
}
275+
? Result
276+
: Expansion extends Array<{ expansion: infer Result }>
277+
? Result
278+
: Expansion, undefined>;
279+
280+
/**
281+
* Provided an QueryBuilder expansion object, determines the union of all possible joins up
282+
* until {@link ResolveJoinDepth} depth.
283+
*/
284+
export type ResolveOptionalJoins<
285+
Expansion,
286+
Prefix extends string = "",
287+
Depth extends Array<unknown> = [],
288+
ResolvedExpansion = InferExpansionForOptionalJoins<Expansion>,
289+
> = Depth["length"] extends ResolveJoinDepth
290+
? never
291+
: {
292+
[K in keyof ResolvedExpansion]: K extends string
293+
? Prefix extends "" // Base case
294+
?
295+
| \`$\{K}\`
296+
| ResolveOptionalJoins<
297+
ResolvedExpansion[K],
298+
\`$\{K}\`,
299+
[unknown, ...Depth]
300+
>
301+
: // Nested case
302+
| \`$\{Prefix}.$\{K}\` // Recursive into other expansions.
303+
| ResolveOptionalJoins<
304+
ResolvedExpansion[K],
305+
\`$\{Prefix}.$\{K}\`,
306+
[unknown, ...Depth]
307+
>
308+
: never;
309+
}[keyof ResolvedExpansion];
310+
311+
/**
312+
* Split the input string on the first '.'-char.
313+
*/
314+
type SplitDot<Input extends string> = Input extends \`$\{infer Start}.$\{string}\`
315+
? Start
316+
: never;
317+
318+
/**
319+
* Check if the provided key is in one of the optional joins. This is also true when the key
320+
* is a prefix of a join. i.e \`settings\` is optional if \`settings.user\` is an optional join.
321+
*/
322+
type IsOptionalJoin<
323+
Key extends string,
324+
Joins extends string,
325+
> = Key extends Joins ? true : Key extends SplitDot<Joins> ? true : false;
326+
327+
/**
328+
* Filters and strips the Joins that start with Prefix.
329+
*/
330+
type FilterOptionalJoins<Joins extends string, Prefix extends string> = {
331+
[K in Joins]: K extends \`$\{Prefix}\`
332+
? never
333+
: K extends \`$\{Prefix}.$\{infer Suffix}\`
334+
? Suffix
335+
: never;
336+
}[Joins];
337+
338+
/**
339+
* Pick the selected fields from the Type.
340+
* If no select field exists on the builder, or if "*" is supplied, the full Type is returned.
341+
*/
342+
type PickSelected<Type, SelectBuilder> = SelectBuilder extends {
343+
select: "*" | Array<string>;
344+
}
345+
? SelectBuilder["select"] extends "*" // Select all fields
346+
? Type
347+
: SelectBuilder["select"] extends Array<infer K extends keyof Type> // Only select the fields
348+
? // that have been selected.
349+
Pick<Type, K>
350+
: never // Defaults to selecting all fields.
351+
: Type;
352+
353+
type ResolveBaseResult<
354+
Base,
355+
QueryBuilder,
356+
OptionalJoins extends string,
357+
> = OmitNever<
358+
ConvertUndefinedToPartial<
359+
PickSelected<
360+
Omit<
361+
Base,
362+
Exclude<keyof QueryBuilder, QueryBuilderSpecialKeys> | OptionalJoins
363+
>,
364+
QueryBuilder
365+
>
366+
>
367+
>;
368+
369+
type ResolveTypeFromExpansion<
370+
DefinitionType,
371+
QueryBuilder,
372+
OptionalJoins extends string,
373+
> =
374+
DefinitionType extends Array<infer SingleDefinition>
375+
? Array<QueryBuilderResolver<SingleDefinition, QueryBuilder, OptionalJoins>>
376+
: QueryBuilderResolver<DefinitionType, QueryBuilder, OptionalJoins>;
377+
378+
type ResolveExpansionKey<
379+
K extends keyof Expansion & string,
380+
Base,
381+
Expansion,
382+
QueryBuilder,
383+
OptionalJoins extends string,
384+
> = K extends keyof QueryBuilder
385+
? ResolveTypeFromExpansion<
386+
Expansion[K],
387+
QueryBuilder[K],
388+
FilterOptionalJoins<OptionalJoins, K>
389+
>
390+
: IsOptionalJoin<K, OptionalJoins> extends true
391+
? K extends keyof Base
392+
? // We need to include the base type if it exists for owning sides of relations.
393+
| ResolveTypeFromExpansion<
394+
Expansion[K],
395+
unknown,
396+
FilterOptionalJoins<OptionalJoins, K>
397+
>
398+
| Base[K]
399+
| undefined
400+
:
401+
| ResolveTypeFromExpansion<
402+
Expansion[K],
403+
unknown,
404+
FilterOptionalJoins<OptionalJoins, K>
405+
>
406+
| undefined
407+
: K extends keyof Base
408+
? Base[K]
409+
: never;
410+
411+
/**
412+
* Provided a Definition and a QueryBuilder, resolves the return type.
413+
*
414+
* For usage of this type in function parameter definitions, OptionalJoins can be supplied.
415+
*/
416+
export type QueryBuilderResolver<
417+
DefinitionType,
418+
QueryBuilder,
419+
OptionalJoins extends string = "",
420+
> =
421+
DefinitionType extends QueryBuilderDefinition<infer Base, infer Expansion>
422+
? _ResolveType<
423+
ResolveBaseResult<Base, QueryBuilder, OptionalJoins> &
424+
OmitNever<
425+
ConvertUndefinedToPartial<
426+
_ResolveType<{
427+
[K in Exclude<
428+
Exclude<keyof Expansion, QueryBuilderSpecialKeys>,
429+
number | symbol
430+
>]: _ResolveType<
431+
ResolveExpansionKey<
432+
K,
433+
Base,
434+
Expansion,
435+
QueryBuilder,
436+
OptionalJoins
437+
>
438+
>;
439+
}>
440+
>
441+
>
442+
>
443+
: never;
444+
445+
/// End Query builder resolver types
446+
/// ================================
447+
`,
221448
);
222-
typeImports.destructure("@compas/store", "QueryBuilderResolver");
223-
typeImports.destructure("@compas/store", "QueryBuilderDefinition");
224-
typeImports.destructure("@compas/store", "ResolveOptionalJoins");
225449
}
226450

227451
const exportPrefix =

0 commit comments

Comments
 (0)