@getcronit/pylon-builder 0.0.49

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.
@@ -0,0 +1,13 @@
1
+ export interface BundlerBuildOptions {
2
+ getTypeDefs: () => string;
3
+ watch?: boolean;
4
+ onWatch?: () => void;
5
+ }
6
+ export declare class Bundler {
7
+ sfiFilePath: string;
8
+ outputDir: string;
9
+ constructor(sfiFilePath: string, outputDir?: string);
10
+ build(options: BundlerBuildOptions): Promise<void>;
11
+ private prependTypeDefs;
12
+ private reportStatus;
13
+ }
@@ -0,0 +1 @@
1
+ export { Bundler } from './bundler.js';
@@ -0,0 +1 @@
1
+ export declare function generateClientWhenServerIsHealthy(): Promise<void>;
@@ -0,0 +1,7 @@
1
+ export interface BuildOptions {
2
+ sfiFilePath: string;
3
+ outputFilePath: string;
4
+ watch?: boolean;
5
+ onWatch?: () => void;
6
+ }
7
+ export declare const build: (options: BuildOptions) => Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,834 @@
1
+ // @bun
2
+ // src/bundler/bundler.ts
3
+ import fs from "fs";
4
+ import chokidar from "chokidar";
5
+
6
+ // src/load-package-json.ts
7
+ import path from "path";
8
+ import {readFile} from "fs/promises";
9
+ async function loadPackageJson() {
10
+ const packageJsonPath = path.resolve(process.cwd(), "package.json");
11
+ const file = await readFile(packageJsonPath);
12
+ const packageJson = JSON.parse(file.toString());
13
+ return packageJson;
14
+ }
15
+
16
+ // src/bundler/bundler.ts
17
+ import ncc2 from "@vercel/ncc";
18
+ import path2 from "path";
19
+
20
+ class Bundler {
21
+ sfiFilePath;
22
+ outputDir;
23
+ constructor(sfiFilePath, outputDir = "./.pylon") {
24
+ this.sfiFilePath = sfiFilePath;
25
+ this.outputDir = outputDir;
26
+ }
27
+ async build(options) {
28
+ this.reportStatus("start");
29
+ const build = async () => {
30
+ const external = new Set([
31
+ "@cronitio/pylon",
32
+ "hono",
33
+ "@prisma/client",
34
+ "openid-client",
35
+ "hono/http-exception"
36
+ ]);
37
+ const packageJson = await loadPackageJson();
38
+ if (packageJson.pylon && packageJson.pylon.external) {
39
+ packageJson.pylon.external.forEach((externalPackage) => {
40
+ external.add(externalPackage);
41
+ });
42
+ }
43
+ const nccOptions = {
44
+ quiet: true,
45
+ externals: Array.from(external),
46
+ sourceMap: true,
47
+ transpileOnly: true
48
+ };
49
+ const inputPath = path2.join(process.cwd(), this.sfiFilePath);
50
+ const dir = path2.join(process.cwd(), this.outputDir);
51
+ const { code, map, assets } = await ncc2(inputPath, nccOptions);
52
+ fs.mkdirSync(dir, {
53
+ recursive: true
54
+ });
55
+ fs.writeFileSync(`${dir}/index.js`, code);
56
+ Object.entries(assets).forEach(([name, asset]) => {
57
+ const source = asset.source;
58
+ if (typeof source === "string") {
59
+ fs.writeFileSync(`${dir}/${name}`, source);
60
+ } else if (typeof source === "object") {
61
+ fs.writeFileSync(`${dir}/${name}`, Buffer.from(source));
62
+ }
63
+ });
64
+ this.prependTypeDefs(options.getTypeDefs());
65
+ this.reportStatus("done");
66
+ };
67
+ if (options.watch) {
68
+ chokidar.watch(this.sfiFilePath).on("change", async () => {
69
+ await build();
70
+ if (options.onWatch) {
71
+ options.onWatch();
72
+ }
73
+ });
74
+ }
75
+ await build();
76
+ }
77
+ prependTypeDefs(typeDefs) {
78
+ const outputFile = `${this.outputDir}/index.js`;
79
+ fs.writeFileSync(outputFile, `export const typeDefs = ${JSON.stringify(typeDefs)};\n` + fs.readFileSync(outputFile));
80
+ }
81
+ reportStatus(status) {
82
+ if (status === "start") {
83
+ console.log("\x1B[32m%s\x1B[0m", `Bundling Pylon: ${this.sfiFilePath}...`);
84
+ }
85
+ if (status === "done") {
86
+ console.log("\x1B[32m%s\x1B[0m", "Bundling Pylon: done");
87
+ }
88
+ }
89
+ }
90
+
91
+ // src/schema/builder.ts
92
+ import ts4 from "typescript";
93
+
94
+ // src/schema/schema-parser.ts
95
+ import ts3 from "typescript";
96
+
97
+ // src/schema/types-helper.ts
98
+ import ts from "typescript";
99
+ function getPublicPropertiesOfType(checker, _type) {
100
+ let type = _type;
101
+ if (type.isUnion()) {
102
+ const nonArrayTypes = type.types.filter((t) => !(t.flags & ts.TypeFlags.Object && t.objectFlags & ts.ObjectFlags.Reference && t.typeArguments?.length));
103
+ const arrayElementTypes = type.types.filter((t) => t.flags & ts.TypeFlags.Object && t.objectFlags & ts.ObjectFlags.Reference && t.typeArguments?.length).map((t) => t.typeArguments[0]);
104
+ const filteredTypes = nonArrayTypes.filter((t) => !arrayElementTypes.some((et) => et === t));
105
+ type = checker.getUnionType(filteredTypes);
106
+ }
107
+ const properties = [];
108
+ if (isList(checker, type) || isPrimitive(type) || isEnum(type))
109
+ return properties;
110
+ let typeProperties = [];
111
+ typeProperties = type.resolvedProperties || type.getProperties() || [];
112
+ for (const prop of typeProperties) {
113
+ const isPublic = prop.valueDeclaration && !(ts.getCombinedModifierFlags(prop.valueDeclaration) & ts.ModifierFlags.Private) && !prop.getName().startsWith("#");
114
+ if (isPublic === false) {
115
+ continue;
116
+ }
117
+ properties.push(prop);
118
+ }
119
+ return properties;
120
+ }
121
+ var isEmptyObject = (type) => {
122
+ return type.flags & ts.TypeFlags.Object && type.getProperties().length === 0 && type.getCallSignatures().length === 0;
123
+ };
124
+ var isList = (checker, type) => {
125
+ const typeNode = checker.typeToTypeNode(type, undefined, undefined);
126
+ const is = !!(typeNode && (typeNode.kind === ts.SyntaxKind.ArrayType || typeNode.kind === ts.SyntaxKind.TupleType));
127
+ return is;
128
+ };
129
+ var isPrimitive = (type) => {
130
+ const primitive = type.flags & ts.TypeFlags.String || type.flags & ts.TypeFlags.Number || type.flags & ts.TypeFlags.Boolean || type.flags & ts.TypeFlags.StringLiteral || type.flags & ts.TypeFlags.NumberLiteral || type.flags & ts.TypeFlags.BooleanLiteral;
131
+ return primitive;
132
+ };
133
+ var isFunction = (type) => {
134
+ return type.getCallSignatures().length > 0;
135
+ };
136
+ var isEnum = (type) => {
137
+ if (isPrimitive(type))
138
+ return false;
139
+ const isUnion = type.types?.length > 1;
140
+ if (isUnion) {
141
+ const isAllPrimitives = type.types.every((t) => t.flags & ts.TypeFlags.StringLiteral || t.flags & ts.TypeFlags.NumberLiteral || t.flags & ts.TypeFlags.BooleanLiteral);
142
+ if (isAllPrimitives) {
143
+ return true;
144
+ }
145
+ }
146
+ return false;
147
+ };
148
+ var isPromise = (type) => {
149
+ return type.getSymbol()?.getName() === "Promise";
150
+ };
151
+ var getPromiseType = (type) => {
152
+ let objectType = type;
153
+ if (objectType.objectFlags & ts.ObjectFlags.Reference) {
154
+ const reference = type;
155
+ const typeArguments = reference.typeArguments;
156
+ if (typeArguments && typeArguments.length > 0) {
157
+ return typeArguments[0];
158
+ }
159
+ }
160
+ };
161
+ var excludeNullUndefinedFromType = (type) => {
162
+ if (!type.isUnion())
163
+ return {
164
+ type,
165
+ wasOptional: false
166
+ };
167
+ const types = type.types.filter((t) => !(t.flags & ts.TypeFlags.Undefined || t.flags & ts.TypeFlags.Null));
168
+ const wasOptional = types.length !== type.types.length;
169
+ return {
170
+ type: type.getNonNullableType(),
171
+ wasOptional
172
+ };
173
+ };
174
+ var safeTypeName = (name) => {
175
+ return name.replace(/[^0-9a-zA-Z_]/g, "_");
176
+ };
177
+
178
+ // src/schema/type-definition-builder.ts
179
+ import ts2 from "typescript";
180
+ class TypeDefinitionBuilder {
181
+ checker;
182
+ schema;
183
+ typesNameMap = new Map;
184
+ inputsNameMap = new Map;
185
+ unions = [];
186
+ enums = [];
187
+ constructor(checker, schema) {
188
+ this.checker = checker;
189
+ this.schema = schema;
190
+ }
191
+ getTypeDefinition = (rawType, options = {
192
+ isInputType: false
193
+ }) => {
194
+ const { type, wasOptional } = excludeNullUndefinedFromType(rawType);
195
+ if (isPromise(type)) {
196
+ const promiseType = getPromiseType(type);
197
+ if (promiseType) {
198
+ return this.getTypeDefinition(promiseType, options);
199
+ }
200
+ }
201
+ const isRequired = options.isRequired !== undefined ? options.isRequired : !wasOptional;
202
+ if (isEmptyObject(type)) {
203
+ return {
204
+ name: "Object",
205
+ isList: false,
206
+ isRequired
207
+ };
208
+ }
209
+ let nameMap = options.isInputType ? this.inputsNameMap : this.typesNameMap;
210
+ if (nameMap.has(type)) {
211
+ const typeName2 = nameMap.get(type);
212
+ return {
213
+ name: typeName2,
214
+ isList: false,
215
+ isRequired
216
+ };
217
+ }
218
+ let typeName = type.aliasSymbol?.escapedName?.toString() || type.symbol?.escapedName.toString();
219
+ if (typeName === "__type" || typeName === "__object") {
220
+ if (!options.propertyName) {
221
+ typeName = undefined;
222
+ } else {
223
+ const capitalizedPropertyName = options.propertyName.charAt(0).toUpperCase() + options.propertyName.slice(1);
224
+ typeName = capitalizedPropertyName;
225
+ }
226
+ }
227
+ if (typeName) {
228
+ typeName = safeTypeName(typeName);
229
+ }
230
+ const duplicateName = (name) => {
231
+ return Array.from(nameMap.values()).includes(name);
232
+ };
233
+ if (typeName) {
234
+ if (options.isInputType) {
235
+ typeName = `${typeName}Input`;
236
+ }
237
+ let i = 1;
238
+ let originalTypeName = typeName;
239
+ while (duplicateName(typeName)) {
240
+ typeName = `${originalTypeName}_${i}`;
241
+ i++;
242
+ }
243
+ }
244
+ if (typeName && this.schema.scalars.includes(typeName)) {
245
+ return {
246
+ name: typeName,
247
+ isList: false,
248
+ isRequired
249
+ };
250
+ }
251
+ if (isList(this.checker, type)) {
252
+ const listType = this.checker.getIndexTypeOfType(type, ts2.IndexKind.Number);
253
+ if (listType) {
254
+ const def = this.getTypeDefinition(listType, options);
255
+ return {
256
+ name: def.name,
257
+ isList: true,
258
+ isRequired: def.isRequired,
259
+ isListRequired: isRequired
260
+ };
261
+ }
262
+ } else if (isEnum(type)) {
263
+ const typeNode = this.checker.typeToTypeNode(type, undefined, undefined);
264
+ const types = type.types.filter((t) => t.flags & ts2.TypeFlags.StringLiteral);
265
+ const members = types.map((t) => {
266
+ if (t.isLiteral()) {
267
+ const name = t.value?.toString();
268
+ if (!name) {
269
+ throw new Error("Enum member name is undefined");
270
+ }
271
+ return safeTypeName(name);
272
+ }
273
+ throw new Error("Invalid type for enum member");
274
+ });
275
+ if (members.length > 0) {
276
+ typeName = typeName || typeNode.typeName?.symbol?.escapedName;
277
+ if (!typeName) {
278
+ typeName = members.join("_").toUpperCase();
279
+ typeName = options.isInputType ? `${typeName}Input` : typeName;
280
+ }
281
+ this.enums.push({
282
+ name: typeName,
283
+ values: members,
284
+ rawType: type
285
+ });
286
+ }
287
+ } else if (isPrimitive(type)) {
288
+ let typeName2 = this.checker.typeToString(type);
289
+ if (type.flags & ts2.TypeFlags.StringLiteral) {
290
+ typeName2 = "String";
291
+ } else if (type.flags & ts2.TypeFlags.NumberLiteral) {
292
+ typeName2 = "Number";
293
+ } else if (type.flags & ts2.TypeFlags.BooleanLiteral) {
294
+ typeName2 = "Boolean";
295
+ } else if (type.flags & ts2.TypeFlags.String) {
296
+ typeName2 = "String";
297
+ } else if (type.flags & ts2.TypeFlags.Number) {
298
+ typeName2 = "Number";
299
+ } else if (type.flags & ts2.TypeFlags.Boolean) {
300
+ typeName2 = "Boolean";
301
+ }
302
+ return {
303
+ name: safeTypeName(typeName2),
304
+ isList: false,
305
+ isRequired
306
+ };
307
+ } else if (type.isIntersection()) {
308
+ const intersectionTypes = type.types;
309
+ const typeNames = intersectionTypes.map((t) => {
310
+ const typeDef = this.getTypeDefinition(t, options);
311
+ return typeDef.name;
312
+ });
313
+ typeName = safeTypeName(typeName || typeNames.join("And"));
314
+ } else if (type.isUnion()) {
315
+ const unionTypes = type.types;
316
+ const required = type.types.length === unionTypes.length;
317
+ const hasPrimitivesOrEnum = unionTypes.some((t) => {
318
+ const type2 = isList(this.checker, t) ? this.checker.getIndexTypeOfType(t, ts2.IndexKind.Number) : t;
319
+ if (!type2) {
320
+ return true;
321
+ }
322
+ return isPrimitive(type2) || isEnum(type2);
323
+ });
324
+ const unionTypeDefs = [
325
+ ...new Set(unionTypes.map((t) => this.getTypeDefinition(t, { ...options, isRequired: required })))
326
+ ];
327
+ const listTypes = unionTypeDefs.filter((t) => t.isList);
328
+ for (const listType of listTypes) {
329
+ const index = unionTypeDefs.findIndex((t) => t.name === listType.name && !t.isList);
330
+ if (index > -1) {
331
+ unionTypeDefs.splice(index, 1);
332
+ }
333
+ }
334
+ const typeNames = unionTypeDefs.map((t) => t.name);
335
+ typeName = safeTypeName(typeName || typeNames.join("Or"));
336
+ if (unionTypeDefs.length > 1 && !options.isInputType && !hasPrimitivesOrEnum) {
337
+ this.unions.push({
338
+ name: typeName,
339
+ types: typeNames,
340
+ rawType: type
341
+ });
342
+ } else {
343
+ const typeDef = unionTypeDefs[0];
344
+ if (!typeDef) {
345
+ throw new Error("Cannot get type definition");
346
+ }
347
+ const isList2 = unionTypeDefs.some((t) => {
348
+ return t.isList && t.name === typeDef.name;
349
+ });
350
+ return {
351
+ name: typeDef.name,
352
+ isList: isList2,
353
+ isRequired
354
+ };
355
+ }
356
+ } else if (isFunction(type)) {
357
+ const signature = type.getCallSignatures()[0];
358
+ let returnType = signature?.getReturnType();
359
+ if (returnType) {
360
+ if (isPromise(returnType)) {
361
+ const pt = getPromiseType(returnType);
362
+ if (pt) {
363
+ returnType = pt;
364
+ }
365
+ }
366
+ }
367
+ if (returnType) {
368
+ const def = this.getTypeDefinition(returnType, options);
369
+ return def;
370
+ }
371
+ }
372
+ if (!typeName) {
373
+ typeName = "Any";
374
+ }
375
+ nameMap.set(type, typeName);
376
+ return {
377
+ name: typeName,
378
+ isList: false,
379
+ isRequired
380
+ };
381
+ };
382
+ getUnions() {
383
+ return this.unions;
384
+ }
385
+ getEnums() {
386
+ return this.enums;
387
+ }
388
+ typeDefinitionToGraphQLType = (typeDefinition) => {
389
+ let type = typeDefinition.name;
390
+ const { isList: isList2, isRequired, isListRequired } = typeDefinition;
391
+ if (isRequired) {
392
+ type = `${type}!`;
393
+ }
394
+ if (isList2) {
395
+ type = `[${type}]`;
396
+ if (isListRequired) {
397
+ type = `${type}!`;
398
+ }
399
+ }
400
+ return type;
401
+ };
402
+ }
403
+
404
+ // src/schema/schema-parser.ts
405
+ class SchemaParser {
406
+ schema;
407
+ checker;
408
+ sfiFile;
409
+ typeDefinitionBuilder;
410
+ constructor(checker, sfiFile) {
411
+ this.schema = {
412
+ types: [],
413
+ inputs: [],
414
+ unions: [],
415
+ enums: [],
416
+ scalars: [
417
+ "Number",
418
+ "Any",
419
+ "Object",
420
+ "File",
421
+ "Date",
422
+ "JSON",
423
+ "String",
424
+ "Boolean"
425
+ ]
426
+ };
427
+ this.checker = checker;
428
+ this.sfiFile = sfiFile;
429
+ this.typeDefinitionBuilder = new TypeDefinitionBuilder(checker, this.schema);
430
+ }
431
+ parse(index) {
432
+ const referenceSchema = this.makeReferenceSchema(index);
433
+ for (const [type, properties] of referenceSchema.types) {
434
+ let typeName = undefined;
435
+ if (index.Query === type) {
436
+ typeName = "Query";
437
+ } else if (index.Mutation === type) {
438
+ typeName = "Mutation";
439
+ }
440
+ this.processSchemaReference(type, properties, typeName, "types");
441
+ }
442
+ for (const [type, properties] of referenceSchema.inputs) {
443
+ this.processSchemaReference(type, properties, undefined, "inputs");
444
+ }
445
+ this.extractForbiddenFieldNamesFromSchema();
446
+ this.schema.unions = this.typeDefinitionBuilder.getUnions().map((union) => {
447
+ return {
448
+ ...union,
449
+ description: this.getJsDocHeaderFromType(union.rawType)
450
+ };
451
+ });
452
+ this.schema.enums = this.typeDefinitionBuilder.getEnums().map((enumType) => {
453
+ return {
454
+ ...enumType,
455
+ description: this.getJsDocHeaderFromType(enumType.rawType)
456
+ };
457
+ });
458
+ }
459
+ toString() {
460
+ const { typeDefinitionToGraphQLType } = this.typeDefinitionBuilder;
461
+ const addDescription = (description) => {
462
+ if (!description)
463
+ return "";
464
+ return `"""\n${description}\n"""\n`;
465
+ };
466
+ let schemaString = "";
467
+ for (const input of this.schema.inputs) {
468
+ schemaString += addDescription(input.description);
469
+ schemaString += `input ${input.name} {\n`;
470
+ if (input.fields.length === 0) {
471
+ schemaString += `\t_ : String\n`;
472
+ }
473
+ for (const field of input.fields) {
474
+ schemaString += `${addDescription(field.type.description)}`;
475
+ schemaString += `\t${field.name}: ${typeDefinitionToGraphQLType(field.type)}\n`;
476
+ }
477
+ schemaString += `}\n`;
478
+ }
479
+ for (const type of this.schema.types) {
480
+ if (type.fields.length === 0)
481
+ continue;
482
+ schemaString += addDescription(type.description);
483
+ schemaString += `type ${type.name} {\n`;
484
+ for (const field of type.fields) {
485
+ let args = "";
486
+ if (field.args.length > 0) {
487
+ args = `(${field.args.map((arg) => `${addDescription(type.description)}${arg.name}: ${typeDefinitionToGraphQLType(arg.type)}`).join(", ")})`;
488
+ }
489
+ schemaString += `${addDescription(field.type.description)}`;
490
+ schemaString += `${field.name}${args}: ${typeDefinitionToGraphQLType(field.type)}\n`;
491
+ }
492
+ schemaString += `}\n`;
493
+ }
494
+ for (const union of this.schema.unions) {
495
+ schemaString += addDescription(union.description);
496
+ schemaString += `union ${union.name} = ${union.types.join(" | ")}\n`;
497
+ }
498
+ for (const scalar of this.schema.scalars) {
499
+ schemaString += `scalar ${scalar}\n`;
500
+ }
501
+ for (const enumType of this.schema.enums) {
502
+ schemaString += addDescription(enumType.description);
503
+ schemaString += `enum ${enumType.name} {\n`;
504
+ for (const value of enumType.values) {
505
+ schemaString += `\t${value}\n`;
506
+ }
507
+ schemaString += `}\n`;
508
+ }
509
+ return schemaString;
510
+ }
511
+ getSchema() {
512
+ return this.schema;
513
+ }
514
+ processSchemaReference(type, properties, typeName, processing = "types") {
515
+ const { getTypeDefinition } = this.typeDefinitionBuilder;
516
+ const isInputType = processing === "inputs";
517
+ const def = getTypeDefinition(type, { isInputType });
518
+ const name = typeName || def.name;
519
+ let root = this.schema[processing].find((t) => t.name === name);
520
+ if (!root) {
521
+ if (this.schema.scalars.includes(name)) {
522
+ return;
523
+ } else {
524
+ this.schema[processing].push({
525
+ name,
526
+ description: this.getJsDocHeaderFromType(type),
527
+ fields: []
528
+ });
529
+ root = this.schema[processing][this.schema[processing].length - 1];
530
+ }
531
+ }
532
+ for (const [propertyName, property] of Object.entries(properties)) {
533
+ const fieldType = property.returnType;
534
+ const fieldDef = getTypeDefinition(fieldType, {
535
+ isInputType,
536
+ propertyName
537
+ });
538
+ if (processing === "types") {
539
+ const field = {
540
+ name: propertyName,
541
+ type: {
542
+ ...fieldDef,
543
+ description: this.getJsDocHeaderFromType(fieldType)
544
+ },
545
+ args: []
546
+ };
547
+ if (property.args) {
548
+ for (const [argName, arg] of Object.entries(property.args)) {
549
+ const argType = arg;
550
+ const fieldDef2 = getTypeDefinition(argType, {
551
+ isInputType: true,
552
+ propertyName: argName
553
+ });
554
+ if (this.schema.scalars.includes(this.checker.typeToString(argType))) {
555
+ fieldDef2.name = this.checker.typeToString(argType);
556
+ }
557
+ field.args.push({
558
+ name: argName,
559
+ type: {
560
+ ...fieldDef2,
561
+ description: this.getJsDocHeaderFromType(argType)
562
+ }
563
+ });
564
+ }
565
+ }
566
+ root.fields.push(field);
567
+ } else if (processing === "inputs") {
568
+ const field = {
569
+ name: propertyName,
570
+ type: {
571
+ ...fieldDef,
572
+ description: this.getJsDocHeaderFromType(fieldType)
573
+ }
574
+ };
575
+ if (!root.fields.find((f) => f.name === field.name)) {
576
+ root.fields.push(field);
577
+ }
578
+ }
579
+ }
580
+ }
581
+ getJsDocHeaderFromType = (type) => {
582
+ let header = "";
583
+ if (type.symbol) {
584
+ const typeDeclaration = type.symbol.declarations?.[0];
585
+ const comments = typeDeclaration?.jsDoc?.map((doc) => doc.comment).join(" ");
586
+ if (comments) {
587
+ header = comments;
588
+ }
589
+ const tags = type.symbol.getJsDocTags();
590
+ const tagComments = tags?.map((tag) => `@${tag.name} ${tag.text?.map((t) => t.text).join(" ")}`).join("\n");
591
+ if (tagComments) {
592
+ header = `${header} ${tagComments}`;
593
+ }
594
+ }
595
+ return header;
596
+ };
597
+ extractForbiddenFieldNamesFromSchema() {
598
+ const validFieldNameRegExp = /^[_A-Za-z][_0-9A-Za-z]*$/;
599
+ const isReserved = (name) => {
600
+ if (!validFieldNameRegExp.test(name)) {
601
+ return true;
602
+ }
603
+ return name.startsWith("__");
604
+ };
605
+ for (const type of this.schema.types) {
606
+ type.fields = type.fields.filter((field) => {
607
+ if (isReserved(field.name)) {
608
+ return false;
609
+ }
610
+ return true;
611
+ });
612
+ }
613
+ for (const input of this.schema.inputs) {
614
+ input.fields = input.fields.filter((field) => {
615
+ if (isReserved(field.name)) {
616
+ return false;
617
+ }
618
+ return true;
619
+ });
620
+ }
621
+ }
622
+ makeReferenceSchema(index) {
623
+ const referenceSchema = {
624
+ types: new Map,
625
+ inputs: new Map
626
+ };
627
+ const recLoop = (type, info = {}, processing = "types", path3 = []) => {
628
+ if (referenceSchema[processing].has(type)) {
629
+ return;
630
+ }
631
+ const wrongType = this.checker.typeToString(type) === "[]";
632
+ if (wrongType) {
633
+ return;
634
+ }
635
+ if (this.schema.scalars.includes(this.checker.typeToString(type))) {
636
+ return;
637
+ }
638
+ if (isPrimitive(type)) {
639
+ return;
640
+ }
641
+ if (isPromise(type)) {
642
+ if (processing === "inputs") {
643
+ return;
644
+ }
645
+ const promiseType = getPromiseType(type);
646
+ if (promiseType) {
647
+ recLoop(promiseType, info, processing, [...path3, "PROMISE"]);
648
+ }
649
+ return;
650
+ }
651
+ if (type.isUnion()) {
652
+ if (processing === "types") {
653
+ type.types.forEach((t) => {
654
+ if (t.flags & ts3.TypeFlags.Null || t.flags & ts3.TypeFlags.Undefined || isPrimitive(t)) {
655
+ return;
656
+ }
657
+ recLoop(t, info, processing, [
658
+ ...path3,
659
+ t.symbol?.getName() || `N/A ${this.checker.typeToString(t)}`
660
+ ]);
661
+ });
662
+ } else {
663
+ let properties = getPublicPropertiesOfType(this.checker, type.getNonNullableType());
664
+ if (properties.length === 0) {
665
+ const nt = type.getNonNullableType();
666
+ if (nt.isUnion()) {
667
+ properties = getPublicPropertiesOfType(this.checker, nt.types[0]);
668
+ }
669
+ }
670
+ if (properties.length > 0) {
671
+ if (!referenceSchema[processing].has(type)) {
672
+ referenceSchema[processing].set(type, {});
673
+ }
674
+ properties.forEach((property) => {
675
+ const propertyType = this.checker.getTypeOfSymbolAtLocation(property, this.sfiFile);
676
+ if (!isFunction(propertyType)) {
677
+ referenceSchema[processing].get(type)[property.escapedName] = {
678
+ returnType: propertyType,
679
+ args: {}
680
+ };
681
+ recLoop(propertyType, {
682
+ propetyName: property.escapedName,
683
+ parentType: type
684
+ }, processing, [...path3, property.escapedName]);
685
+ }
686
+ });
687
+ }
688
+ }
689
+ } else if (isFunction(type)) {
690
+ if (processing === "inputs") {
691
+ return;
692
+ }
693
+ if (!info.parentType) {
694
+ throw new Error("Cannot have a function without a parent type");
695
+ }
696
+ const signature = type.getCallSignatures()[0];
697
+ const args = signature?.getParameters() || [];
698
+ const returnType = signature?.getReturnType();
699
+ const schemaType = referenceSchema[processing].get(info.parentType)[info.propetyName];
700
+ args.forEach((arg) => {
701
+ if (arg.valueDeclaration && ts3.isParameter(arg.valueDeclaration) && arg.valueDeclaration.dotDotDotToken) {
702
+ console.warn("Cannot handle rest parameters yet, skipping", arg.escapedName);
703
+ } else {
704
+ const argType = this.checker.getTypeOfSymbolAtLocation(arg, this.sfiFile);
705
+ if (schemaType.args) {
706
+ schemaType.args[arg.escapedName] = argType;
707
+ recLoop(argType, {
708
+ parentType: type
709
+ }, "inputs", [...path3, arg.escapedName]);
710
+ }
711
+ }
712
+ });
713
+ if (returnType) {
714
+ recLoop(returnType, info, processing, [...path3, "RETURN_TYPE"]);
715
+ }
716
+ } else if (isList(this.checker, type)) {
717
+ const itemType = this.checker.getIndexTypeOfType(type, ts3.IndexKind.Number);
718
+ if (itemType && !isPrimitive(itemType)) {
719
+ if (!referenceSchema[processing].has(type)) {
720
+ referenceSchema[processing].set(type, {});
721
+ }
722
+ recLoop(itemType, info, processing, [...path3, "ITEM_TYPE"]);
723
+ }
724
+ } else if (!isPrimitive(type)) {
725
+ const properties = getPublicPropertiesOfType(this.checker, type);
726
+ if (!referenceSchema[processing].has(type)) {
727
+ referenceSchema[processing].set(type, {});
728
+ }
729
+ properties.forEach((property) => {
730
+ const propertyType = this.checker.getTypeOfSymbolAtLocation(property, this.sfiFile);
731
+ if (!referenceSchema[processing].get(type)[property.escapedName]) {
732
+ referenceSchema[processing].get(type)[property.escapedName] = {
733
+ returnType: propertyType,
734
+ args: {}
735
+ };
736
+ }
737
+ recLoop(propertyType, {
738
+ propetyName: property.escapedName,
739
+ parentType: type
740
+ }, processing, [...path3, property.escapedName]);
741
+ });
742
+ }
743
+ };
744
+ if (index.Query) {
745
+ recLoop(index.Query);
746
+ }
747
+ if (index.Mutation) {
748
+ recLoop(index.Mutation);
749
+ }
750
+ return referenceSchema;
751
+ }
752
+ }
753
+
754
+ // src/schema/builder.ts
755
+ import path3 from "path";
756
+
757
+ class SchemaBuilder {
758
+ program;
759
+ checker;
760
+ sfiFile;
761
+ sfi;
762
+ sfiFilePath;
763
+ constructor(sfiFilePath) {
764
+ this.sfiFilePath = path3.join(process.cwd(), sfiFilePath);
765
+ this.program = ts4.createProgram([this.sfiFilePath], {
766
+ target: ts4.ScriptTarget.ESNext,
767
+ module: ts4.ModuleKind.CommonJS,
768
+ strict: true,
769
+ esModuleInterop: true,
770
+ skipLibCheck: false,
771
+ forceConsistentCasingInFileNames: true,
772
+ noImplicitAny: true,
773
+ experimentalDecorators: true
774
+ });
775
+ this.checker = this.program.getTypeChecker();
776
+ this.loadSfi();
777
+ }
778
+ loadSfi() {
779
+ const sourceFiles = this.program.getSourceFiles();
780
+ const file = sourceFiles.find((file2) => file2.fileName === this.sfiFilePath);
781
+ if (!file) {
782
+ throw new Error("Could not find index.ts (pylon entrypoint)");
783
+ }
784
+ this.sfiFile = file;
785
+ const sfiFileSymbol = this.checker.getSymbolAtLocation(file);
786
+ const sfiFileExports = this.checker.getExportsOfModule(sfiFileSymbol);
787
+ const sfiFileDefaultExport = sfiFileExports.find((exportSymbol) => exportSymbol.escapedName === "default");
788
+ if (!sfiFileDefaultExport) {
789
+ throw new Error("Could not find default export");
790
+ }
791
+ this.sfi = sfiFileDefaultExport;
792
+ }
793
+ build() {
794
+ const sfiType = this.checker.getTypeOfSymbolAtLocation(this.sfi, this.sfiFile);
795
+ const plainResolversProperty = sfiType.getProperty("plainResolvers");
796
+ if (!plainResolversProperty) {
797
+ throw new Error("Could not find plainResolvers property");
798
+ }
799
+ const plainResolversType = this.checker.getTypeOfSymbolAtLocation(plainResolversProperty, this.sfiFile);
800
+ const queryProperty = plainResolversType.getProperty("Query");
801
+ const mutationProperty = plainResolversType.getProperty("Mutation");
802
+ const queryType = queryProperty ? this.checker.getTypeOfSymbolAtLocation(queryProperty, this.sfiFile) : undefined;
803
+ const mutationType = mutationProperty ? this.checker.getTypeOfSymbolAtLocation(mutationProperty, this.sfiFile) : undefined;
804
+ const parser = new SchemaParser(this.checker, this.sfiFile);
805
+ parser.parse({
806
+ Query: queryType,
807
+ Mutation: mutationType
808
+ });
809
+ return {
810
+ typeDefs: parser.toString(),
811
+ schema: parser.getSchema()
812
+ };
813
+ }
814
+ }
815
+
816
+ // src/index.ts
817
+ var build = async (options) => {
818
+ const bundler2 = new Bundler(options.sfiFilePath, options.outputFilePath);
819
+ await bundler2.build({
820
+ getTypeDefs: () => {
821
+ const builder2 = new SchemaBuilder(options.sfiFilePath);
822
+ const built = builder2.build();
823
+ const typeDefs = built.typeDefs;
824
+ return typeDefs;
825
+ },
826
+ watch: options.watch,
827
+ onWatch: options.onWatch
828
+ });
829
+ };
830
+ export {
831
+ build
832
+ };
833
+
834
+ //# debugId=0C352A137DDA7D9F64756e2164756e21
@@ -0,0 +1,21 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/bundler/bundler.ts", "../src/load-package-json.ts", "../src/bundler/bundler.ts", "../src/schema/builder.ts", "../src/schema/schema-parser.ts", "../src/schema/types-helper.ts", "../src/schema/types-helper.ts", "../src/schema/type-definition-builder.ts", "../src/schema/type-definition-builder.ts", "../src/schema/schema-parser.ts", "../src/schema/builder.ts", "../src/index.ts"],
4
+ "sourcesContent": [
5
+ "// bundler.ts\nimport fs from 'fs'\nimport chokidar from 'chokidar'\nimport {loadPackageJson} from '../load-package-json'\nimport ncc from '@vercel/ncc'\nimport path from 'path'\n\nexport interface BundlerBuildOptions {\n getTypeDefs: () => string\n watch?: boolean\n onWatch?: () => void\n}\n\nexport class Bundler {\n sfiFilePath: string\n outputDir: string\n\n constructor(sfiFilePath: string, outputDir: string = './.pylon') {\n this.sfiFilePath = sfiFilePath\n this.outputDir = outputDir\n }\n\n public async build(options: BundlerBuildOptions): Promise<void> {\n this.reportStatus('start')\n\n const build = async () => {\n const external = new Set<string>([\n '@cronitio/pylon',\n 'hono',\n '@prisma/client',\n 'openid-client',\n 'hono/http-exception'\n ])\n\n // Read \"external\" from package.json\n const packageJson = await loadPackageJson()\n\n if (packageJson.pylon && packageJson.pylon.external) {\n packageJson.pylon.external.forEach((externalPackage: string) => {\n external.add(externalPackage)\n })\n }\n\n const nccOptions = {\n quiet: true,\n externals: Array.from(external),\n sourceMap: true, // Generate sourcemap\n transpileOnly: true\n }\n\n const inputPath = path.join(process.cwd(), this.sfiFilePath)\n const dir = path.join(process.cwd(), this.outputDir)\n\n const {code, map, assets} = await ncc(inputPath, nccOptions)\n\n fs.mkdirSync(dir, {\n recursive: true\n })\n\n fs.writeFileSync(`${dir}/index.js`, code)\n\n Object.entries(assets).forEach(([name, asset]) => {\n const source = (asset as any).source\n\n if (typeof source === 'string') {\n fs.writeFileSync(`${dir}/${name}`, source)\n } else if (typeof source === 'object') {\n fs.writeFileSync(`${dir}/${name}`, Buffer.from(source))\n }\n })\n\n // attach typeDefs to the output\n this.prependTypeDefs(options.getTypeDefs())\n\n this.reportStatus('done')\n }\n\n if (options.watch) {\n chokidar.watch(this.sfiFilePath).on('change', async () => {\n await build()\n\n if (options.onWatch) {\n options.onWatch()\n }\n })\n }\n\n await build()\n }\n\n private prependTypeDefs(typeDefs: string): void {\n const outputFile = `${this.outputDir}/index.js`\n\n fs.writeFileSync(\n outputFile,\n `\\export const typeDefs = ${JSON.stringify(typeDefs)};\\n` +\n fs.readFileSync(outputFile)\n )\n }\n\n private reportStatus(status: 'start' | 'done'): void {\n if (status === 'start') {\n console.log('\\x1b[32m%s\\x1b[0m', `Bundling Pylon: ${this.sfiFilePath}...`)\n }\n\n if (status === 'done') {\n console.log('\\x1b[32m%s\\x1b[0m', 'Bundling Pylon: done')\n }\n }\n}\n",
6
+ "import path from 'path'\nimport {readFile} from 'fs/promises'\n\nexport async function loadPackageJson(): Promise<{\n baseURL?: string\n pylon?: {\n external?: string[]\n }\n}> {\n const packageJsonPath = path.resolve(process.cwd(), 'package.json')\n\n const file = await readFile(packageJsonPath)\n const packageJson = JSON.parse(file.toString())\n return packageJson\n}\n",
7
+ "// bundler.ts\nimport fs from 'fs'\nimport chokidar from 'chokidar'\nimport {loadPackageJson} from '../load-package-json'\nimport ncc from '@vercel/ncc'\nimport path from 'path'\n\nexport interface BundlerBuildOptions {\n getTypeDefs: () => string\n watch?: boolean\n onWatch?: () => void\n}\n\nexport class Bundler {\n sfiFilePath: string\n outputDir: string\n\n constructor(sfiFilePath: string, outputDir: string = './.pylon') {\n this.sfiFilePath = sfiFilePath\n this.outputDir = outputDir\n }\n\n public async build(options: BundlerBuildOptions): Promise<void> {\n this.reportStatus('start')\n\n const build = async () => {\n const external = new Set<string>([\n '@cronitio/pylon',\n 'hono',\n '@prisma/client',\n 'openid-client',\n 'hono/http-exception'\n ])\n\n // Read \"external\" from package.json\n const packageJson = await loadPackageJson()\n\n if (packageJson.pylon && packageJson.pylon.external) {\n packageJson.pylon.external.forEach((externalPackage: string) => {\n external.add(externalPackage)\n })\n }\n\n const nccOptions = {\n quiet: true,\n externals: Array.from(external),\n sourceMap: true, // Generate sourcemap\n transpileOnly: true\n }\n\n const inputPath = path.join(process.cwd(), this.sfiFilePath)\n const dir = path.join(process.cwd(), this.outputDir)\n\n const {code, map, assets} = await ncc(inputPath, nccOptions)\n\n fs.mkdirSync(dir, {\n recursive: true\n })\n\n fs.writeFileSync(`${dir}/index.js`, code)\n\n Object.entries(assets).forEach(([name, asset]) => {\n const source = (asset as any).source\n\n if (typeof source === 'string') {\n fs.writeFileSync(`${dir}/${name}`, source)\n } else if (typeof source === 'object') {\n fs.writeFileSync(`${dir}/${name}`, Buffer.from(source))\n }\n })\n\n // attach typeDefs to the output\n this.prependTypeDefs(options.getTypeDefs())\n\n this.reportStatus('done')\n }\n\n if (options.watch) {\n chokidar.watch(this.sfiFilePath).on('change', async () => {\n await build()\n\n if (options.onWatch) {\n options.onWatch()\n }\n })\n }\n\n await build()\n }\n\n private prependTypeDefs(typeDefs: string): void {\n const outputFile = `${this.outputDir}/index.js`\n\n fs.writeFileSync(\n outputFile,\n `\\export const typeDefs = ${JSON.stringify(typeDefs)};\\n` +\n fs.readFileSync(outputFile)\n )\n }\n\n private reportStatus(status: 'start' | 'done'): void {\n if (status === 'start') {\n console.log('\\x1b[32m%s\\x1b[0m', `Bundling Pylon: ${this.sfiFilePath}...`)\n }\n\n if (status === 'done') {\n console.log('\\x1b[32m%s\\x1b[0m', 'Bundling Pylon: done')\n }\n }\n}\n",
8
+ "import ts from 'typescript'\nimport {SchemaParser} from './schema-parser'\nimport path from 'path'\n\nexport class SchemaBuilder {\n private program: ts.Program\n private checker: ts.TypeChecker\n private sfiFile!: ts.SourceFile\n private sfi!: ts.Symbol\n private sfiFilePath: string\n\n constructor(sfiFilePath: string) {\n this.sfiFilePath = path.join(process.cwd(), sfiFilePath)\n\n this.program = ts.createProgram([this.sfiFilePath], {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.CommonJS,\n strict: true,\n esModuleInterop: true,\n skipLibCheck: false,\n forceConsistentCasingInFileNames: true,\n noImplicitAny: true,\n experimentalDecorators: true\n })\n\n this.checker = this.program.getTypeChecker()\n\n this.loadSfi()\n }\n\n private loadSfi() {\n const sourceFiles = this.program.getSourceFiles()\n const file = sourceFiles.find(file => file.fileName === this.sfiFilePath)\n\n if (!file) {\n throw new Error('Could not find index.ts (pylon entrypoint)')\n }\n\n this.sfiFile = file\n\n const sfiFileSymbol = this.checker.getSymbolAtLocation(file)!\n const sfiFileExports = this.checker.getExportsOfModule(sfiFileSymbol!)\n const sfiFileDefaultExport = sfiFileExports.find(\n exportSymbol => exportSymbol.escapedName === 'default'\n )\n\n if (!sfiFileDefaultExport) {\n throw new Error('Could not find default export')\n }\n\n this.sfi = sfiFileDefaultExport\n }\n\n public build() {\n const sfiType = this.checker.getTypeOfSymbolAtLocation(\n this.sfi,\n this.sfiFile\n )\n\n const plainResolversProperty = sfiType.getProperty('plainResolvers')\n\n if (!plainResolversProperty) {\n throw new Error('Could not find plainResolvers property')\n }\n\n const plainResolversType = this.checker.getTypeOfSymbolAtLocation(\n plainResolversProperty,\n this.sfiFile\n )\n\n const queryProperty = plainResolversType.getProperty('Query')\n const mutationProperty = plainResolversType.getProperty('Mutation')\n\n const queryType = queryProperty\n ? this.checker.getTypeOfSymbolAtLocation(queryProperty, this.sfiFile)\n : undefined\n const mutationType = mutationProperty\n ? this.checker.getTypeOfSymbolAtLocation(mutationProperty, this.sfiFile)\n : undefined\n\n const parser = new SchemaParser(this.checker, this.sfiFile)\n\n parser.parse({\n Query: queryType,\n Mutation: mutationType\n })\n\n return {\n typeDefs: parser.toString(),\n schema: parser.getSchema()\n }\n }\n}\n",
9
+ "import ts from 'typescript'\nimport {\n getPromiseType,\n getPublicPropertiesOfType,\n isFunction,\n isList,\n isPrimitive,\n isPromise\n} from './types-helper.js'\nimport {\n TypeDefinitionBuilder,\n Union as _Union,\n Enum as _Enum\n} from './type-definition-builder.js'\n\ntype Union = _Union & {\n description: string\n}\n\ntype Enum = _Enum & {\n description: string\n}\n\ninterface TypeDefinition {\n name: string\n isList: boolean\n isRequired: boolean\n description: string\n}\n\ninterface Field {\n name: string\n type: TypeDefinition\n}\n\ninterface Input {\n name: string\n description: string\n fields: Array<{\n name: string\n type: TypeDefinition\n }>\n}\n\ninterface Type {\n name: string\n description: string\n fields: Array<\n Field & {\n args: Array<{\n name: string\n type: TypeDefinition\n }>\n }\n >\n}\n\nexport interface Schema {\n types: Array<Type>\n inputs: Array<Input>\n unions: Array<Union>\n enums: Array<Enum>\n scalars: Array<string>\n}\n\ntype ReferenceSchemaType = {\n returnType: ts.Type\n args: {\n // value needs to be inputs type\n [key: string]: ts.Type\n }\n}\n\ninterface ReferenceSchema {\n types: Map<\n ts.Type,\n {\n [key: string]: ReferenceSchemaType\n }\n >\n inputs: ReferenceSchema['types']\n}\n\ninterface Index {\n Query?: ts.Type\n Mutation?: ts.Type\n}\n\nexport class SchemaParser {\n private schema: Schema\n private checker: ts.TypeChecker\n private sfiFile: ts.SourceFile\n private typeDefinitionBuilder: TypeDefinitionBuilder\n\n constructor(checker: ts.TypeChecker, sfiFile: ts.SourceFile) {\n this.schema = {\n types: [],\n inputs: [],\n unions: [],\n enums: [],\n scalars: [\n 'Number',\n 'Any',\n 'Object',\n 'File',\n 'Date',\n 'JSON',\n 'String',\n 'Boolean'\n ]\n }\n\n this.checker = checker\n this.sfiFile = sfiFile\n\n this.typeDefinitionBuilder = new TypeDefinitionBuilder(checker, this.schema)\n }\n\n public parse(index: Index) {\n const referenceSchema = this.makeReferenceSchema(index)\n\n for (const [type, properties] of referenceSchema.types) {\n let typeName: string | undefined = undefined\n\n if (index.Query === type) {\n typeName = 'Query'\n } else if (index.Mutation === type) {\n typeName = 'Mutation'\n }\n\n this.processSchemaReference(type, properties, typeName, 'types')\n }\n\n for (const [type, properties] of referenceSchema.inputs) {\n this.processSchemaReference(type, properties, undefined, 'inputs')\n }\n\n this.extractForbiddenFieldNamesFromSchema()\n\n this.schema.unions = this.typeDefinitionBuilder\n .getUnions()\n .map((union): Union => {\n return {\n ...union,\n description: this.getJsDocHeaderFromType(union.rawType)\n }\n })\n\n this.schema.enums = this.typeDefinitionBuilder\n .getEnums()\n .map((enumType): Enum => {\n return {\n ...enumType,\n description: this.getJsDocHeaderFromType(enumType.rawType)\n }\n })\n }\n\n public toString() {\n const {typeDefinitionToGraphQLType} = this.typeDefinitionBuilder\n\n const addDescription = (description: string) => {\n if (!description) return ''\n\n return `\"\"\"\\n${description}\\n\"\"\"\\n`\n }\n\n // build a valid GraphQL schema string from the schema object\n let schemaString = ''\n\n // loop over the input objects in the schema\n for (const input of this.schema.inputs) {\n // add the input object to the schema string\n\n schemaString += addDescription(input.description)\n schemaString += `input ${input.name} {\\n`\n\n // add a nop field to the input object if it has no fields\n if (input.fields.length === 0) {\n schemaString += `\\t_ : String\\n`\n }\n\n // loop over the fields in the input object\n for (const field of input.fields) {\n // add the field to the input object in the schema string\n schemaString += `${addDescription(field.type.description)}`\n schemaString += `\\t${field.name}: ${typeDefinitionToGraphQLType(\n field.type\n )}\\n`\n }\n\n schemaString += `}\\n`\n }\n\n // loop over the type objects in the schema\n for (const type of this.schema.types) {\n if (type.fields.length === 0) continue\n\n // add the type object to the schema string\n schemaString += addDescription(type.description)\n schemaString += `type ${type.name} {\\n`\n\n // loop over the fields in the type object\n for (const field of type.fields) {\n // build the argument list for the field if there is at least one argument\n let args = ''\n\n if (field.args.length > 0) {\n args = `(${field.args\n .map(\n arg =>\n `${addDescription(type.description)}${\n arg.name\n }: ${typeDefinitionToGraphQLType(arg.type)}`\n )\n .join(', ')})`\n }\n\n // add the field to the type object in the schema string\n schemaString += `${addDescription(field.type.description)}`\n schemaString += `${field.name}${args}: ${typeDefinitionToGraphQLType(\n field.type\n )}\\n`\n }\n\n schemaString += `}\\n`\n }\n\n // loop over the union objects in the schema\n for (const union of this.schema.unions) {\n // add the union object to the schema string\n schemaString += addDescription(union.description)\n schemaString += `union ${union.name} = ${union.types.join(' | ')}\\n`\n }\n\n // loop over the scalar objects in the schema\n for (const scalar of this.schema.scalars) {\n // add the scalar object to the schema string\n schemaString += `scalar ${scalar}\\n`\n }\n\n // loop over the enum objects in the schema\n for (const enumType of this.schema.enums) {\n // add the enum object to the schema string\n schemaString += addDescription(enumType.description)\n schemaString += `enum ${enumType.name} {\\n`\n\n // loop over the values in the enum object\n for (const value of enumType.values) {\n // add the value to the enum object in the schema string\n schemaString += `\\t${value}\\n`\n }\n\n schemaString += `}\\n`\n }\n\n // return the schema string\n return schemaString\n }\n\n public getSchema() {\n return this.schema\n }\n\n private processSchemaReference(\n type: ts.Type,\n properties: {[key: string]: ReferenceSchemaType},\n typeName?: string,\n processing: 'inputs' | 'types' = 'types'\n ) {\n const {getTypeDefinition} = this.typeDefinitionBuilder\n\n const isInputType = processing === 'inputs'\n\n const def = getTypeDefinition(type, {isInputType})\n\n const name = typeName || def.name\n\n let root = this.schema[processing].find(t => t.name === name)\n\n if (!root) {\n if (this.schema.scalars.includes(name)) {\n return\n } else {\n this.schema[processing].push({\n name,\n description: this.getJsDocHeaderFromType(type),\n fields: []\n })\n\n root = this.schema[processing][this.schema[processing].length - 1]!\n }\n }\n\n for (const [propertyName, property] of Object.entries(properties)) {\n const fieldType = property.returnType\n\n const fieldDef = getTypeDefinition(fieldType, {\n isInputType,\n propertyName\n })\n\n if (processing === 'types') {\n const field: Type['fields'][number] = {\n name: propertyName,\n type: {\n ...fieldDef,\n description: this.getJsDocHeaderFromType(fieldType)\n },\n args: []\n }\n\n if (property.args) {\n for (const [argName, arg] of Object.entries(property.args)) {\n const argType = arg\n\n const fieldDef = getTypeDefinition(argType, {\n isInputType: true,\n propertyName: argName\n })\n\n if (\n this.schema.scalars.includes(this.checker.typeToString(argType))\n ) {\n fieldDef.name = this.checker.typeToString(argType)\n }\n\n field.args.push({\n name: argName,\n type: {\n ...fieldDef,\n description: this.getJsDocHeaderFromType(argType)\n }\n })\n }\n }\n\n root.fields.push(field)\n } else if (processing === 'inputs') {\n const field: Input['fields'][number] = {\n name: propertyName,\n type: {\n ...fieldDef,\n description: this.getJsDocHeaderFromType(fieldType)\n }\n }\n\n if (!root.fields.find(f => f.name === field.name)) {\n root.fields.push(field)\n }\n }\n }\n }\n\n private getJsDocHeaderFromType = (type: ts.Type) => {\n let header = ''\n\n if (type.symbol) {\n const typeDeclaration = type.symbol.declarations?.[0] as unknown as\n | {\n jsDoc: ts.JSDoc[]\n }\n | undefined\n\n const comments = typeDeclaration?.jsDoc?.map(doc => doc.comment).join(' ')\n\n if (comments) {\n header = comments\n }\n\n const tags = type.symbol.getJsDocTags()\n const tagComments = tags\n ?.map(tag => `@${tag.name} ${tag.text?.map(t => t.text).join(' ')}`)\n .join('\\n')\n\n if (tagComments) {\n header = `${header} ${tagComments}`\n }\n }\n\n return header\n }\n\n /**\n * Extracts reserved field names from the schema by removing them from their respective types and inputs.\n */\n private extractForbiddenFieldNamesFromSchema(): void {\n // Define a regular expression to check if a field name is a valid GraphQL field name.\n const validFieldNameRegExp = /^[_A-Za-z][_0-9A-Za-z]*$/\n\n // Define a helper function to check if a field name is reserved.\n const isReserved = (name: string): boolean => {\n if (!validFieldNameRegExp.test(name)) {\n // console.warn(\n // `\\x1b[33mWarning: forbidden field name \"${name}\" detected\\x1b[0m`\n // )\n return true\n }\n // Fields starting with \"__\" are considered reserved.\n return name.startsWith('__')\n }\n\n // Loop over each type in the schema and remove any reserved fields.\n for (const type of this.schema.types) {\n type.fields = type.fields.filter(field => {\n if (isReserved(field.name)) {\n // console.warn(\n // `\\x1b[33mWarning: forbidden field \"${field.name}\" detected in type \"${type.name}\". This field will be excluded from the schema.\\x1b[0m`\n // )\n return false\n }\n return true\n })\n }\n\n // Loop over each input in the schema and remove any reserved fields.\n for (const input of this.schema.inputs) {\n input.fields = input.fields.filter(field => {\n if (isReserved(field.name)) {\n // console.warn(\n // `\\x1b[33mWarning: reserved field \"${field.name}\" detected in input \"${input.name}\". This field will be excluded from the schema.\\x1b[0m`\n // )\n return false\n }\n return true\n })\n }\n }\n\n private makeReferenceSchema(index: Index): ReferenceSchema {\n const referenceSchema: ReferenceSchema = {\n types: new Map(),\n inputs: new Map()\n }\n\n const recLoop = (\n type: ts.Type,\n info: {\n propetyName?: string\n parentType?: ts.Type\n } = {},\n processing: 'inputs' | 'types' = 'types',\n path: Array<string> = []\n ) => {\n if (referenceSchema[processing].has(type)) {\n return\n }\n\n // check if argType is a real type to ignore '[]'\n const wrongType = this.checker.typeToString(type) === '[]'\n\n if (wrongType) {\n return\n }\n\n // skip if scalar\n if (this.schema.scalars.includes(this.checker.typeToString(type))) {\n return\n }\n\n if (isPrimitive(type)) {\n return\n }\n\n if (isPromise(type)) {\n // skip if input\n if (processing === 'inputs') {\n return\n }\n\n const promiseType = getPromiseType(type)\n\n if (promiseType) {\n recLoop(promiseType, info, processing, [...path, 'PROMISE'])\n }\n\n return\n }\n\n if (type.isUnion()) {\n if (processing === 'types') {\n type.types.forEach(t => {\n // if null or undefined, skip\n if (\n t.flags & ts.TypeFlags.Null ||\n t.flags & ts.TypeFlags.Undefined ||\n isPrimitive(t)\n ) {\n return\n }\n\n recLoop(t, info, processing, [\n ...path,\n t.symbol?.getName() || `N/A ${this.checker.typeToString(t)}`\n ])\n })\n } else {\n let properties = getPublicPropertiesOfType(\n this.checker,\n type.getNonNullableType()\n )\n\n if (properties.length === 0) {\n //get first union type of non nullable type\n const nt = type.getNonNullableType()\n if (nt.isUnion()) {\n properties = getPublicPropertiesOfType(this.checker, nt.types[0]!)\n }\n }\n\n if (properties.length > 0) {\n if (!referenceSchema[processing].has(type)) {\n referenceSchema[processing].set(type, {})\n }\n\n // Go through all properties of the union type and add them to the reference schema\n properties.forEach(property => {\n const propertyType = this.checker.getTypeOfSymbolAtLocation(\n property,\n this.sfiFile\n )\n\n if (!isFunction(propertyType)) {\n referenceSchema[processing].get(type)![\n property.escapedName as string\n ] = {\n returnType: propertyType,\n args: {}\n }\n\n recLoop(\n propertyType,\n {\n propetyName: property.escapedName as string,\n parentType: type\n },\n processing,\n [...path, property.escapedName as string]\n )\n }\n })\n }\n }\n } else if (isFunction(type)) {\n // skip fn for inputs\n if (processing === 'inputs') {\n return\n }\n\n if (!info.parentType) {\n throw new Error('Cannot have a function without a parent type')\n }\n\n const signature = type.getCallSignatures()[0]\n const args = signature?.getParameters() || []\n const returnType = signature?.getReturnType()\n\n const schemaType = referenceSchema[processing].get(info.parentType)![\n info.propetyName!\n ]!\n\n args.forEach(arg => {\n if (\n arg.valueDeclaration &&\n ts.isParameter(arg.valueDeclaration) &&\n arg.valueDeclaration.dotDotDotToken\n ) {\n console.warn(\n 'Cannot handle rest parameters yet, skipping',\n arg.escapedName\n )\n } else {\n const argType = this.checker.getTypeOfSymbolAtLocation(\n arg,\n this.sfiFile\n )\n\n // set args to empty object if not set\n if (schemaType.args) {\n schemaType.args[arg.escapedName as string] = argType\n\n recLoop(\n argType,\n {\n parentType: type\n },\n 'inputs',\n [...path, arg.escapedName as string]\n )\n }\n }\n })\n\n if (returnType) {\n recLoop(returnType, info, processing, [...path, 'RETURN_TYPE'])\n }\n } else if (isList(this.checker, type)) {\n const itemType = this.checker.getIndexTypeOfType(\n type,\n ts.IndexKind.Number\n )\n\n if (itemType && !isPrimitive(itemType)) {\n if (!referenceSchema[processing].has(type)) {\n referenceSchema[processing].set(type, {})\n }\n\n recLoop(itemType, info, processing, [...path, 'ITEM_TYPE'])\n }\n } else if (!isPrimitive(type)) {\n const properties = getPublicPropertiesOfType(this.checker, type)\n\n if (!referenceSchema[processing].has(type)) {\n referenceSchema[processing].set(type, {})\n }\n\n properties.forEach(property => {\n const propertyType = this.checker.getTypeOfSymbolAtLocation(\n property,\n this.sfiFile\n )\n\n if (\n !referenceSchema[processing].get(type)![\n property.escapedName as string\n ]\n ) {\n referenceSchema[processing].get(type)![\n property.escapedName as string\n ] = {\n returnType: propertyType,\n args: {}\n }\n }\n\n recLoop(\n propertyType,\n {\n propetyName: property.escapedName as string,\n parentType: type\n },\n processing,\n [...path, property.escapedName as string]\n )\n })\n }\n }\n\n if (index.Query) {\n recLoop(index.Query)\n }\n\n if (index.Mutation) {\n recLoop(index.Mutation)\n }\n\n return referenceSchema\n }\n}\n",
10
+ "import ts from 'typescript'\n\nexport const isEmptyObject = (type: ts.Type) => {\n return (\n type.flags & ts.TypeFlags.Object &&\n type.getProperties().length === 0 &&\n type.getCallSignatures().length === 0\n )\n}\n\nexport const isList = (checker: ts.TypeChecker, type: ts.Type) => {\n const typeNode = checker.typeToTypeNode(type, undefined, undefined)\n\n const is = !!(\n typeNode &&\n (typeNode.kind === ts.SyntaxKind.ArrayType ||\n typeNode.kind === ts.SyntaxKind.TupleType)\n )\n\n return is\n}\n\nexport const isBoolean = (type: ts.Type) => {\n // check if is union and all types are boolean\n if (type.isUnion()) {\n const isAllBooleans = type.types.every(\n t => t.flags & ts.TypeFlags.BooleanLiteral\n )\n\n return isAllBooleans\n }\n\n return false\n}\n\nexport const isPrimitive = (type: ts.Type) => {\n const primitive =\n type.flags & ts.TypeFlags.String ||\n type.flags & ts.TypeFlags.Number ||\n type.flags & ts.TypeFlags.Boolean ||\n type.flags & ts.TypeFlags.StringLiteral ||\n type.flags & ts.TypeFlags.NumberLiteral ||\n type.flags & ts.TypeFlags.BooleanLiteral\n\n return primitive\n}\n\nexport const isFunction = (type: ts.Type) => {\n return type.getCallSignatures().length > 0\n}\n\nexport const isEnum = (type: ts.Type) => {\n if (isPrimitive(type)) return false\n\n const isUnion = (type as ts.UnionType).types?.length > 1\n\n if (isUnion) {\n // check if all types are primitives\n const isAllPrimitives = (type as ts.UnionType).types.every(\n t =>\n t.flags & ts.TypeFlags.StringLiteral ||\n t.flags & ts.TypeFlags.NumberLiteral ||\n t.flags & ts.TypeFlags.BooleanLiteral\n )\n\n if (isAllPrimitives) {\n return true\n }\n }\n\n return false\n}\n\nexport const isPromise = (type: ts.Type) => {\n // check if type is a promise\n return type.getSymbol()?.getName() === 'Promise'\n}\n\nexport const getPromiseType = (type: ts.Type) => {\n let objectType = type as ts.ObjectType\n\n if (objectType.objectFlags & ts.ObjectFlags.Reference) {\n const reference = type as ts.TypeReference\n const typeArguments = reference.typeArguments\n if (typeArguments && typeArguments.length > 0) {\n return typeArguments[0]\n }\n }\n}\n\nexport const excludeNullUndefinedFromType = (type: ts.Type) => {\n if (!type.isUnion())\n return {\n type,\n wasOptional: false\n }\n\n const types = type.types.filter(\n t => !(t.flags & ts.TypeFlags.Undefined || t.flags & ts.TypeFlags.Null)\n )\n\n const wasOptional = types.length !== type.types.length\n\n return {\n type: type.getNonNullableType(),\n wasOptional\n }\n}\n\nexport function getPublicPropertiesOfType(\n checker: ts.TypeChecker,\n _type: ts.Type\n): ts.Symbol[] {\n let type = _type\n\n if (type.isUnion()) {\n // Filter out array types from the union if their non-array counterparts are also present\n const nonArrayTypes = type.types.filter(\n t =>\n !(\n t.flags & ts.TypeFlags.Object &&\n (t as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference &&\n (t as ts.TypeReference).typeArguments?.length\n )\n )\n const arrayElementTypes = type.types\n .filter(\n t =>\n t.flags & ts.TypeFlags.Object &&\n (t as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference &&\n (t as ts.TypeReference).typeArguments?.length\n )\n .map(t => (t as ts.TypeReference).typeArguments![0])\n const filteredTypes = nonArrayTypes.filter(\n t => !arrayElementTypes.some(et => et === t)\n )\n // .filter(t => {\n // // filter out primitive types\n // return !isPrimitive(t)\n // })\n\n // Create a new type that is the union of all the types in the union\n // This is necessary because the type checker will not resolve properties\n // that are only present on some types in the union\n // getUnionType is not available in TS 3.7, so we have to use the private API\n type = (checker as any).getUnionType(filteredTypes) as ts.Type\n }\n\n const properties: ts.Symbol[] = []\n\n // if type is a primitive, return empty array\n if (isList(checker, type) || isPrimitive(type) || isEnum(type))\n return properties\n\n let typeProperties: ts.Symbol[] = []\n\n typeProperties =\n (type as any).resolvedProperties || type.getProperties() || []\n\n for (const prop of typeProperties) {\n const isPublic =\n prop.valueDeclaration &&\n !(\n ts.getCombinedModifierFlags(prop.valueDeclaration) &\n ts.ModifierFlags.Private\n ) &&\n !prop.getName().startsWith('#')\n\n if (isPublic === false) {\n continue\n }\n\n properties.push(prop)\n }\n\n return properties\n}\n\nexport const safeTypeName = (name: string) => {\n return name.replace(/[^0-9a-zA-Z_]/g, '_')\n}\n",
11
+ "import ts from 'typescript'\n\nexport const isEmptyObject = (type: ts.Type) => {\n return (\n type.flags & ts.TypeFlags.Object &&\n type.getProperties().length === 0 &&\n type.getCallSignatures().length === 0\n )\n}\n\nexport const isList = (checker: ts.TypeChecker, type: ts.Type) => {\n const typeNode = checker.typeToTypeNode(type, undefined, undefined)\n\n const is = !!(\n typeNode &&\n (typeNode.kind === ts.SyntaxKind.ArrayType ||\n typeNode.kind === ts.SyntaxKind.TupleType)\n )\n\n return is\n}\n\nexport const isBoolean = (type: ts.Type) => {\n // check if is union and all types are boolean\n if (type.isUnion()) {\n const isAllBooleans = type.types.every(\n t => t.flags & ts.TypeFlags.BooleanLiteral\n )\n\n return isAllBooleans\n }\n\n return false\n}\n\nexport const isPrimitive = (type: ts.Type) => {\n const primitive =\n type.flags & ts.TypeFlags.String ||\n type.flags & ts.TypeFlags.Number ||\n type.flags & ts.TypeFlags.Boolean ||\n type.flags & ts.TypeFlags.StringLiteral ||\n type.flags & ts.TypeFlags.NumberLiteral ||\n type.flags & ts.TypeFlags.BooleanLiteral\n\n return primitive\n}\n\nexport const isFunction = (type: ts.Type) => {\n return type.getCallSignatures().length > 0\n}\n\nexport const isEnum = (type: ts.Type) => {\n if (isPrimitive(type)) return false\n\n const isUnion = (type as ts.UnionType).types?.length > 1\n\n if (isUnion) {\n // check if all types are primitives\n const isAllPrimitives = (type as ts.UnionType).types.every(\n t =>\n t.flags & ts.TypeFlags.StringLiteral ||\n t.flags & ts.TypeFlags.NumberLiteral ||\n t.flags & ts.TypeFlags.BooleanLiteral\n )\n\n if (isAllPrimitives) {\n return true\n }\n }\n\n return false\n}\n\nexport const isPromise = (type: ts.Type) => {\n // check if type is a promise\n return type.getSymbol()?.getName() === 'Promise'\n}\n\nexport const getPromiseType = (type: ts.Type) => {\n let objectType = type as ts.ObjectType\n\n if (objectType.objectFlags & ts.ObjectFlags.Reference) {\n const reference = type as ts.TypeReference\n const typeArguments = reference.typeArguments\n if (typeArguments && typeArguments.length > 0) {\n return typeArguments[0]\n }\n }\n}\n\nexport const excludeNullUndefinedFromType = (type: ts.Type) => {\n if (!type.isUnion())\n return {\n type,\n wasOptional: false\n }\n\n const types = type.types.filter(\n t => !(t.flags & ts.TypeFlags.Undefined || t.flags & ts.TypeFlags.Null)\n )\n\n const wasOptional = types.length !== type.types.length\n\n return {\n type: type.getNonNullableType(),\n wasOptional\n }\n}\n\nexport function getPublicPropertiesOfType(\n checker: ts.TypeChecker,\n _type: ts.Type\n): ts.Symbol[] {\n let type = _type\n\n if (type.isUnion()) {\n // Filter out array types from the union if their non-array counterparts are also present\n const nonArrayTypes = type.types.filter(\n t =>\n !(\n t.flags & ts.TypeFlags.Object &&\n (t as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference &&\n (t as ts.TypeReference).typeArguments?.length\n )\n )\n const arrayElementTypes = type.types\n .filter(\n t =>\n t.flags & ts.TypeFlags.Object &&\n (t as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference &&\n (t as ts.TypeReference).typeArguments?.length\n )\n .map(t => (t as ts.TypeReference).typeArguments![0])\n const filteredTypes = nonArrayTypes.filter(\n t => !arrayElementTypes.some(et => et === t)\n )\n // .filter(t => {\n // // filter out primitive types\n // return !isPrimitive(t)\n // })\n\n // Create a new type that is the union of all the types in the union\n // This is necessary because the type checker will not resolve properties\n // that are only present on some types in the union\n // getUnionType is not available in TS 3.7, so we have to use the private API\n type = (checker as any).getUnionType(filteredTypes) as ts.Type\n }\n\n const properties: ts.Symbol[] = []\n\n // if type is a primitive, return empty array\n if (isList(checker, type) || isPrimitive(type) || isEnum(type))\n return properties\n\n let typeProperties: ts.Symbol[] = []\n\n typeProperties =\n (type as any).resolvedProperties || type.getProperties() || []\n\n for (const prop of typeProperties) {\n const isPublic =\n prop.valueDeclaration &&\n !(\n ts.getCombinedModifierFlags(prop.valueDeclaration) &\n ts.ModifierFlags.Private\n ) &&\n !prop.getName().startsWith('#')\n\n if (isPublic === false) {\n continue\n }\n\n properties.push(prop)\n }\n\n return properties\n}\n\nexport const safeTypeName = (name: string) => {\n return name.replace(/[^0-9a-zA-Z_]/g, '_')\n}\n",
12
+ "import ts from 'typescript'\nimport {\n excludeNullUndefinedFromType,\n getPromiseType,\n isEmptyObject,\n isEnum,\n isFunction,\n isList,\n isPrimitive,\n isPromise,\n safeTypeName\n} from './types-helper.js'\nimport {Schema} from './schema-parser.js'\n\nexport interface Union {\n name: string\n rawType: ts.Type\n types: Array<string>\n}\n\nexport interface Enum {\n name: string\n rawType: ts.Type\n values: Array<string>\n}\n\ninterface FieldDefinition {\n name: string\n isList: boolean\n isRequired: boolean\n isListRequired?: boolean\n}\n\nexport class TypeDefinitionBuilder {\n private checker: ts.TypeChecker\n private schema: Schema\n\n private typesNameMap: Map<ts.Type, string> = new Map()\n private inputsNameMap: Map<ts.Type, string> = new Map()\n\n private unions: Array<Union> = []\n private enums: Array<Enum> = []\n\n constructor(checker: ts.TypeChecker, schema: Schema) {\n this.checker = checker\n this.schema = schema\n }\n\n getTypeDefinition = (\n rawType: ts.Type,\n options: {\n isInputType?: boolean\n isRequired?: boolean\n propertyName?: string\n } = {\n isInputType: false\n }\n ): FieldDefinition => {\n const {type, wasOptional} = excludeNullUndefinedFromType(rawType)\n\n if (isPromise(type)) {\n const promiseType = getPromiseType(type)\n if (promiseType) {\n return this.getTypeDefinition(promiseType, options)\n }\n }\n\n const isRequired =\n options.isRequired !== undefined ? options.isRequired : !wasOptional\n\n if (isEmptyObject(type)) {\n return {\n name: 'Object',\n isList: false,\n isRequired\n }\n }\n\n let nameMap = options.isInputType ? this.inputsNameMap : this.typesNameMap\n\n if (nameMap.has(type)) {\n const typeName = nameMap.get(type) as string\n\n return {\n name: typeName,\n isList: false,\n isRequired\n }\n }\n\n let typeName: string | undefined =\n type.aliasSymbol?.escapedName?.toString() ||\n type.symbol?.escapedName.toString()\n\n if (typeName === '__type' || typeName === '__object') {\n if (!options.propertyName) {\n typeName = undefined\n } else {\n const capitalizedPropertyName =\n options.propertyName.charAt(0).toUpperCase() +\n options.propertyName.slice(1)\n\n typeName = capitalizedPropertyName\n }\n }\n\n if (typeName) {\n typeName = safeTypeName(typeName)\n }\n\n const duplicateName = (name: string) => {\n // find name in typeNameMap\n return Array.from(nameMap.values()).includes(name)\n }\n\n if (typeName) {\n if (options.isInputType) {\n typeName = `${typeName}Input`\n }\n\n let i = 1\n let originalTypeName = typeName\n while (duplicateName(typeName as string)) {\n typeName = `${originalTypeName}_${i}`\n\n i++\n }\n }\n\n if (typeName && this.schema.scalars.includes(typeName)) {\n return {\n name: typeName,\n isList: false,\n isRequired\n }\n }\n\n if (isList(this.checker, type)) {\n const listType = this.checker.getIndexTypeOfType(\n type,\n ts.IndexKind.Number\n )\n\n if (listType) {\n const def = this.getTypeDefinition(listType, options)\n\n return {\n name: def.name,\n isList: true,\n isRequired: def.isRequired,\n isListRequired: isRequired\n }\n }\n } else if (isEnum(type)) {\n const typeNode = this.checker.typeToTypeNode(\n type,\n undefined,\n undefined\n ) as any | undefined\n\n const types = (type as ts.UnionType).types.filter(\n t => t.flags & ts.TypeFlags.StringLiteral\n )\n\n // enumerate all members of the enum\n const members = types.map((t: ts.Type) => {\n if (t.isLiteral()) {\n const name = t.value?.toString()\n\n if (!name) {\n throw new Error('Enum member name is undefined')\n }\n\n return safeTypeName(name)\n }\n\n throw new Error('Invalid type for enum member')\n })\n\n if (members.length > 0) {\n typeName = typeName || typeNode.typeName?.symbol?.escapedName\n\n if (!typeName) {\n typeName = members.join('_').toUpperCase()\n typeName = options.isInputType ? `${typeName}Input` : typeName\n }\n\n this.enums.push({\n name: typeName,\n values: members,\n rawType: type\n })\n }\n }\n // handle primitives\n else if (isPrimitive(type)) {\n let typeName = this.checker.typeToString(type)\n\n if (type.flags & ts.TypeFlags.StringLiteral) {\n typeName = 'String'\n } else if (type.flags & ts.TypeFlags.NumberLiteral) {\n typeName = 'Number'\n } else if (type.flags & ts.TypeFlags.BooleanLiteral) {\n typeName = 'Boolean'\n } else if (type.flags & ts.TypeFlags.String) {\n typeName = 'String'\n } else if (type.flags & ts.TypeFlags.Number) {\n typeName = 'Number'\n } else if (type.flags & ts.TypeFlags.Boolean) {\n typeName = 'Boolean'\n }\n\n return {\n name: safeTypeName(typeName),\n isList: false,\n isRequired\n }\n } else if (type.isIntersection()) {\n const intersectionTypes = type.types\n\n const typeNames = intersectionTypes.map(t => {\n const typeDef = this.getTypeDefinition(t, options)\n\n return typeDef.name\n })\n\n typeName = safeTypeName(typeName || typeNames.join('And'))\n } else if (type.isUnion()) {\n const unionTypes = type.types\n const required = type.types.length === unionTypes.length\n\n const hasPrimitivesOrEnum = unionTypes.some(t => {\n // If t is a array take the element type\n const type = isList(this.checker, t)\n ? this.checker.getIndexTypeOfType(t, ts.IndexKind.Number)\n : t\n\n if (!type) {\n // Return true if the type is undefined because we don't know what it is\n // Marking it as a primitive or enum will make it more error prone\n return true\n }\n\n return isPrimitive(type) || isEnum(type)\n })\n\n const unionTypeDefs = [\n ...new Set(\n unionTypes.map(t =>\n this.getTypeDefinition(t, {...options, isRequired: required})\n )\n )\n ]\n\n // If the union contains a array of some type, remove the type from the union because\n // the array type will be handled by the list type\n\n const listTypes = unionTypeDefs.filter(t => t.isList)\n\n // Check if the union contains the same type as a list\n for (const listType of listTypes) {\n const index = unionTypeDefs.findIndex(\n t => t.name === listType.name && !t.isList\n )\n\n if (index > -1) {\n unionTypeDefs.splice(index, 1)\n }\n }\n\n const typeNames = unionTypeDefs.map(t => t.name)\n\n typeName = safeTypeName(typeName || typeNames.join('Or'))\n\n if (\n unionTypeDefs.length > 1 &&\n !options.isInputType &&\n !hasPrimitivesOrEnum\n ) {\n this.unions.push({\n name: typeName,\n types: typeNames,\n rawType: type\n })\n } else {\n // We only care about the first type in the union since GraphQL doesn't support unions of input types\n const typeDef = unionTypeDefs[0]\n\n if (!typeDef) {\n throw new Error('Cannot get type definition')\n }\n\n // If the types contain a list of the same type as the first type, then we can make the first type a list\n const isList = unionTypeDefs.some(t => {\n return t.isList && t.name === typeDef.name\n })\n\n return {\n name: typeDef.name,\n isList,\n isRequired\n }\n }\n }\n // handle functions\n else if (isFunction(type)) {\n const signature = type.getCallSignatures()[0]\n let returnType = signature?.getReturnType()\n\n if (returnType) {\n if (isPromise(returnType)) {\n const pt = getPromiseType(returnType)\n\n if (pt) {\n returnType = pt\n }\n }\n }\n\n if (returnType) {\n const def = this.getTypeDefinition(returnType, options)\n\n return def\n }\n }\n\n if (!typeName) {\n typeName = 'Any'\n }\n\n nameMap.set(type, typeName)\n\n return {\n name: typeName,\n isList: false,\n isRequired\n }\n }\n\n public getUnions(): Array<Union> {\n return this.unions\n }\n\n public getEnums(): Array<Enum> {\n return this.enums\n }\n\n public typeDefinitionToGraphQLType = (\n typeDefinition: FieldDefinition\n ): string => {\n let type = typeDefinition.name\n const {isList, isRequired, isListRequired} = typeDefinition\n\n if (isRequired) {\n type = `${type}!`\n }\n\n if (isList) {\n type = `[${type}]`\n\n if (isListRequired) {\n type = `${type}!`\n }\n }\n\n return type\n }\n}\n",
13
+ "import ts from 'typescript'\nimport {\n excludeNullUndefinedFromType,\n getPromiseType,\n isEmptyObject,\n isEnum,\n isFunction,\n isList,\n isPrimitive,\n isPromise,\n safeTypeName\n} from './types-helper.js'\nimport {Schema} from './schema-parser.js'\n\nexport interface Union {\n name: string\n rawType: ts.Type\n types: Array<string>\n}\n\nexport interface Enum {\n name: string\n rawType: ts.Type\n values: Array<string>\n}\n\ninterface FieldDefinition {\n name: string\n isList: boolean\n isRequired: boolean\n isListRequired?: boolean\n}\n\nexport class TypeDefinitionBuilder {\n private checker: ts.TypeChecker\n private schema: Schema\n\n private typesNameMap: Map<ts.Type, string> = new Map()\n private inputsNameMap: Map<ts.Type, string> = new Map()\n\n private unions: Array<Union> = []\n private enums: Array<Enum> = []\n\n constructor(checker: ts.TypeChecker, schema: Schema) {\n this.checker = checker\n this.schema = schema\n }\n\n getTypeDefinition = (\n rawType: ts.Type,\n options: {\n isInputType?: boolean\n isRequired?: boolean\n propertyName?: string\n } = {\n isInputType: false\n }\n ): FieldDefinition => {\n const {type, wasOptional} = excludeNullUndefinedFromType(rawType)\n\n if (isPromise(type)) {\n const promiseType = getPromiseType(type)\n if (promiseType) {\n return this.getTypeDefinition(promiseType, options)\n }\n }\n\n const isRequired =\n options.isRequired !== undefined ? options.isRequired : !wasOptional\n\n if (isEmptyObject(type)) {\n return {\n name: 'Object',\n isList: false,\n isRequired\n }\n }\n\n let nameMap = options.isInputType ? this.inputsNameMap : this.typesNameMap\n\n if (nameMap.has(type)) {\n const typeName = nameMap.get(type) as string\n\n return {\n name: typeName,\n isList: false,\n isRequired\n }\n }\n\n let typeName: string | undefined =\n type.aliasSymbol?.escapedName?.toString() ||\n type.symbol?.escapedName.toString()\n\n if (typeName === '__type' || typeName === '__object') {\n if (!options.propertyName) {\n typeName = undefined\n } else {\n const capitalizedPropertyName =\n options.propertyName.charAt(0).toUpperCase() +\n options.propertyName.slice(1)\n\n typeName = capitalizedPropertyName\n }\n }\n\n if (typeName) {\n typeName = safeTypeName(typeName)\n }\n\n const duplicateName = (name: string) => {\n // find name in typeNameMap\n return Array.from(nameMap.values()).includes(name)\n }\n\n if (typeName) {\n if (options.isInputType) {\n typeName = `${typeName}Input`\n }\n\n let i = 1\n let originalTypeName = typeName\n while (duplicateName(typeName as string)) {\n typeName = `${originalTypeName}_${i}`\n\n i++\n }\n }\n\n if (typeName && this.schema.scalars.includes(typeName)) {\n return {\n name: typeName,\n isList: false,\n isRequired\n }\n }\n\n if (isList(this.checker, type)) {\n const listType = this.checker.getIndexTypeOfType(\n type,\n ts.IndexKind.Number\n )\n\n if (listType) {\n const def = this.getTypeDefinition(listType, options)\n\n return {\n name: def.name,\n isList: true,\n isRequired: def.isRequired,\n isListRequired: isRequired\n }\n }\n } else if (isEnum(type)) {\n const typeNode = this.checker.typeToTypeNode(\n type,\n undefined,\n undefined\n ) as any | undefined\n\n const types = (type as ts.UnionType).types.filter(\n t => t.flags & ts.TypeFlags.StringLiteral\n )\n\n // enumerate all members of the enum\n const members = types.map((t: ts.Type) => {\n if (t.isLiteral()) {\n const name = t.value?.toString()\n\n if (!name) {\n throw new Error('Enum member name is undefined')\n }\n\n return safeTypeName(name)\n }\n\n throw new Error('Invalid type for enum member')\n })\n\n if (members.length > 0) {\n typeName = typeName || typeNode.typeName?.symbol?.escapedName\n\n if (!typeName) {\n typeName = members.join('_').toUpperCase()\n typeName = options.isInputType ? `${typeName}Input` : typeName\n }\n\n this.enums.push({\n name: typeName,\n values: members,\n rawType: type\n })\n }\n }\n // handle primitives\n else if (isPrimitive(type)) {\n let typeName = this.checker.typeToString(type)\n\n if (type.flags & ts.TypeFlags.StringLiteral) {\n typeName = 'String'\n } else if (type.flags & ts.TypeFlags.NumberLiteral) {\n typeName = 'Number'\n } else if (type.flags & ts.TypeFlags.BooleanLiteral) {\n typeName = 'Boolean'\n } else if (type.flags & ts.TypeFlags.String) {\n typeName = 'String'\n } else if (type.flags & ts.TypeFlags.Number) {\n typeName = 'Number'\n } else if (type.flags & ts.TypeFlags.Boolean) {\n typeName = 'Boolean'\n }\n\n return {\n name: safeTypeName(typeName),\n isList: false,\n isRequired\n }\n } else if (type.isIntersection()) {\n const intersectionTypes = type.types\n\n const typeNames = intersectionTypes.map(t => {\n const typeDef = this.getTypeDefinition(t, options)\n\n return typeDef.name\n })\n\n typeName = safeTypeName(typeName || typeNames.join('And'))\n } else if (type.isUnion()) {\n const unionTypes = type.types\n const required = type.types.length === unionTypes.length\n\n const hasPrimitivesOrEnum = unionTypes.some(t => {\n // If t is a array take the element type\n const type = isList(this.checker, t)\n ? this.checker.getIndexTypeOfType(t, ts.IndexKind.Number)\n : t\n\n if (!type) {\n // Return true if the type is undefined because we don't know what it is\n // Marking it as a primitive or enum will make it more error prone\n return true\n }\n\n return isPrimitive(type) || isEnum(type)\n })\n\n const unionTypeDefs = [\n ...new Set(\n unionTypes.map(t =>\n this.getTypeDefinition(t, {...options, isRequired: required})\n )\n )\n ]\n\n // If the union contains a array of some type, remove the type from the union because\n // the array type will be handled by the list type\n\n const listTypes = unionTypeDefs.filter(t => t.isList)\n\n // Check if the union contains the same type as a list\n for (const listType of listTypes) {\n const index = unionTypeDefs.findIndex(\n t => t.name === listType.name && !t.isList\n )\n\n if (index > -1) {\n unionTypeDefs.splice(index, 1)\n }\n }\n\n const typeNames = unionTypeDefs.map(t => t.name)\n\n typeName = safeTypeName(typeName || typeNames.join('Or'))\n\n if (\n unionTypeDefs.length > 1 &&\n !options.isInputType &&\n !hasPrimitivesOrEnum\n ) {\n this.unions.push({\n name: typeName,\n types: typeNames,\n rawType: type\n })\n } else {\n // We only care about the first type in the union since GraphQL doesn't support unions of input types\n const typeDef = unionTypeDefs[0]\n\n if (!typeDef) {\n throw new Error('Cannot get type definition')\n }\n\n // If the types contain a list of the same type as the first type, then we can make the first type a list\n const isList = unionTypeDefs.some(t => {\n return t.isList && t.name === typeDef.name\n })\n\n return {\n name: typeDef.name,\n isList,\n isRequired\n }\n }\n }\n // handle functions\n else if (isFunction(type)) {\n const signature = type.getCallSignatures()[0]\n let returnType = signature?.getReturnType()\n\n if (returnType) {\n if (isPromise(returnType)) {\n const pt = getPromiseType(returnType)\n\n if (pt) {\n returnType = pt\n }\n }\n }\n\n if (returnType) {\n const def = this.getTypeDefinition(returnType, options)\n\n return def\n }\n }\n\n if (!typeName) {\n typeName = 'Any'\n }\n\n nameMap.set(type, typeName)\n\n return {\n name: typeName,\n isList: false,\n isRequired\n }\n }\n\n public getUnions(): Array<Union> {\n return this.unions\n }\n\n public getEnums(): Array<Enum> {\n return this.enums\n }\n\n public typeDefinitionToGraphQLType = (\n typeDefinition: FieldDefinition\n ): string => {\n let type = typeDefinition.name\n const {isList, isRequired, isListRequired} = typeDefinition\n\n if (isRequired) {\n type = `${type}!`\n }\n\n if (isList) {\n type = `[${type}]`\n\n if (isListRequired) {\n type = `${type}!`\n }\n }\n\n return type\n }\n}\n",
14
+ "import ts from 'typescript'\nimport {\n getPromiseType,\n getPublicPropertiesOfType,\n isFunction,\n isList,\n isPrimitive,\n isPromise\n} from './types-helper.js'\nimport {\n TypeDefinitionBuilder,\n Union as _Union,\n Enum as _Enum\n} from './type-definition-builder.js'\n\ntype Union = _Union & {\n description: string\n}\n\ntype Enum = _Enum & {\n description: string\n}\n\ninterface TypeDefinition {\n name: string\n isList: boolean\n isRequired: boolean\n description: string\n}\n\ninterface Field {\n name: string\n type: TypeDefinition\n}\n\ninterface Input {\n name: string\n description: string\n fields: Array<{\n name: string\n type: TypeDefinition\n }>\n}\n\ninterface Type {\n name: string\n description: string\n fields: Array<\n Field & {\n args: Array<{\n name: string\n type: TypeDefinition\n }>\n }\n >\n}\n\nexport interface Schema {\n types: Array<Type>\n inputs: Array<Input>\n unions: Array<Union>\n enums: Array<Enum>\n scalars: Array<string>\n}\n\ntype ReferenceSchemaType = {\n returnType: ts.Type\n args: {\n // value needs to be inputs type\n [key: string]: ts.Type\n }\n}\n\ninterface ReferenceSchema {\n types: Map<\n ts.Type,\n {\n [key: string]: ReferenceSchemaType\n }\n >\n inputs: ReferenceSchema['types']\n}\n\ninterface Index {\n Query?: ts.Type\n Mutation?: ts.Type\n}\n\nexport class SchemaParser {\n private schema: Schema\n private checker: ts.TypeChecker\n private sfiFile: ts.SourceFile\n private typeDefinitionBuilder: TypeDefinitionBuilder\n\n constructor(checker: ts.TypeChecker, sfiFile: ts.SourceFile) {\n this.schema = {\n types: [],\n inputs: [],\n unions: [],\n enums: [],\n scalars: [\n 'Number',\n 'Any',\n 'Object',\n 'File',\n 'Date',\n 'JSON',\n 'String',\n 'Boolean'\n ]\n }\n\n this.checker = checker\n this.sfiFile = sfiFile\n\n this.typeDefinitionBuilder = new TypeDefinitionBuilder(checker, this.schema)\n }\n\n public parse(index: Index) {\n const referenceSchema = this.makeReferenceSchema(index)\n\n for (const [type, properties] of referenceSchema.types) {\n let typeName: string | undefined = undefined\n\n if (index.Query === type) {\n typeName = 'Query'\n } else if (index.Mutation === type) {\n typeName = 'Mutation'\n }\n\n this.processSchemaReference(type, properties, typeName, 'types')\n }\n\n for (const [type, properties] of referenceSchema.inputs) {\n this.processSchemaReference(type, properties, undefined, 'inputs')\n }\n\n this.extractForbiddenFieldNamesFromSchema()\n\n this.schema.unions = this.typeDefinitionBuilder\n .getUnions()\n .map((union): Union => {\n return {\n ...union,\n description: this.getJsDocHeaderFromType(union.rawType)\n }\n })\n\n this.schema.enums = this.typeDefinitionBuilder\n .getEnums()\n .map((enumType): Enum => {\n return {\n ...enumType,\n description: this.getJsDocHeaderFromType(enumType.rawType)\n }\n })\n }\n\n public toString() {\n const {typeDefinitionToGraphQLType} = this.typeDefinitionBuilder\n\n const addDescription = (description: string) => {\n if (!description) return ''\n\n return `\"\"\"\\n${description}\\n\"\"\"\\n`\n }\n\n // build a valid GraphQL schema string from the schema object\n let schemaString = ''\n\n // loop over the input objects in the schema\n for (const input of this.schema.inputs) {\n // add the input object to the schema string\n\n schemaString += addDescription(input.description)\n schemaString += `input ${input.name} {\\n`\n\n // add a nop field to the input object if it has no fields\n if (input.fields.length === 0) {\n schemaString += `\\t_ : String\\n`\n }\n\n // loop over the fields in the input object\n for (const field of input.fields) {\n // add the field to the input object in the schema string\n schemaString += `${addDescription(field.type.description)}`\n schemaString += `\\t${field.name}: ${typeDefinitionToGraphQLType(\n field.type\n )}\\n`\n }\n\n schemaString += `}\\n`\n }\n\n // loop over the type objects in the schema\n for (const type of this.schema.types) {\n if (type.fields.length === 0) continue\n\n // add the type object to the schema string\n schemaString += addDescription(type.description)\n schemaString += `type ${type.name} {\\n`\n\n // loop over the fields in the type object\n for (const field of type.fields) {\n // build the argument list for the field if there is at least one argument\n let args = ''\n\n if (field.args.length > 0) {\n args = `(${field.args\n .map(\n arg =>\n `${addDescription(type.description)}${\n arg.name\n }: ${typeDefinitionToGraphQLType(arg.type)}`\n )\n .join(', ')})`\n }\n\n // add the field to the type object in the schema string\n schemaString += `${addDescription(field.type.description)}`\n schemaString += `${field.name}${args}: ${typeDefinitionToGraphQLType(\n field.type\n )}\\n`\n }\n\n schemaString += `}\\n`\n }\n\n // loop over the union objects in the schema\n for (const union of this.schema.unions) {\n // add the union object to the schema string\n schemaString += addDescription(union.description)\n schemaString += `union ${union.name} = ${union.types.join(' | ')}\\n`\n }\n\n // loop over the scalar objects in the schema\n for (const scalar of this.schema.scalars) {\n // add the scalar object to the schema string\n schemaString += `scalar ${scalar}\\n`\n }\n\n // loop over the enum objects in the schema\n for (const enumType of this.schema.enums) {\n // add the enum object to the schema string\n schemaString += addDescription(enumType.description)\n schemaString += `enum ${enumType.name} {\\n`\n\n // loop over the values in the enum object\n for (const value of enumType.values) {\n // add the value to the enum object in the schema string\n schemaString += `\\t${value}\\n`\n }\n\n schemaString += `}\\n`\n }\n\n // return the schema string\n return schemaString\n }\n\n public getSchema() {\n return this.schema\n }\n\n private processSchemaReference(\n type: ts.Type,\n properties: {[key: string]: ReferenceSchemaType},\n typeName?: string,\n processing: 'inputs' | 'types' = 'types'\n ) {\n const {getTypeDefinition} = this.typeDefinitionBuilder\n\n const isInputType = processing === 'inputs'\n\n const def = getTypeDefinition(type, {isInputType})\n\n const name = typeName || def.name\n\n let root = this.schema[processing].find(t => t.name === name)\n\n if (!root) {\n if (this.schema.scalars.includes(name)) {\n return\n } else {\n this.schema[processing].push({\n name,\n description: this.getJsDocHeaderFromType(type),\n fields: []\n })\n\n root = this.schema[processing][this.schema[processing].length - 1]!\n }\n }\n\n for (const [propertyName, property] of Object.entries(properties)) {\n const fieldType = property.returnType\n\n const fieldDef = getTypeDefinition(fieldType, {\n isInputType,\n propertyName\n })\n\n if (processing === 'types') {\n const field: Type['fields'][number] = {\n name: propertyName,\n type: {\n ...fieldDef,\n description: this.getJsDocHeaderFromType(fieldType)\n },\n args: []\n }\n\n if (property.args) {\n for (const [argName, arg] of Object.entries(property.args)) {\n const argType = arg\n\n const fieldDef = getTypeDefinition(argType, {\n isInputType: true,\n propertyName: argName\n })\n\n if (\n this.schema.scalars.includes(this.checker.typeToString(argType))\n ) {\n fieldDef.name = this.checker.typeToString(argType)\n }\n\n field.args.push({\n name: argName,\n type: {\n ...fieldDef,\n description: this.getJsDocHeaderFromType(argType)\n }\n })\n }\n }\n\n root.fields.push(field)\n } else if (processing === 'inputs') {\n const field: Input['fields'][number] = {\n name: propertyName,\n type: {\n ...fieldDef,\n description: this.getJsDocHeaderFromType(fieldType)\n }\n }\n\n if (!root.fields.find(f => f.name === field.name)) {\n root.fields.push(field)\n }\n }\n }\n }\n\n private getJsDocHeaderFromType = (type: ts.Type) => {\n let header = ''\n\n if (type.symbol) {\n const typeDeclaration = type.symbol.declarations?.[0] as unknown as\n | {\n jsDoc: ts.JSDoc[]\n }\n | undefined\n\n const comments = typeDeclaration?.jsDoc?.map(doc => doc.comment).join(' ')\n\n if (comments) {\n header = comments\n }\n\n const tags = type.symbol.getJsDocTags()\n const tagComments = tags\n ?.map(tag => `@${tag.name} ${tag.text?.map(t => t.text).join(' ')}`)\n .join('\\n')\n\n if (tagComments) {\n header = `${header} ${tagComments}`\n }\n }\n\n return header\n }\n\n /**\n * Extracts reserved field names from the schema by removing them from their respective types and inputs.\n */\n private extractForbiddenFieldNamesFromSchema(): void {\n // Define a regular expression to check if a field name is a valid GraphQL field name.\n const validFieldNameRegExp = /^[_A-Za-z][_0-9A-Za-z]*$/\n\n // Define a helper function to check if a field name is reserved.\n const isReserved = (name: string): boolean => {\n if (!validFieldNameRegExp.test(name)) {\n // console.warn(\n // `\\x1b[33mWarning: forbidden field name \"${name}\" detected\\x1b[0m`\n // )\n return true\n }\n // Fields starting with \"__\" are considered reserved.\n return name.startsWith('__')\n }\n\n // Loop over each type in the schema and remove any reserved fields.\n for (const type of this.schema.types) {\n type.fields = type.fields.filter(field => {\n if (isReserved(field.name)) {\n // console.warn(\n // `\\x1b[33mWarning: forbidden field \"${field.name}\" detected in type \"${type.name}\". This field will be excluded from the schema.\\x1b[0m`\n // )\n return false\n }\n return true\n })\n }\n\n // Loop over each input in the schema and remove any reserved fields.\n for (const input of this.schema.inputs) {\n input.fields = input.fields.filter(field => {\n if (isReserved(field.name)) {\n // console.warn(\n // `\\x1b[33mWarning: reserved field \"${field.name}\" detected in input \"${input.name}\". This field will be excluded from the schema.\\x1b[0m`\n // )\n return false\n }\n return true\n })\n }\n }\n\n private makeReferenceSchema(index: Index): ReferenceSchema {\n const referenceSchema: ReferenceSchema = {\n types: new Map(),\n inputs: new Map()\n }\n\n const recLoop = (\n type: ts.Type,\n info: {\n propetyName?: string\n parentType?: ts.Type\n } = {},\n processing: 'inputs' | 'types' = 'types',\n path: Array<string> = []\n ) => {\n if (referenceSchema[processing].has(type)) {\n return\n }\n\n // check if argType is a real type to ignore '[]'\n const wrongType = this.checker.typeToString(type) === '[]'\n\n if (wrongType) {\n return\n }\n\n // skip if scalar\n if (this.schema.scalars.includes(this.checker.typeToString(type))) {\n return\n }\n\n if (isPrimitive(type)) {\n return\n }\n\n if (isPromise(type)) {\n // skip if input\n if (processing === 'inputs') {\n return\n }\n\n const promiseType = getPromiseType(type)\n\n if (promiseType) {\n recLoop(promiseType, info, processing, [...path, 'PROMISE'])\n }\n\n return\n }\n\n if (type.isUnion()) {\n if (processing === 'types') {\n type.types.forEach(t => {\n // if null or undefined, skip\n if (\n t.flags & ts.TypeFlags.Null ||\n t.flags & ts.TypeFlags.Undefined ||\n isPrimitive(t)\n ) {\n return\n }\n\n recLoop(t, info, processing, [\n ...path,\n t.symbol?.getName() || `N/A ${this.checker.typeToString(t)}`\n ])\n })\n } else {\n let properties = getPublicPropertiesOfType(\n this.checker,\n type.getNonNullableType()\n )\n\n if (properties.length === 0) {\n //get first union type of non nullable type\n const nt = type.getNonNullableType()\n if (nt.isUnion()) {\n properties = getPublicPropertiesOfType(this.checker, nt.types[0]!)\n }\n }\n\n if (properties.length > 0) {\n if (!referenceSchema[processing].has(type)) {\n referenceSchema[processing].set(type, {})\n }\n\n // Go through all properties of the union type and add them to the reference schema\n properties.forEach(property => {\n const propertyType = this.checker.getTypeOfSymbolAtLocation(\n property,\n this.sfiFile\n )\n\n if (!isFunction(propertyType)) {\n referenceSchema[processing].get(type)![\n property.escapedName as string\n ] = {\n returnType: propertyType,\n args: {}\n }\n\n recLoop(\n propertyType,\n {\n propetyName: property.escapedName as string,\n parentType: type\n },\n processing,\n [...path, property.escapedName as string]\n )\n }\n })\n }\n }\n } else if (isFunction(type)) {\n // skip fn for inputs\n if (processing === 'inputs') {\n return\n }\n\n if (!info.parentType) {\n throw new Error('Cannot have a function without a parent type')\n }\n\n const signature = type.getCallSignatures()[0]\n const args = signature?.getParameters() || []\n const returnType = signature?.getReturnType()\n\n const schemaType = referenceSchema[processing].get(info.parentType)![\n info.propetyName!\n ]!\n\n args.forEach(arg => {\n if (\n arg.valueDeclaration &&\n ts.isParameter(arg.valueDeclaration) &&\n arg.valueDeclaration.dotDotDotToken\n ) {\n console.warn(\n 'Cannot handle rest parameters yet, skipping',\n arg.escapedName\n )\n } else {\n const argType = this.checker.getTypeOfSymbolAtLocation(\n arg,\n this.sfiFile\n )\n\n // set args to empty object if not set\n if (schemaType.args) {\n schemaType.args[arg.escapedName as string] = argType\n\n recLoop(\n argType,\n {\n parentType: type\n },\n 'inputs',\n [...path, arg.escapedName as string]\n )\n }\n }\n })\n\n if (returnType) {\n recLoop(returnType, info, processing, [...path, 'RETURN_TYPE'])\n }\n } else if (isList(this.checker, type)) {\n const itemType = this.checker.getIndexTypeOfType(\n type,\n ts.IndexKind.Number\n )\n\n if (itemType && !isPrimitive(itemType)) {\n if (!referenceSchema[processing].has(type)) {\n referenceSchema[processing].set(type, {})\n }\n\n recLoop(itemType, info, processing, [...path, 'ITEM_TYPE'])\n }\n } else if (!isPrimitive(type)) {\n const properties = getPublicPropertiesOfType(this.checker, type)\n\n if (!referenceSchema[processing].has(type)) {\n referenceSchema[processing].set(type, {})\n }\n\n properties.forEach(property => {\n const propertyType = this.checker.getTypeOfSymbolAtLocation(\n property,\n this.sfiFile\n )\n\n if (\n !referenceSchema[processing].get(type)![\n property.escapedName as string\n ]\n ) {\n referenceSchema[processing].get(type)![\n property.escapedName as string\n ] = {\n returnType: propertyType,\n args: {}\n }\n }\n\n recLoop(\n propertyType,\n {\n propetyName: property.escapedName as string,\n parentType: type\n },\n processing,\n [...path, property.escapedName as string]\n )\n })\n }\n }\n\n if (index.Query) {\n recLoop(index.Query)\n }\n\n if (index.Mutation) {\n recLoop(index.Mutation)\n }\n\n return referenceSchema\n }\n}\n",
15
+ "import ts from 'typescript'\nimport {SchemaParser} from './schema-parser'\nimport path from 'path'\n\nexport class SchemaBuilder {\n private program: ts.Program\n private checker: ts.TypeChecker\n private sfiFile!: ts.SourceFile\n private sfi!: ts.Symbol\n private sfiFilePath: string\n\n constructor(sfiFilePath: string) {\n this.sfiFilePath = path.join(process.cwd(), sfiFilePath)\n\n this.program = ts.createProgram([this.sfiFilePath], {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.CommonJS,\n strict: true,\n esModuleInterop: true,\n skipLibCheck: false,\n forceConsistentCasingInFileNames: true,\n noImplicitAny: true,\n experimentalDecorators: true\n })\n\n this.checker = this.program.getTypeChecker()\n\n this.loadSfi()\n }\n\n private loadSfi() {\n const sourceFiles = this.program.getSourceFiles()\n const file = sourceFiles.find(file => file.fileName === this.sfiFilePath)\n\n if (!file) {\n throw new Error('Could not find index.ts (pylon entrypoint)')\n }\n\n this.sfiFile = file\n\n const sfiFileSymbol = this.checker.getSymbolAtLocation(file)!\n const sfiFileExports = this.checker.getExportsOfModule(sfiFileSymbol!)\n const sfiFileDefaultExport = sfiFileExports.find(\n exportSymbol => exportSymbol.escapedName === 'default'\n )\n\n if (!sfiFileDefaultExport) {\n throw new Error('Could not find default export')\n }\n\n this.sfi = sfiFileDefaultExport\n }\n\n public build() {\n const sfiType = this.checker.getTypeOfSymbolAtLocation(\n this.sfi,\n this.sfiFile\n )\n\n const plainResolversProperty = sfiType.getProperty('plainResolvers')\n\n if (!plainResolversProperty) {\n throw new Error('Could not find plainResolvers property')\n }\n\n const plainResolversType = this.checker.getTypeOfSymbolAtLocation(\n plainResolversProperty,\n this.sfiFile\n )\n\n const queryProperty = plainResolversType.getProperty('Query')\n const mutationProperty = plainResolversType.getProperty('Mutation')\n\n const queryType = queryProperty\n ? this.checker.getTypeOfSymbolAtLocation(queryProperty, this.sfiFile)\n : undefined\n const mutationType = mutationProperty\n ? this.checker.getTypeOfSymbolAtLocation(mutationProperty, this.sfiFile)\n : undefined\n\n const parser = new SchemaParser(this.checker, this.sfiFile)\n\n parser.parse({\n Query: queryType,\n Mutation: mutationType\n })\n\n return {\n typeDefs: parser.toString(),\n schema: parser.getSchema()\n }\n }\n}\n",
16
+ "import {Bundler} from './bundler/bundler.js'\nimport {SchemaBuilder} from './schema/builder.js'\n\nexport interface BuildOptions {\n sfiFilePath: string\n outputFilePath: string\n watch?: boolean\n onWatch?: () => void\n}\n\nexport const build = async (options: BuildOptions) => {\n const bundler = new Bundler(options.sfiFilePath, options.outputFilePath)\n\n await bundler.build({\n getTypeDefs: () => {\n const builder = new SchemaBuilder(options.sfiFilePath)\n\n const built = builder.build()\n\n // Write typedefs to file (only for debugging purposes)\n const typeDefs = built.typeDefs\n\n return typeDefs\n },\n watch: options.watch,\n onWatch: options.onWatch\n })\n}\n"
17
+ ],
18
+ "mappings": ";;AA/////fACA;AACA;;;ACjgggggBAAA;AACA;AAEA,eAAsB,eAAe,GAKlC;AACD,QAAM,kBAAkB,KAAK,QAAQ,QAAQ,IAAI,GAAG,cAAc;AAElE,QAAM,OAAO,MAAM,SAAS,eAAe;AAC3C,QAAM,cAAc,KAAK,MAAM,KAAK,SAAS,CAAC;AAC9C,SAAO;AAAA;;;AC5gggggBTAA;;;AC/////fAAA;;;AC/////fAAA;AA6GO,SAAS,yBAAyB,CACvC,SACA,OACa;AACb,MAAI,OAAO;AAEX,MAAI,KAAK,QAAQ,GAAG;AAElB,UAAM,gBAAgB,KAAK,MAAM,OAC/B,SAEI,EAAE,QAAQ,GAAG,UAAU,UACtB,EAAoB,cAAc,GAAG,YAAY,aACjD,EAAuB,eAAe,OAE7C;AACA,UAAM,oBAAoB,KAAK,MAC5B,OACC,OACE,EAAE,QAAQ,GAAG,UAAU,UACtB,EAAoB,cAAc,GAAG,YAAY,aACjD,EAAuB,eAAe,MAC3C,EACC,IAAI,OAAM,EAAuB,cAAe,EAAE;AACrD,UAAM,gBAAgB,cAAc,OAClC,QAAM,kBAAkB,KAAK,QAAM,OAAO,CAAC,CAC7C;AAUA,WAAQ,QAAgB,aAAa,aAAa;AAAA,EACpD;AAEA,QAAM,aAA0B,CAAC;AAGjC,MAAI,OAAO,SAAS,IAAI,KAAK,YAAY,IAAI,KAAK,OAAO,IAAI;AAC3D,WAAO;AAET,MAAI,iBAA8B,CAAC;AAEnC,mBACG,KAAa,sBAAsB,KAAK,cAAc,KAAK,CAAC;AAE/D,aAAW,QAAQ,gBAAgB;AACjC,UAAM,WACJ,KAAK,sBAEH,GAAG,yBAAyB,KAAK,gBAAgB,IACjD,GAAG,cAAc,aAElB,KAAK,QAAQ,EAAE,WAAW,GAAG;AAEhC,QAAI,aAAa,OAAO;AACtB;AAAA,IACF;AAEA,eAAW,KAAK,IAAI;AAAA,EACtB;AAEA,SAAO;AAAA;AA7KF,IAAM,gBAAgB,CAAC,SAAkB;AAC9C,SACE,KAAK,QAAQ,GAAG,UAAU,UAC1B,KAAK,cAAc,EAAE,WAAW,KAChC,KAAK,kBAAkB,EAAE,WAAW;AAAA;AAIjC,IAAM,SAAS,CAAC,SAAyB,SAAkB;AAChE,QAAM,WAAW,QAAQ,eAAe,MAAM,WAAW,SAAS;AAElE,QAAM,QACJ,aACC,SAAS,SAAS,GAAG,WAAW,aAC/B,SAAS,SAAS,GAAG,WAAW;AAGpC,SAAO;AAAA;;;AClhggggBTAA;;;AC/////fAUO,IAAM,QAAQ,OAAO,YAA0B;AACpD,QAAM,WAAU,IAAI,QAAQ,QAAQ,aAAa,QAAQ,cAAc;AAEvE,QAAM,SAAQ,MAAM;AAAA,IAClB,aAAa,MAAM;AACjB,YAAM,WAAU,IAAI,cAAc,QAAQ,WAAW;AAErD,YAAM,QAAQ,SAAQ,MAAM;AAG5B,YAAM,WAAW,MAAM;AAEvB,aAAO;AAAA;AAAA,IAET,OAAO,QAAQ;AAAA,IACf,SAAS,QAAQ;AAAA,EACnB,CAAC;AAAA;",
19
+ "debugId": "0C352A137DDA7D9F64756e2164756e21",
20
+ "names": []
21
+ }
@@ -0,0 +1,6 @@
1
+ export declare function loadPackageJson(): Promise<{
2
+ baseURL?: string;
3
+ pylon?: {
4
+ external?: string[];
5
+ };
6
+ }>;
@@ -0,0 +1,13 @@
1
+ export declare class SchemaBuilder {
2
+ private program;
3
+ private checker;
4
+ private sfiFile;
5
+ private sfi;
6
+ private sfiFilePath;
7
+ constructor(sfiFilePath: string);
8
+ private loadSfi;
9
+ build(): {
10
+ typeDefs: string;
11
+ schema: import("./schema-parser").Schema;
12
+ };
13
+ }
@@ -0,0 +1,65 @@
1
+ import ts from 'typescript';
2
+ import { Union as _Union, Enum as _Enum } from './type-definition-builder.js';
3
+ type Union = _Union & {
4
+ description: string;
5
+ };
6
+ type Enum = _Enum & {
7
+ description: string;
8
+ };
9
+ interface TypeDefinition {
10
+ name: string;
11
+ isList: boolean;
12
+ isRequired: boolean;
13
+ description: string;
14
+ }
15
+ interface Field {
16
+ name: string;
17
+ type: TypeDefinition;
18
+ }
19
+ interface Input {
20
+ name: string;
21
+ description: string;
22
+ fields: Array<{
23
+ name: string;
24
+ type: TypeDefinition;
25
+ }>;
26
+ }
27
+ interface Type {
28
+ name: string;
29
+ description: string;
30
+ fields: Array<Field & {
31
+ args: Array<{
32
+ name: string;
33
+ type: TypeDefinition;
34
+ }>;
35
+ }>;
36
+ }
37
+ export interface Schema {
38
+ types: Array<Type>;
39
+ inputs: Array<Input>;
40
+ unions: Array<Union>;
41
+ enums: Array<Enum>;
42
+ scalars: Array<string>;
43
+ }
44
+ interface Index {
45
+ Query?: ts.Type;
46
+ Mutation?: ts.Type;
47
+ }
48
+ export declare class SchemaParser {
49
+ private schema;
50
+ private checker;
51
+ private sfiFile;
52
+ private typeDefinitionBuilder;
53
+ constructor(checker: ts.TypeChecker, sfiFile: ts.SourceFile);
54
+ parse(index: Index): void;
55
+ toString(): string;
56
+ getSchema(): Schema;
57
+ private processSchemaReference;
58
+ private getJsDocHeaderFromType;
59
+ /**
60
+ * Extracts reserved field names from the schema by removing them from their respective types and inputs.
61
+ */
62
+ private extractForbiddenFieldNamesFromSchema;
63
+ private makeReferenceSchema;
64
+ }
65
+ export {};
@@ -0,0 +1,36 @@
1
+ import ts from 'typescript';
2
+ import { Schema } from './schema-parser.js';
3
+ export interface Union {
4
+ name: string;
5
+ rawType: ts.Type;
6
+ types: Array<string>;
7
+ }
8
+ export interface Enum {
9
+ name: string;
10
+ rawType: ts.Type;
11
+ values: Array<string>;
12
+ }
13
+ interface FieldDefinition {
14
+ name: string;
15
+ isList: boolean;
16
+ isRequired: boolean;
17
+ isListRequired?: boolean;
18
+ }
19
+ export declare class TypeDefinitionBuilder {
20
+ private checker;
21
+ private schema;
22
+ private typesNameMap;
23
+ private inputsNameMap;
24
+ private unions;
25
+ private enums;
26
+ constructor(checker: ts.TypeChecker, schema: Schema);
27
+ getTypeDefinition: (rawType: ts.Type, options?: {
28
+ isInputType?: boolean;
29
+ isRequired?: boolean;
30
+ propertyName?: string;
31
+ }) => FieldDefinition;
32
+ getUnions(): Array<Union>;
33
+ getEnums(): Array<Enum>;
34
+ typeDefinitionToGraphQLType: (typeDefinition: FieldDefinition) => string;
35
+ }
36
+ export {};
@@ -0,0 +1,15 @@
1
+ import ts from 'typescript';
2
+ export declare const isEmptyObject: (type: ts.Type) => boolean | 0;
3
+ export declare const isList: (checker: ts.TypeChecker, type: ts.Type) => boolean;
4
+ export declare const isBoolean: (type: ts.Type) => boolean;
5
+ export declare const isPrimitive: (type: ts.Type) => number;
6
+ export declare const isFunction: (type: ts.Type) => boolean;
7
+ export declare const isEnum: (type: ts.Type) => boolean;
8
+ export declare const isPromise: (type: ts.Type) => boolean;
9
+ export declare const getPromiseType: (type: ts.Type) => ts.Type | undefined;
10
+ export declare const excludeNullUndefinedFromType: (type: ts.Type) => {
11
+ type: ts.Type;
12
+ wasOptional: boolean;
13
+ };
14
+ export declare function getPublicPropertiesOfType(checker: ts.TypeChecker, _type: ts.Type): ts.Symbol[];
15
+ export declare const safeTypeName: (name: string) => string;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@getcronit/pylon-builder",
3
+ "version": "0.0.49",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "bun run ../../build.js && bun run build:declarations",
9
+ "build:declarations": "tsc --declaration --emitDeclarationOnly --outDir ./dist"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "license": "UNLICENSED",
15
+ "dependencies": {
16
+ "@rollup/plugin-commonjs": "^25.0.7",
17
+ "@rollup/plugin-node-resolve": "^15.2.3",
18
+ "@rollup/plugin-typescript": "^11.1.6",
19
+ "@vercel/ncc": "^0.38.1",
20
+ "chokidar": "^3.5.3",
21
+ "rollup": "^4.9.6",
22
+ "rollup-plugin-terser": "^7.0.2",
23
+ "source-map-support": "^0.5.21",
24
+ "typescript": "^4.9.5"
25
+ },
26
+ "devDependencies": {
27
+ "@babel/core": "^7.21.0",
28
+ "@babel/plugin-syntax-import-assertions": "^7.20.0",
29
+ "babel-jest": "^29.4.2",
30
+ "jest": "^29.4.2",
31
+ "jest-environment-jsdom": "^29.4.2",
32
+ "microbundle": "^0.15.1",
33
+ "ts-jest": "^29.0.5",
34
+ "tslib": "^2.5.0"
35
+ }
36
+ }