bunsane 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TODO.md +1 -1
- package/bun.lock +156 -150
- package/core/App.ts +188 -31
- package/core/ArcheType.ts +1044 -26
- package/core/ComponentRegistry.ts +172 -29
- package/core/Components.ts +102 -24
- package/core/Decorators.ts +0 -1
- package/core/Entity.ts +55 -7
- package/core/EntityInterface.ts +4 -0
- package/core/EntityManager.ts +4 -4
- package/core/Query.ts +169 -3
- package/core/RequestLoaders.ts +101 -12
- package/core/SchedulerManager.ts +3 -4
- package/core/metadata/definitions/ArcheType.ts +9 -0
- package/core/metadata/definitions/Component.ts +16 -0
- package/core/metadata/definitions/gqlObject.ts +10 -0
- package/core/metadata/getMetadataStorage.ts +14 -0
- package/core/metadata/index.ts +17 -0
- package/core/metadata/metadata-storage.ts +81 -0
- package/database/DatabaseHelper.ts +22 -20
- package/database/sqlHelpers.ts +0 -2
- package/gql/ArchetypeOperations.ts +281 -0
- package/gql/Generator.ts +252 -62
- package/gql/helpers.ts +5 -5
- package/gql/index.ts +19 -17
- package/gql/types.ts +58 -11
- package/index.ts +93 -82
- package/package.json +39 -37
- package/plugins/index.ts +13 -0
- package/scheduler/index.ts +87 -0
- package/service/Service.ts +4 -0
- package/service/ServiceRegistry.ts +5 -1
- package/service/index.ts +1 -1
- package/swagger/decorators.ts +65 -0
- package/swagger/generator.ts +100 -0
- package/swagger/index.ts +2 -0
- package/tests/bench/insert.bench.ts +1 -0
- package/tests/bench/relations.bench.ts +1 -0
- package/tests/bench/sorting.bench.ts +1 -0
- package/tests/component-hooks-simple.test.ts +117 -0
- package/tests/component-hooks.test.ts +83 -31
- package/tests/component.test.ts +1 -0
- package/tests/hooks.test.ts +1 -0
- package/tests/query.test.ts +46 -4
- package/tests/relations.test.ts +1 -0
- package/types/app.types.ts +0 -0
- package/upload/index.ts +0 -2
- package/core/processors/ImageProcessor.ts +0 -423
package/gql/Generator.ts
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
import { GraphQLSchema, GraphQLError } from "graphql";
|
|
1
|
+
import { GraphQLSchema, GraphQLError, printSchema } from "graphql";
|
|
2
2
|
import { createSchema } from "graphql-yoga";
|
|
3
3
|
import { logger as MainLogger } from "core/Logger";
|
|
4
4
|
import type { GraphQLType } from "./helpers";
|
|
5
|
+
import { generateArchetypeOperations } from "./ArchetypeOperations";
|
|
6
|
+
import { getArchetypeSchema } from "../core/ArcheType";
|
|
7
|
+
import BaseArcheType from "../core/ArcheType";
|
|
8
|
+
import { getMetadataStorage } from "../core/metadata";
|
|
9
|
+
import type { BaseService } from "service";
|
|
10
|
+
import { type ZodType } from "zod";
|
|
11
|
+
import { ZodWeaver } from "@gqloom/zod";
|
|
12
|
+
import { weave } from "@gqloom/core";
|
|
13
|
+
import * as z from "zod";
|
|
14
|
+
|
|
5
15
|
const logger = MainLogger.child({ scope: "GraphQLGenerator" });
|
|
6
16
|
export interface GraphQLObjectTypeMeta {
|
|
7
17
|
name: string;
|
|
8
18
|
fields: Record<string, GraphQLType>;
|
|
9
19
|
}
|
|
10
20
|
|
|
11
|
-
export interface GraphQLOperationMeta {
|
|
21
|
+
export interface GraphQLOperationMeta<T extends BaseArcheType | BaseArcheType[] | string = string> {
|
|
12
22
|
type: "Query" | "Mutation";
|
|
23
|
+
propertyKey?: string;
|
|
13
24
|
name?: string;
|
|
14
|
-
input?: Record<string, GraphQLType
|
|
15
|
-
output: GraphQLType | Record<string, GraphQLType
|
|
25
|
+
input?: Record<string, GraphQLType> | any;
|
|
26
|
+
output: GraphQLType | Record<string, GraphQLType> | T;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export interface GraphQLFieldMeta {
|
|
@@ -20,8 +31,9 @@ export interface GraphQLFieldMeta {
|
|
|
20
31
|
field: string;
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
|
|
23
35
|
export function GraphQLObjectType(meta: GraphQLObjectTypeMeta) {
|
|
24
|
-
return (target:
|
|
36
|
+
return (target: BaseService) => {
|
|
25
37
|
if (!target.__graphqlObjectType) target.__graphqlObjectType = [];
|
|
26
38
|
target.__graphqlObjectType.push(meta);
|
|
27
39
|
}
|
|
@@ -34,14 +46,14 @@ export function GraphQLScalarType(name: string) {
|
|
|
34
46
|
}
|
|
35
47
|
}
|
|
36
48
|
|
|
37
|
-
export function GraphQLOperation(meta: GraphQLOperationMeta) {
|
|
38
|
-
return function (target:
|
|
49
|
+
export function GraphQLOperation<T extends BaseArcheType | BaseArcheType[] | string = string>(meta: GraphQLOperationMeta<T>) {
|
|
50
|
+
return function (target: BaseService, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
39
51
|
if (!target.__graphqlOperations) target.__graphqlOperations = [];
|
|
40
52
|
const operationName = meta.name ?? propertyKey;
|
|
41
53
|
if (!operationName) {
|
|
42
54
|
throw new Error("GraphQLOperation: Operation name is required (either meta.name or propertyKey must be defined)");
|
|
43
55
|
}
|
|
44
|
-
const operationMeta = { ...meta, name: operationName, propertyKey }
|
|
56
|
+
const operationMeta = { ...meta, name: operationName, propertyKey } as GraphQLOperationMeta<any>;
|
|
45
57
|
target.__graphqlOperations.push(operationMeta);
|
|
46
58
|
};
|
|
47
59
|
}
|
|
@@ -53,7 +65,55 @@ export function GraphQLField(meta: GraphQLFieldMeta) {
|
|
|
53
65
|
};
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Helper function to get the registered GraphQL type name from an archetype instance.
|
|
70
|
+
* This respects the custom name set via @ArcheType("CustomName") decorator.
|
|
71
|
+
* Falls back to inferring from class name if not found in registry.
|
|
72
|
+
*/
|
|
73
|
+
function getArchetypeTypeName(archetypeInstance: any): string | null {
|
|
74
|
+
if (!archetypeInstance || !(archetypeInstance instanceof BaseArcheType)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const storage = getMetadataStorage();
|
|
79
|
+
const className = archetypeInstance.constructor.name;
|
|
80
|
+
|
|
81
|
+
// Look up the archetype metadata by class name to get the custom name
|
|
82
|
+
const archetypeMetadata = storage.archetypes.find(a => a.target?.name === className);
|
|
83
|
+
|
|
84
|
+
if (archetypeMetadata?.name) {
|
|
85
|
+
// Use the custom name from @ArcheType("CustomName") decorator
|
|
86
|
+
logger.trace(`Found custom archetype name: ${archetypeMetadata.name} for class ${className}`);
|
|
87
|
+
|
|
88
|
+
// Ensure schema is generated and cached
|
|
89
|
+
try {
|
|
90
|
+
if (!getArchetypeSchema(archetypeMetadata.name)) {
|
|
91
|
+
archetypeInstance.getZodObjectSchema();
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
logger.warn(`Failed to generate schema for archetype ${archetypeMetadata.name}: ${error}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return archetypeMetadata.name;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fallback: infer from class name
|
|
101
|
+
const inferredName = className.replace(/ArcheType$/, '');
|
|
102
|
+
logger.trace(`Using inferred archetype name: ${inferredName} for class ${className}`);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
if (!getArchetypeSchema(inferredName)) {
|
|
106
|
+
archetypeInstance.getZodObjectSchema();
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
logger.warn(`Failed to generate schema for archetype ${inferredName}: ${error}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return inferredName;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function generateGraphQLSchema(services: any[], options?: { enableArchetypeOperations?: boolean }): { schema: GraphQLSchema | null; resolvers: any } {
|
|
116
|
+
logger.trace(`generateGraphQLSchema called with ${services.length} services`);
|
|
57
117
|
let typeDefs = `
|
|
58
118
|
`;
|
|
59
119
|
const scalarTypes: Set<string> = new Set();
|
|
@@ -61,8 +121,53 @@ export function generateGraphQLSchema(services: any[]): { schema: GraphQLSchema
|
|
|
61
121
|
const queryFields: string[] = [];
|
|
62
122
|
const mutationFields: string[] = [];
|
|
63
123
|
|
|
124
|
+
// PRE-GENERATE ALL ARCHETYPE SCHEMAS
|
|
125
|
+
// Scan all services for archetype instances and generate their schemas upfront
|
|
126
|
+
logger.trace(`Pre-generating archetype schemas from service operations...`);
|
|
127
|
+
services.forEach(service => {
|
|
128
|
+
const operations = service.__graphqlOperations || service.constructor.prototype.__graphqlOperations;
|
|
129
|
+
if (operations) {
|
|
130
|
+
operations.forEach((op: any) => {
|
|
131
|
+
const { output } = op;
|
|
132
|
+
// Check if output is an archetype or array of archetypes
|
|
133
|
+
if (Array.isArray(output) && output[0] instanceof BaseArcheType) {
|
|
134
|
+
const archetypeInstance = output[0];
|
|
135
|
+
getArchetypeTypeName(archetypeInstance); // This will cache the schema
|
|
136
|
+
} else if (output instanceof BaseArcheType) {
|
|
137
|
+
getArchetypeTypeName(output); // This will cache the schema
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
logger.trace(`Completed pre-generation of archetype schemas`);
|
|
143
|
+
|
|
144
|
+
// Generate archetype operations if enabled
|
|
145
|
+
if (options?.enableArchetypeOperations !== false) {
|
|
146
|
+
try {
|
|
147
|
+
const archetypeOps = generateArchetypeOperations();
|
|
148
|
+
typeDefs += archetypeOps.typeDefs;
|
|
149
|
+
queryFields.push(...archetypeOps.queryFields);
|
|
150
|
+
mutationFields.push(...archetypeOps.mutationFields);
|
|
151
|
+
Object.assign(resolvers, archetypeOps.resolvers);
|
|
152
|
+
logger.trace(`Added archetype operations: ${archetypeOps.queryFields.length} queries, ${archetypeOps.mutationFields.length} mutations`);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.error(`Failed to generate archetype operations: ${error}`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
// Still generate type definitions even if operations are disabled
|
|
158
|
+
try {
|
|
159
|
+
const archetypeOps = generateArchetypeOperations();
|
|
160
|
+
typeDefs += archetypeOps.typeDefs;
|
|
161
|
+
logger.trace(`Added archetype type definitions (without operations)`);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.error(`Failed to generate archetype type definitions: ${error}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
64
167
|
services.forEach(service => {
|
|
65
168
|
logger.trace(`Processing service: ${service.constructor.name}`);
|
|
169
|
+
// Check if service has graphql operations (either on instance or prototype)
|
|
170
|
+
const operations = service.__graphqlOperations || service.constructor.prototype.__graphqlOperations;
|
|
66
171
|
if(service.constructor.__graphqlScalarTypes) {
|
|
67
172
|
for (const scalarName of service.constructor.__graphqlScalarTypes) {
|
|
68
173
|
if (!scalarTypes.has(scalarName)) {
|
|
@@ -77,71 +182,152 @@ export function generateGraphQLSchema(services: any[]): { schema: GraphQLSchema
|
|
|
77
182
|
typeDefs += `type ${name} {\n${Object.entries(fields).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
|
|
78
183
|
}
|
|
79
184
|
}
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
185
|
+
if (operations) {
|
|
186
|
+
logger.trace(`Processing ${operations.length} operations for ${service.constructor.name}`);
|
|
187
|
+
operations.forEach((op: any) => {
|
|
188
|
+
try {
|
|
189
|
+
let { type, name, input, output, propertyKey } = op;
|
|
190
|
+
if (!resolvers[type]) resolvers[type] = {};
|
|
191
|
+
let fieldDef = `${name}`;
|
|
192
|
+
if (input) {
|
|
193
|
+
const inputName = `${name}Input`;
|
|
194
|
+
// Check if input is a Zod schema
|
|
195
|
+
if (input && typeof input === 'object' && '_def' in input) {
|
|
196
|
+
// It's a Zod schema - use GQLoom's weave to generate GraphQL type
|
|
197
|
+
try {
|
|
198
|
+
// Add __typename to input zod object
|
|
199
|
+
input = input.extend({ __typename: z.literal(inputName).nullish() });
|
|
200
|
+
logger.trace(`Weaving Zod schema for ${name}`);
|
|
201
|
+
const gqlInputSchema = weave(ZodWeaver, input as ZodType) as GraphQLSchema;
|
|
202
|
+
const schemaString = printSchema(gqlInputSchema);
|
|
203
|
+
logger.trace(`Schema string for ${name}: ${schemaString}`);
|
|
204
|
+
// Extract the type definition and convert it to an input type
|
|
205
|
+
// The schema will contain "type <TypeName> { ... }", we need to replace with "input <inputName> { ... }"
|
|
206
|
+
const typeMatch = schemaString.match(/type\s+(\w+)\s*\{([^}]*)\}/s);
|
|
207
|
+
if (typeMatch) {
|
|
208
|
+
const fields = typeMatch[2];
|
|
209
|
+
typeDefs += `input ${inputName} {${fields}}\n`;
|
|
210
|
+
logger.trace(`Successfully generated input type ${inputName}`);
|
|
211
|
+
} else {
|
|
212
|
+
logger.warn(`Could not extract type from Zod schema for ${name}, schema: ${schemaString}`);
|
|
213
|
+
typeDefs += `input ${inputName} { _placeholder: String }\n`;
|
|
102
214
|
}
|
|
103
|
-
})
|
|
215
|
+
} catch (error) {
|
|
216
|
+
logger.error(`Failed to weave Zod schema for ${name}: ${error}`);
|
|
217
|
+
logger.error(`Error stack: ${error instanceof Error ? error.stack : 'No stack'}`);
|
|
218
|
+
// Fallback: generate basic input type
|
|
219
|
+
typeDefs += `input ${inputName} { _placeholder: String }\n`;
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// Legacy Record<string, GraphQLType> format
|
|
223
|
+
typeDefs += `input ${inputName} {\n${Object.entries(input).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
|
|
104
224
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
225
|
+
fieldDef += `(input: ${inputName}!)`;
|
|
226
|
+
|
|
227
|
+
// Store the Zod schema for validation if it's a Zod type
|
|
228
|
+
const zodSchema = (input && typeof input === 'object' && '_def' in input) ? input as ZodType : null;
|
|
229
|
+
|
|
230
|
+
resolvers[type][name] = async (_: any, args: any, context: any, info: any) => {
|
|
231
|
+
try {
|
|
232
|
+
const inputArgs = args.input || args;
|
|
233
|
+
|
|
234
|
+
// Automatically validate with Zod schema if provided
|
|
235
|
+
if (zodSchema) {
|
|
236
|
+
try {
|
|
237
|
+
const validated = zodSchema.parse(inputArgs);
|
|
238
|
+
return await service[propertyKey](validated, context, info);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error instanceof z.ZodError) {
|
|
241
|
+
// Let handleGraphQLError convert Zod errors to user-friendly messages
|
|
242
|
+
const { handleGraphQLError } = await import("../core/ErrorHandler");
|
|
243
|
+
handleGraphQLError(error);
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
return await service[propertyKey](inputArgs, context, info);
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
logger.error(`Error in ${type}.${name}:`);
|
|
252
|
+
logger.error(error);
|
|
253
|
+
if (error instanceof GraphQLError) {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
throw new GraphQLError(`Internal error in ${name}`, {
|
|
257
|
+
extensions: {
|
|
258
|
+
code: "INTERNAL_ERROR",
|
|
259
|
+
originalError: process.env.NODE_ENV === 'development' ? error : undefined
|
|
260
|
+
}
|
|
261
|
+
});
|
|
115
262
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
263
|
+
};
|
|
264
|
+
} else {
|
|
265
|
+
resolvers[type][name] = async (_: any, args: any, context: any, info: any) => {
|
|
266
|
+
try {
|
|
267
|
+
return await service[propertyKey]({}, context, info);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.error(`Error in ${type}.${name}:`);
|
|
270
|
+
logger.error(error);
|
|
271
|
+
if (error instanceof GraphQLError) {
|
|
272
|
+
throw error;
|
|
120
273
|
}
|
|
121
|
-
|
|
274
|
+
throw new GraphQLError(`Internal error in ${name}`, {
|
|
275
|
+
extensions: {
|
|
276
|
+
code: "INTERNAL_ERROR",
|
|
277
|
+
originalError: process.env.NODE_ENV === 'development' ? error : undefined
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (typeof output === 'string') {
|
|
284
|
+
fieldDef += `: ${output}`;
|
|
285
|
+
} else if (Array.isArray(output)) {
|
|
286
|
+
// Handle array of archetypes: [serviceAreaArcheType]
|
|
287
|
+
const archetypeInstance = output[0];
|
|
288
|
+
const typeName = getArchetypeTypeName(archetypeInstance);
|
|
289
|
+
if (typeName) {
|
|
290
|
+
fieldDef += `: [${typeName}]`;
|
|
291
|
+
} else {
|
|
292
|
+
logger.warn(`Invalid array output type for ${name}, expected archetype instance`);
|
|
293
|
+
fieldDef += `: [Any]`;
|
|
122
294
|
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
295
|
+
} else if (output instanceof BaseArcheType) {
|
|
296
|
+
// Handle single archetype instance: serviceAreaArcheType
|
|
297
|
+
const typeName = getArchetypeTypeName(output);
|
|
298
|
+
if (typeName) {
|
|
299
|
+
fieldDef += `: ${typeName}`;
|
|
300
|
+
} else {
|
|
301
|
+
logger.warn(`Could not determine type name for archetype in ${name}`);
|
|
302
|
+
fieldDef += `: Any`;
|
|
303
|
+
}
|
|
304
|
+
} else if (typeof output === 'object') {
|
|
305
|
+
const outputName = `${name}Output`;
|
|
306
|
+
typeDefs += `type ${outputName} {\n${Object.entries(output).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
|
|
307
|
+
fieldDef += `: ${outputName}`;
|
|
308
|
+
}
|
|
309
|
+
if (type === 'Query') {
|
|
310
|
+
queryFields.push(fieldDef);
|
|
311
|
+
logger.trace(`Added query field: ${fieldDef}`);
|
|
312
|
+
} else if (type === 'Mutation') {
|
|
313
|
+
mutationFields.push(fieldDef);
|
|
314
|
+
logger.trace(`Added mutation field: ${fieldDef}`);
|
|
315
|
+
}
|
|
316
|
+
} catch (opError) {
|
|
317
|
+
logger.error(`Failed to process operation ${op.name || 'unknown'} in ${service.constructor.name}: ${opError}`);
|
|
318
|
+
logger.error(`Error stack: ${opError instanceof Error ? opError.stack : 'No stack'}`);
|
|
136
319
|
}
|
|
137
320
|
});
|
|
321
|
+
|
|
322
|
+
logger.trace(`Completed processing operations for ${service.constructor.name}`);
|
|
138
323
|
}
|
|
139
324
|
});
|
|
140
325
|
|
|
141
326
|
// Process field resolvers
|
|
142
327
|
services.forEach(service => {
|
|
143
|
-
|
|
144
|
-
|
|
328
|
+
const fields = service.__graphqlFields || service.constructor.prototype.__graphqlFields;
|
|
329
|
+
if (fields) {
|
|
330
|
+
fields.forEach((fieldMeta: any) => {
|
|
145
331
|
const { type, field, propertyKey } = fieldMeta;
|
|
146
332
|
if (!resolvers[type]) resolvers[type] = {};
|
|
147
333
|
resolvers[type][field] = async (parent: any, args: any, context: any, info: any) => {
|
|
@@ -172,11 +358,15 @@ export function generateGraphQLSchema(services: any[]): { schema: GraphQLSchema
|
|
|
172
358
|
typeDefs += `type Mutation {\n${mutationFields.map(f => ` ${f}`).join('\n')}\n}\n`;
|
|
173
359
|
}
|
|
174
360
|
|
|
361
|
+
logger.trace(`Query fields count: ${queryFields.length}, Mutation fields count: ${mutationFields.length}`);
|
|
175
362
|
logger.trace(`System Type Defs: ${typeDefs}`);
|
|
176
363
|
let schema : GraphQLSchema | null = null;
|
|
177
364
|
// Check if typeDefs contains actual schema definitions, not just whitespace
|
|
178
365
|
if(typeDefs.trim() !== "" && (queryFields.length > 0 || mutationFields.length > 0 || scalarTypes.size > 0)) {
|
|
366
|
+
logger.trace(`Creating schema with resolvers: ${Object.keys(resolvers).join(', ')}`);
|
|
179
367
|
schema = createSchema({ typeDefs, resolvers });
|
|
368
|
+
} else {
|
|
369
|
+
logger.warn(`No schema generated - queryFields: ${queryFields.length}, mutationFields: ${mutationFields.length}, scalarTypes: ${scalarTypes.size}`);
|
|
180
370
|
}
|
|
181
371
|
return { schema, resolvers };
|
|
182
372
|
}
|
package/gql/helpers.ts
CHANGED
|
@@ -14,15 +14,15 @@ export function isValidGraphQLType(type: string): type is GraphQLType {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export type TypeFromGraphQL<T extends GraphQLType> =
|
|
17
|
-
T extends GraphQLFieldTypes.ID_REQUIRED | GraphQLFieldTypes.
|
|
17
|
+
T extends GraphQLFieldTypes.ID_REQUIRED | GraphQLFieldTypes.ID ? string :
|
|
18
18
|
T extends GraphQLFieldTypes.STRING_REQUIRED ? string :
|
|
19
|
-
T extends GraphQLFieldTypes.
|
|
19
|
+
T extends GraphQLFieldTypes.STRING ? string | null :
|
|
20
20
|
T extends GraphQLFieldTypes.INT_REQUIRED ? number :
|
|
21
|
-
T extends GraphQLFieldTypes.
|
|
21
|
+
T extends GraphQLFieldTypes.INT ? number | null :
|
|
22
22
|
T extends GraphQLFieldTypes.BOOLEAN_REQUIRED ? boolean :
|
|
23
|
-
T extends GraphQLFieldTypes.
|
|
23
|
+
T extends GraphQLFieldTypes.BOOLEAN ? boolean | null :
|
|
24
24
|
T extends GraphQLFieldTypes.FLOAT_REQUIRED ? number :
|
|
25
|
-
T extends GraphQLFieldTypes.
|
|
25
|
+
T extends GraphQLFieldTypes.FLOAT ? number | null :
|
|
26
26
|
T extends `[${string}]` | `[${string}]!` ? any[] :
|
|
27
27
|
any;
|
|
28
28
|
|
package/gql/index.ts
CHANGED
|
@@ -104,26 +104,28 @@ const maskError = (error: any, message: string): GraphQLError => {
|
|
|
104
104
|
return error instanceof GraphQLError ? error : new GraphQLError(message, { originalError: error });
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
export function createYogaInstance(schema?: GraphQLSchema, plugins: Plugin[] = []) {
|
|
107
|
+
export function createYogaInstance(schema?: GraphQLSchema, plugins: Plugin[] = [], contextFactory?: (context: any) => any) {
|
|
108
|
+
const yogaConfig: any = {
|
|
109
|
+
plugins,
|
|
110
|
+
maskedErrors: {
|
|
111
|
+
maskError,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Add context factory if provided
|
|
116
|
+
if (contextFactory) {
|
|
117
|
+
yogaConfig.context = contextFactory;
|
|
118
|
+
}
|
|
119
|
+
|
|
108
120
|
if (schema) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
plugins,
|
|
112
|
-
maskedErrors: {
|
|
113
|
-
maskError,
|
|
114
|
-
},
|
|
115
|
-
});
|
|
121
|
+
yogaConfig.schema = schema;
|
|
122
|
+
return createYoga(yogaConfig);
|
|
116
123
|
} else {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
resolvers: staticResolvers,
|
|
121
|
-
}),
|
|
122
|
-
plugins,
|
|
123
|
-
maskedErrors: {
|
|
124
|
-
maskError,
|
|
125
|
-
},
|
|
124
|
+
yogaConfig.schema = createSchema({
|
|
125
|
+
typeDefs: staticTypeDefs,
|
|
126
|
+
resolvers: staticResolvers,
|
|
126
127
|
});
|
|
128
|
+
return createYoga(yogaConfig);
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
package/gql/types.ts
CHANGED
|
@@ -1,23 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
1
3
|
export enum GraphQLScalar {
|
|
2
4
|
ID = "ID",
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
INT = "Int",
|
|
6
|
+
FLOAT = "Float",
|
|
7
|
+
STRING = "String",
|
|
8
|
+
BOOLEAN = "Boolean",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GraphQLObject {
|
|
12
|
+
name: string;
|
|
13
|
+
fields: GraphQLField[];
|
|
8
14
|
}
|
|
9
15
|
|
|
16
|
+
export type GraphQLType = GraphQLScalar | string;
|
|
17
|
+
export interface GraphQLField {
|
|
18
|
+
name: string;
|
|
19
|
+
type: GraphQLType;
|
|
20
|
+
isList?: boolean;
|
|
21
|
+
isRequired?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// TODO: Remove this when we have a better way to define GraphQL type
|
|
10
27
|
export enum GraphQLFieldTypes {
|
|
28
|
+
ID = "ID",
|
|
11
29
|
ID_REQUIRED = "ID!",
|
|
12
|
-
|
|
13
|
-
STRING_REQUIRED = "String!",
|
|
14
|
-
STRING_OPTIONAL = "String",
|
|
30
|
+
INT = "Int",
|
|
15
31
|
INT_REQUIRED = "Int!",
|
|
16
|
-
|
|
32
|
+
FLOAT = "Float",
|
|
17
33
|
FLOAT_REQUIRED = "Float!",
|
|
18
|
-
|
|
34
|
+
STRING = "String",
|
|
35
|
+
STRING_REQUIRED = "String!",
|
|
36
|
+
BOOLEAN = "Boolean",
|
|
19
37
|
BOOLEAN_REQUIRED = "Boolean!",
|
|
20
|
-
BOOLEAN_OPTIONAL = "Boolean",
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
export const GraphQLList = {
|
|
@@ -25,4 +42,34 @@ export const GraphQLList = {
|
|
|
25
42
|
ofRequired: (type: string) => `[${type}]!`,
|
|
26
43
|
} as const;
|
|
27
44
|
|
|
45
|
+
// Utils for building GraphQL Enums
|
|
46
|
+
import { GraphQLEnumType } from 'graphql';
|
|
47
|
+
|
|
48
|
+
type EnumObject = {
|
|
49
|
+
[index: string]: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type EnumObjectResult = {
|
|
53
|
+
[index: string]: {
|
|
54
|
+
value: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export const enumBuilderValues = <T extends EnumObject>(
|
|
58
|
+
constants: T,
|
|
59
|
+
): EnumObjectResult =>
|
|
60
|
+
Object.keys(constants).reduce(
|
|
61
|
+
(prev, curr) => ({
|
|
62
|
+
...prev,
|
|
63
|
+
[curr]: {
|
|
64
|
+
value: constants[curr],
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
{},
|
|
68
|
+
);
|
|
69
|
+
|
|
28
70
|
|
|
71
|
+
export const graphqlEnumBuilder = <T extends EnumObject>(name: string, values: T) =>
|
|
72
|
+
new GraphQLEnumType({
|
|
73
|
+
name,
|
|
74
|
+
values: enumBuilderValues(values),
|
|
75
|
+
});
|