Skip to content

Commit f0f7304

Browse files
committed
feat(code-gen,store): dynamically resolve query builder results based on the passed in builder
This only works in TypeScript projects.
1 parent 0b5a6b6 commit f0f7304

File tree

17 files changed

+668
-53
lines changed

17 files changed

+668
-53
lines changed

packages/cli/src/generated/common/types.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Generated by @compas/code-gen
22

33

4+
45
export type CliFlagDefinition = {
56
"name": string;
67
"rawName": string;
@@ -94,5 +95,3 @@ export type CliCompletion =
9495
"specification": "boolean"|"number"|"string"|"booleanOrString";
9596
"description"?: string|undefined;
9697
};
97-
98-

packages/code-gen/package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@
1212
"./package.json": "./package.json"
1313
},
1414
"type": "module",
15-
"keywords": [
16-
"compas",
17-
"generate",
18-
"router",
19-
"validation",
20-
"code-gen"
21-
],
15+
"keywords": ["compas", "generate", "router", "validation", "code-gen"],
2216
"license": "MIT",
2317
"dependencies": {
2418
"@compas/stdlib": "0.15.2"

packages/code-gen/src/database/ts-postgres.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export function tsPostgresGenerateUtils(generateContext) {
5656
helperImportCollector.destructure("@compas/stdlib", "AppError");
5757
helperTypeImportCollector.destructure("@compas/store", "QueryPart");
5858
helperTypeImportCollector.destructure("@compas/store", "WrappedQueryPart");
59+
helperTypeImportCollector.destructure("@compas/store", "WrappedQueryResult");
5960

6061
fileWrite(
6162
helperFile,
@@ -156,7 +157,10 @@ export function tsPostgresCreateFile(generateContext, model) {
156157
importCollector.destructure("@compas/stdlib", "isNil");
157158
importCollector.destructure("@compas/stdlib", "AppError");
158159

160+
typeImportCollector.destructure("@compas/store", "Postgres");
161+
typeImportCollector.destructure("@compas/store", "QueryPart");
159162
typeImportCollector.destructure("@compas/store", "WrappedQueryPart");
163+
typeImportCollector.destructure("@compas/store", "WrappedQueryResult");
160164

161165
fileWrite(file, `\nexport const ${model.name}Queries = {`);
162166
fileContextSetIndent(file, 1);
@@ -1119,6 +1123,9 @@ export function tsPostgresGenerateQueryBuilder(
11191123
entityInformation: `$$(): any => ${relationInfo.modelInverse.name}QueryBuilderSpec$$`,
11201124
});
11211125
}
1126+
1127+
const fullTypeName = `${upperCaseFirst(model.group)}${upperCaseFirst(model.name)}`;
1128+
11221129
for (const relation of inverseRelations) {
11231130
const relationInfo = modelRelationGetInformation(relation);
11241131

@@ -1165,7 +1172,7 @@ export function tsPostgresGenerateQueryBuilder(
11651172
// Function
11661173
fileBlockStart(
11671174
file,
1168-
`export function query${upperCaseFirst(model.name)}(input: ${contextNames.queryBuilderType.inputType} = {}): WrappedQueryPart<${contextNames.queryResultType.outputType}>`,
1175+
`export function query${upperCaseFirst(model.name)}<QueryBuilder extends ${contextNames.queryBuilderType.inputType}>(input: QueryBuilder = {}): WrappedQueryResult<${fullTypeName}QueryResolver<QueryBuilder>>`,
11691176
);
11701177

11711178
// Input validation

packages/code-gen/src/generate.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from "./processors/model-partials.js";
2525
import {
2626
modelQueryBuilderTypes,
27+
modelQueryRawTypes,
2728
modelQueryResultTypes,
2829
} from "./processors/model-query.js";
2930
import {
@@ -153,6 +154,7 @@ export function generateExecute(generator, options) {
153154
typesGeneratorInit(generateContext);
154155

155156
databaseGenerator(generateContext);
157+
modelQueryRawTypes(generateContext);
156158

157159
routerGenerator(generateContext);
158160
apiClientGenerator(generateContext);

packages/code-gen/src/generated/common/types.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Generated by @compas/code-gen
22

33

4+
45
export type StructureAnyDefinitionTarget = "js"|"ts"|"jsKoaReceive"|"tsKoaReceive"|"jsKoaSend"|"tsKoaSend"|"jsPostgres"|"tsPostgres"|"jsAxios"|"tsAxios"|"jsAxiosNode"|"tsAxiosBrowser"|"tsAxiosReactNative"|"jsFetch"|"tsFetch"|"jsFetchNode"|"tsFetchBrowser"|"tsFetchReactNative";
56

67
export type StructureAnyDefinition = {
@@ -1388,5 +1389,3 @@ export type StructureTypeDefinitionInput =
13881389
|StructureReferenceDefinitionInput
13891390
|StructureRelationDefinitionInput
13901391
|StructureRouteInvalidationDefinitionInput;
1391-
1392-

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

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import {
22
AnyOfType,
3+
AnyType,
34
ArrayType,
45
NumberType,
56
ObjectType,
67
ReferenceType,
78
} from "../builders/index.js";
9+
import { databaseIsEnabled } from "../database/generator.js";
10+
import { fileWriteRaw } from "../file/write.js";
11+
import { TypescriptImportCollector } from "../target/typescript.js";
12+
import { typesTypescriptResolveFile } from "../types/typescript.js";
813
import { upperCaseFirst } from "../utils.js";
914
import {
1015
modelRelationGetInformation,
@@ -79,6 +84,13 @@ export function modelQueryBuilderTypes(generateContext) {
7984
*/
8085
export function modelQueryResultTypes(generateContext) {
8186
for (const model of structureModels(generateContext)) {
87+
const expansionType = new ObjectType(
88+
"queryExpansion",
89+
model.group + upperCaseFirst(model.name),
90+
)
91+
.keys({})
92+
.build();
93+
8294
const type = new ObjectType(
8395
"queryResult",
8496
model.group + upperCaseFirst(model.name),
@@ -99,6 +111,7 @@ export function modelQueryResultTypes(generateContext) {
99111
type.keys[relationInfo.keyNameOwn],
100112
["isOptional"],
101113
);
114+
102115
const joinedType = new ReferenceType(
103116
"queryResult",
104117
`${relationInfo.modelInverse.group}${upperCaseFirst(
@@ -110,9 +123,19 @@ export function modelQueryResultTypes(generateContext) {
110123
if (isOptional) {
111124
anyOfType.optional();
112125
}
113-
type.keys[relationInfo.keyNameOwn] = anyOfType.build();
114126

127+
type.keys[relationInfo.keyNameOwn] = anyOfType.build();
115128
type.keys[relationInfo.keyNameOwn].values = [existingType, joinedType];
129+
130+
const joinedExpansionType = getQueryDefinitionReference(
131+
relationInfo.modelInverse.group,
132+
relationInfo.modelInverse.name,
133+
);
134+
if (isOptional) {
135+
joinedExpansionType.optional();
136+
}
137+
138+
expansionType.keys[relationInfo.keyNameOwn] = joinedExpansionType.build();
116139
}
117140

118141
for (const relation of modelRelationGetInverse(model)) {
@@ -136,10 +159,97 @@ export function modelQueryResultTypes(generateContext) {
136159
type.keys[relationInfo.virtualKeyNameInverse] = joinedType
137160
.optional()
138161
.build();
162+
163+
const joinedExpansionType =
164+
relation.subType === "oneToMany" ?
165+
new ArrayType().values(
166+
getQueryDefinitionReference(
167+
relationInfo.modelOwn.group,
168+
relationInfo.modelOwn.name,
169+
),
170+
)
171+
: getQueryDefinitionReference(
172+
relationInfo.modelOwn.group,
173+
relationInfo.modelOwn.name,
174+
);
175+
176+
expansionType.keys[relationInfo.virtualKeyNameInverse] =
177+
joinedExpansionType.optional().build();
139178
}
140179

141180
structureAddType(generateContext.structure, type, {
142181
skipReferenceExtraction: true,
143182
});
183+
structureAddType(generateContext.structure, expansionType, {
184+
skipReferenceExtraction: true,
185+
});
186+
}
187+
}
188+
189+
function getQueryDefinitionReference(group, name) {
190+
const resolvedName = `${upperCaseFirst(group)}${upperCaseFirst(name)}`;
191+
192+
const implementation = {
193+
validatorInputType: `QueryDefinition${resolvedName}`,
194+
validatorOutputType: `QueryDefinition${resolvedName}`,
195+
};
196+
return new AnyType().implementations({
197+
js: implementation,
198+
ts: implementation,
199+
jsPostgres: implementation,
200+
tsPostgres: implementation,
201+
});
202+
}
203+
204+
/**
205+
* Add raw types related to models and query builders
206+
*
207+
* @param {import("../generate.js").GenerateContext} generateContext
208+
* @returns {void}
209+
*/
210+
export function modelQueryRawTypes(generateContext) {
211+
if (!databaseIsEnabled(generateContext)) {
212+
return;
213+
}
214+
215+
const file = typesTypescriptResolveFile(generateContext);
216+
217+
if (generateContext.options.targetLanguage === "ts") {
218+
const typeImports = TypescriptImportCollector.getImportCollector(
219+
file,
220+
true,
221+
);
222+
typeImports.destructure("@compas/store", "QueryBuilderResolver");
223+
typeImports.destructure("@compas/store", "QueryBuilderDefinition");
224+
typeImports.destructure("@compas/store", "ResolveOptionalJoins");
225+
}
226+
227+
const exportPrefix =
228+
generateContext.options.generators.types?.declareGlobalTypes ?
229+
""
230+
: "export";
231+
232+
for (const model of structureModels(generateContext)) {
233+
const name = `${upperCaseFirst(model.group)}${upperCaseFirst(model.name)}`;
234+
235+
if (generateContext.options.targetLanguage === "ts") {
236+
fileWriteRaw(
237+
file,
238+
`${exportPrefix} type QueryDefinition${name} = QueryBuilderDefinition<${name}, QueryExpansion${name}>;\n`,
239+
);
240+
fileWriteRaw(
241+
file,
242+
`${exportPrefix} type ${name}QueryResolver<QueryBuilder extends ${name}QueryBuilderInput, const OptionalJoins extends ResolveOptionalJoins<QueryExpansion${name}> = never> = QueryBuilderResolver<QueryDefinition${name}, QueryBuilder, OptionalJoins>;\n\n`,
243+
);
244+
} else if (generateContext.options.targetLanguage === "js") {
245+
fileWriteRaw(
246+
file,
247+
`${exportPrefix} type QueryDefinition${name} = import("@compas/store").QueryBuilderDefinition<${name}, QueryExpansion${name}>;\n`,
248+
);
249+
fileWriteRaw(
250+
file,
251+
`${exportPrefix} type ${name}QueryResolver<QueryBuilder extends ${name}QueryBuilderInput, const OptionalJoins extends import("@compas/store").ResolveOptionalJoins<QueryExpansion${name}> = never> = import("@compas/store").QueryBuilderResolver<QueryDefinition${name}, QueryBuilder, OptionalJoins>;\n\n`,
252+
);
253+
}
144254
}
145255
}

packages/code-gen/src/types/typescript.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export function typesTypescriptResolveFile(generateContext) {
4545
export function typesTypescriptInitFile(generateContext) {
4646
return fileContextCreateGeneric(generateContext, "common/types.d.ts", {
4747
importCollector: new TypescriptImportCollector(),
48+
typeImportCollector: new TypescriptImportCollector(true),
4849
});
4950
}
5051

packages/code-gen/src/validators/javascript.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ export function validatorJavascriptGetFile(generateContext, type) {
121121
/**
122122
* @typedef {Record<string, any|undefined>} ValidatorErrorMap
123123
*/
124+
124125
126+
// eslint-disable-next-line unused-imports/no-unused-vars
125127
const isRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
126128
`,
127129
);

packages/code-gen/src/validators/typescript.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type Either<T, E> = { value: T; error?: never }|{ value?: never; error: E };
6262
6363
type ValidatorErrorMap = Record<string, any|undefined>;
6464
65+
// eslint-disable-next-line unused-imports/no-unused-vars
6566
const isRecord = (v: unknown): v is Record<string, any> => !!v && typeof v === "object" && !Array.isArray(v);
6667
`,
6768
);

packages/create-compas/package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
"bin": {
1010
"create-compas": "src/create-compas.js"
1111
},
12-
"keywords": [
13-
"compas",
14-
"create"
15-
],
12+
"keywords": ["compas", "create"],
1613
"license": "MIT",
1714
"dependencies": {
1815
"@compas/stdlib": "0.15.2",

0 commit comments

Comments
 (0)