@nexus-rpc/gen-core 0.1.0-alpha.3 → 0.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/definition-source.d.ts +5 -0
- package/dist/definition-source.js +1 -0
- package/dist/generator.d.ts +16 -4
- package/dist/generator.js +95 -51
- package/dist/index.d.ts +1 -0
- package/dist/language-go.js +1 -1
- package/dist/language-typescript.js +1 -1
- package/dist/parser.d.ts +2 -2
- package/dist/parser.js +27 -8
- package/dist/render-adapter.d.ts +2 -2
- package/package.json +1 -1
- package/src/definition-source.ts +6 -0
- package/src/generator.ts +149 -77
- package/src/index.ts +1 -0
- package/src/language-go.ts +1 -1
- package/src/language-typescript.ts +1 -1
- package/src/parser.ts +36 -9
- package/src/render-adapter.ts +2 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/generator.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { type LanguageName, type RendererOptions, type TargetLanguage } from "quicktype-core";
|
|
2
|
-
import type {
|
|
2
|
+
import type { DefinitionSource } from "./definition-source.js";
|
|
3
3
|
export interface GeneratorOptions<Lang extends LanguageName = LanguageName> {
|
|
4
4
|
lang: TargetLanguage;
|
|
5
|
-
|
|
5
|
+
definitionSources: DefinitionSource[];
|
|
6
6
|
rendererOptions: RendererOptions<Lang>;
|
|
7
7
|
firstFilenameSansExtensions: string;
|
|
8
8
|
}
|
|
9
|
+
export interface PreparedSchemaSources {
|
|
10
|
+
sources: PreparedSchema[];
|
|
11
|
+
}
|
|
9
12
|
export interface PreparedSchema {
|
|
13
|
+
sourceURI: string;
|
|
10
14
|
services: {
|
|
11
15
|
[key: string]: PreparedService;
|
|
12
16
|
};
|
|
@@ -37,8 +41,16 @@ export interface PreparedTypeReference {
|
|
|
37
41
|
kind: "jsonSchema" | "existing";
|
|
38
42
|
name: string;
|
|
39
43
|
}
|
|
44
|
+
export interface MergedSources {
|
|
45
|
+
services: {
|
|
46
|
+
[key: string]: PreparedService;
|
|
47
|
+
};
|
|
48
|
+
topLevelJsonSchemaTypes: {
|
|
49
|
+
[key: string]: any;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
40
52
|
export interface NexusRendererOptions {
|
|
41
|
-
nexusSchema:
|
|
53
|
+
nexusSchema: MergedSources;
|
|
42
54
|
firstFilenameSansExtensions: string;
|
|
43
55
|
}
|
|
44
56
|
export declare function getNexusRendererOptions(rendererOptions: RendererOptions): NexusRendererOptions;
|
|
@@ -48,6 +60,6 @@ export declare class Generator {
|
|
|
48
60
|
generate(): Promise<{
|
|
49
61
|
[fileName: string]: string;
|
|
50
62
|
}>;
|
|
51
|
-
private
|
|
63
|
+
private prepareSchemas;
|
|
52
64
|
private prepareInOutType;
|
|
53
65
|
}
|
package/dist/generator.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InputData, JSONSchemaInput, JSONSchemaStore, quicktypeMultiFile, Ref, } from "quicktype-core";
|
|
2
2
|
import { PathElementKind } from "quicktype-core/dist/input/PathElement.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import yaml from "yaml";
|
|
4
5
|
export function getNexusRendererOptions(rendererOptions) {
|
|
5
6
|
return rendererOptions.nexusOptions;
|
|
6
7
|
}
|
|
@@ -11,73 +12,102 @@ export class Generator {
|
|
|
11
12
|
}
|
|
12
13
|
async generate() {
|
|
13
14
|
// Prepare schema
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
...schema.sharedJsonSchema,
|
|
19
|
-
...schema.topLevelJsonSchemaTypes,
|
|
15
|
+
const sources = this.prepareSchemas();
|
|
16
|
+
const mergedSources = {
|
|
17
|
+
services: {},
|
|
18
|
+
topLevelJsonSchemaTypes: {},
|
|
20
19
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
schemaInput.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
// Build quicktype input
|
|
21
|
+
const schemaInput = new JSONSchemaInput(new LocalFetchingSchemaStore());
|
|
22
|
+
for (const schema of sources.sources) {
|
|
23
|
+
const jsonSchema = {
|
|
24
|
+
...schema.sharedJsonSchema,
|
|
25
|
+
...schema.topLevelJsonSchemaTypes,
|
|
26
|
+
};
|
|
27
|
+
await schemaInput.addSource({
|
|
28
|
+
// Append __ALL_TYPES__ to make it easier to filter out top level
|
|
29
|
+
// while rendering
|
|
30
|
+
name: schema.sourceURI.toString() + "/__ALL_TYPES__",
|
|
31
|
+
uris: [schema.sourceURI],
|
|
32
|
+
schema: JSON.stringify(jsonSchema),
|
|
33
|
+
});
|
|
34
|
+
// Set the top-level types
|
|
35
|
+
for (const topLevel of Object.keys(schema.topLevelJsonSchemaTypes)) {
|
|
36
|
+
schemaInput.addTopLevel(topLevel, Ref.parse(`${schema.sourceURI}#/${topLevel}`));
|
|
37
|
+
}
|
|
38
|
+
for (const [name, reference] of Object.entries(schema.topLevelJsonSchemaLocalRefs)) {
|
|
39
|
+
schemaInput.addTopLevel(name, Ref.parse(`${schema.sourceURI}${reference}`));
|
|
40
|
+
}
|
|
41
|
+
mergeSources(mergedSources, schema);
|
|
32
42
|
}
|
|
33
43
|
const inputData = new InputData();
|
|
34
44
|
inputData.addInput(schemaInput);
|
|
35
45
|
// Update renderer options with the prepared schema
|
|
36
46
|
const rendererOptions = {
|
|
37
47
|
nexusOptions: {
|
|
38
|
-
nexusSchema:
|
|
48
|
+
nexusSchema: mergedSources,
|
|
39
49
|
firstFilenameSansExtensions: this.options.firstFilenameSansExtensions,
|
|
40
50
|
},
|
|
41
51
|
...this.options.rendererOptions,
|
|
42
52
|
};
|
|
43
53
|
// Run quicktype and return
|
|
44
54
|
const returnValue = {};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
try {
|
|
56
|
+
const results = await quicktypeMultiFile({
|
|
57
|
+
inputData,
|
|
58
|
+
lang: this.options.lang,
|
|
59
|
+
rendererOptions,
|
|
60
|
+
});
|
|
61
|
+
results.forEach((contents, fileName) => (returnValue[fileName] = contents.lines.join("\n")));
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof Error &&
|
|
65
|
+
"properties" in error &&
|
|
66
|
+
error.messageName === "SchemaFetchError") {
|
|
67
|
+
// If there is an error with schema fetching, the message
|
|
68
|
+
// produced by quicktype will use an empty string for the address.
|
|
69
|
+
const { address, base } = error.properties;
|
|
70
|
+
throw new Error(`Could not fetch schema "${address}", referred to from ${base}`);
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
51
74
|
return returnValue;
|
|
52
75
|
}
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
sharedJsonSchema: {
|
|
57
|
-
types: this.options.schema.types ?? {},
|
|
58
|
-
},
|
|
59
|
-
topLevelJsonSchemaTypes: {},
|
|
60
|
-
topLevelJsonSchemaLocalRefs: {},
|
|
76
|
+
prepareSchemas() {
|
|
77
|
+
const prepared = {
|
|
78
|
+
sources: [],
|
|
61
79
|
};
|
|
62
|
-
for (const
|
|
63
|
-
schema
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
for (const definitionSource of this.options.definitionSources) {
|
|
81
|
+
const schema = {
|
|
82
|
+
sourceURI: definitionSource.fileURI.toString(),
|
|
83
|
+
services: {},
|
|
84
|
+
sharedJsonSchema: {
|
|
85
|
+
types: definitionSource.schema.types ?? {},
|
|
86
|
+
},
|
|
87
|
+
topLevelJsonSchemaTypes: {},
|
|
88
|
+
topLevelJsonSchemaLocalRefs: {},
|
|
66
89
|
};
|
|
67
|
-
for (const [
|
|
68
|
-
|
|
69
|
-
description:
|
|
90
|
+
for (const [serviceName, service] of Object.entries(definitionSource.schema.services ?? {})) {
|
|
91
|
+
schema.services[serviceName] = {
|
|
92
|
+
description: service.description,
|
|
93
|
+
operations: {},
|
|
70
94
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
for (const [operationName, operation] of Object.entries(service.operations)) {
|
|
96
|
+
const schemaOp = {
|
|
97
|
+
description: operation.description,
|
|
98
|
+
};
|
|
99
|
+
schema.services[serviceName].operations[operationName] = schemaOp;
|
|
100
|
+
if (operation.input) {
|
|
101
|
+
schemaOp.input = this.prepareInOutType(schema, serviceName, operationName, operation.input, "Input");
|
|
102
|
+
}
|
|
103
|
+
if (operation.output) {
|
|
104
|
+
schemaOp.output = this.prepareInOutType(schema, serviceName, operationName, operation.output, "Output");
|
|
105
|
+
}
|
|
77
106
|
}
|
|
78
107
|
}
|
|
108
|
+
prepared.sources.push(schema);
|
|
79
109
|
}
|
|
80
|
-
return
|
|
110
|
+
return prepared;
|
|
81
111
|
}
|
|
82
112
|
prepareInOutType(schema, serviceName, operationName, opInOut, suffix) {
|
|
83
113
|
// Check for an existing ref for this specific lang first
|
|
@@ -120,9 +150,23 @@ export class Generator {
|
|
|
120
150
|
return { kind: "jsonSchema", name };
|
|
121
151
|
}
|
|
122
152
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
function mergeSources(destination, source) {
|
|
154
|
+
for (const name of Object.keys(source.topLevelJsonSchemaTypes)) {
|
|
155
|
+
if (name in destination.topLevelJsonSchemaTypes) {
|
|
156
|
+
throw new Error(`Duplicate type "${name}" defined across multiple definition files`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const name of Object.keys(source.services)) {
|
|
160
|
+
if (name in destination.services) {
|
|
161
|
+
throw new Error(`Duplicate service "${name}" defined across multiple definition files`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
Object.assign(destination.topLevelJsonSchemaTypes, source.topLevelJsonSchemaTypes);
|
|
165
|
+
Object.assign(destination.services, source.services);
|
|
166
|
+
}
|
|
167
|
+
class LocalFetchingSchemaStore extends JSONSchemaStore {
|
|
168
|
+
async fetch(address) {
|
|
169
|
+
const content = await readFile(new URL(address), "utf8");
|
|
170
|
+
return yaml.parse(content);
|
|
127
171
|
}
|
|
128
172
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export { GoLanguageWithNexus } from "./language-go.js";
|
|
|
5
5
|
export { JavaLanguageWithNexus } from "./language-java.js";
|
|
6
6
|
export { PythonLanguageWithNexus } from "./language-python.js";
|
|
7
7
|
export { TypeScriptLanguageWithNexus } from "./language-typescript.js";
|
|
8
|
+
export type { DefinitionSource } from "./definition-source.js";
|
|
8
9
|
export { parseFiles } from "./parser.js";
|
package/dist/language-go.js
CHANGED
|
@@ -28,7 +28,7 @@ export class GoLanguageWithNexus extends GoTargetLanguage {
|
|
|
28
28
|
},
|
|
29
29
|
emitTopLevel(original, t, name) {
|
|
30
30
|
// Do not emit __ALL_TYPES__ placeholder
|
|
31
|
-
if (name.firstProposedName(new Map())
|
|
31
|
+
if (name.firstProposedName(new Map()).endsWith("/__ALL_TYPES__")) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
original(t, name);
|
|
@@ -132,7 +132,7 @@ class TypeScriptRenderAdapter extends RenderAdapter {
|
|
|
132
132
|
// alterations.
|
|
133
133
|
// Primitives
|
|
134
134
|
this.render.forEachWithBlankLines(this.render.topLevels, "none", (t, name, pos) => {
|
|
135
|
-
if (!t.isPrimitive() || name
|
|
135
|
+
if (!t.isPrimitive() || name.endsWith("/__ALL_TYPES__")) {
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
138
|
this.render.ensureBlankLine();
|
package/dist/parser.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function parseFiles(files: string[]): Promise<
|
|
1
|
+
import type { DefinitionSource } from "./definition-source.js";
|
|
2
|
+
export declare function parseFiles(files: string[]): Promise<DefinitionSource[]>;
|
package/dist/parser.js
CHANGED
|
@@ -3,6 +3,7 @@ import Ajv from "ajv";
|
|
|
3
3
|
import addFormats from "ajv-formats";
|
|
4
4
|
import yaml from "yaml";
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
6
7
|
const ajv = new Ajv({
|
|
7
8
|
allErrors: true,
|
|
8
9
|
strict: false,
|
|
@@ -12,13 +13,8 @@ const ajv = new Ajv({
|
|
|
12
13
|
},
|
|
13
14
|
});
|
|
14
15
|
addFormats(ajv);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (files.length != 1) {
|
|
18
|
-
throw new Error("Must have only 1 file at this time");
|
|
19
|
-
}
|
|
20
|
-
// Parse YAML
|
|
21
|
-
const document = yaml.parse(await readFile(files[0], "utf8"));
|
|
16
|
+
async function parseFile(file) {
|
|
17
|
+
const document = yaml.parse(await readFile(file, "utf8"));
|
|
22
18
|
// Validate. We recreate the validator because it carries state.
|
|
23
19
|
const valueFunction = await ajv.compileAsync(jsonSchema);
|
|
24
20
|
if (!valueFunction(document)) {
|
|
@@ -30,5 +26,28 @@ export async function parseFiles(files) {
|
|
|
30
26
|
?.map((error) => `${error.instancePath || "(root)"}: ${error.message} (${JSON.stringify(error.params)})`)
|
|
31
27
|
.join(", "));
|
|
32
28
|
}
|
|
33
|
-
return document;
|
|
29
|
+
return { fileURI: pathToFileURL(file), schema: document };
|
|
30
|
+
}
|
|
31
|
+
export async function parseFiles(files) {
|
|
32
|
+
if (files.length === 0) {
|
|
33
|
+
throw new Error("Must have at least 1 file");
|
|
34
|
+
}
|
|
35
|
+
const results = await Promise.allSettled(files.map(parseFile));
|
|
36
|
+
const errors = [];
|
|
37
|
+
const sources = [];
|
|
38
|
+
for (let index = 0; index < results.length; index++) {
|
|
39
|
+
const result = results[index];
|
|
40
|
+
if (result.status === "rejected") {
|
|
41
|
+
errors.push(new Error(`${files[index]}: ${result.reason}`, {
|
|
42
|
+
cause: result.reason,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
sources.push(result.value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (errors.length > 0) {
|
|
50
|
+
throw new AggregateError(errors, `Failed to parse ${errors.length} file(s)`);
|
|
51
|
+
}
|
|
52
|
+
return sources;
|
|
34
53
|
}
|
package/dist/render-adapter.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConvenienceRenderer, Name, Namer, type RendererOptions, type Sourcelike } from "quicktype-core";
|
|
2
|
-
import { type NexusRendererOptions, type
|
|
2
|
+
import { type NexusRendererOptions, type MergedSources } from "./generator.js";
|
|
3
3
|
import type { BlankLineConfig, ForEachPosition } from "quicktype-core/dist/Renderer.js";
|
|
4
4
|
import type { ClassProperty, ClassType, EnumType, ObjectType, Type, TypeGraph, UnionType } from "quicktype-core/dist/Type/index.js";
|
|
5
5
|
import type { ForbiddenWordsInfo } from "quicktype-core/dist/ConvenienceRenderer.js";
|
|
@@ -9,7 +9,7 @@ export declare abstract class RenderAdapter<AccessibleRenderer> {
|
|
|
9
9
|
protected readonly nexusRendererOptions: NexusRendererOptions;
|
|
10
10
|
private readonly serviceTypeNamesInUse;
|
|
11
11
|
constructor(render: ConvenienceRenderer, rendererOptions: RendererOptions);
|
|
12
|
-
get schema():
|
|
12
|
+
get schema(): MergedSources;
|
|
13
13
|
makeRenderer<T extends ConvenienceRenderer>(overrides: {
|
|
14
14
|
[K in keyof AccessibleRenderer]?: AccessibleRenderer[K] extends AnyFunction ? (this: AccessibleRenderer, original: AccessibleRenderer[K], ...arguments_: Parameters<AccessibleRenderer[K]>) => ReturnType<AccessibleRenderer[K]> : never;
|
|
15
15
|
}): T;
|
package/package.json
CHANGED
package/src/generator.ts
CHANGED
|
@@ -9,19 +9,25 @@ import {
|
|
|
9
9
|
type RendererOptions,
|
|
10
10
|
type TargetLanguage,
|
|
11
11
|
} from "quicktype-core";
|
|
12
|
-
import type {
|
|
12
|
+
import type { DefinitionSource } from "./definition-source.js";
|
|
13
13
|
import { PathElementKind } from "quicktype-core/dist/input/PathElement.js";
|
|
14
|
-
import {
|
|
14
|
+
import { readFile } from "node:fs/promises";
|
|
15
|
+
import yaml from "yaml";
|
|
15
16
|
|
|
16
17
|
export interface GeneratorOptions<Lang extends LanguageName = LanguageName> {
|
|
17
18
|
lang: TargetLanguage;
|
|
18
|
-
|
|
19
|
+
definitionSources: DefinitionSource[];
|
|
19
20
|
rendererOptions: RendererOptions<Lang>;
|
|
20
21
|
// Used to help with ideal output filename
|
|
21
22
|
firstFilenameSansExtensions: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export interface PreparedSchemaSources {
|
|
26
|
+
sources: PreparedSchema[];
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
export interface PreparedSchema {
|
|
30
|
+
sourceURI: string;
|
|
25
31
|
services: { [key: string]: PreparedService };
|
|
26
32
|
sharedJsonSchema: { types: { [key: string]: any } };
|
|
27
33
|
topLevelJsonSchemaTypes: { [key: string]: any };
|
|
@@ -44,8 +50,13 @@ export interface PreparedTypeReference {
|
|
|
44
50
|
name: string;
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
export interface MergedSources {
|
|
54
|
+
services: { [key: string]: PreparedService };
|
|
55
|
+
topLevelJsonSchemaTypes: { [key: string]: any };
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
export interface NexusRendererOptions {
|
|
48
|
-
nexusSchema:
|
|
59
|
+
nexusSchema: MergedSources;
|
|
49
60
|
firstFilenameSansExtensions: string;
|
|
50
61
|
}
|
|
51
62
|
|
|
@@ -64,30 +75,42 @@ export class Generator {
|
|
|
64
75
|
|
|
65
76
|
async generate(): Promise<{ [fileName: string]: string }> {
|
|
66
77
|
// Prepare schema
|
|
67
|
-
const
|
|
78
|
+
const sources = this.prepareSchemas();
|
|
79
|
+
const mergedSources: MergedSources = {
|
|
80
|
+
services: {},
|
|
81
|
+
topLevelJsonSchemaTypes: {},
|
|
82
|
+
};
|
|
68
83
|
|
|
69
84
|
// Build quicktype input
|
|
70
|
-
const schemaInput = new JSONSchemaInput(new
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const schemaInput = new JSONSchemaInput(new LocalFetchingSchemaStore());
|
|
86
|
+
for (const schema of sources.sources) {
|
|
87
|
+
const jsonSchema = {
|
|
88
|
+
...schema.sharedJsonSchema,
|
|
89
|
+
...schema.topLevelJsonSchemaTypes,
|
|
90
|
+
};
|
|
91
|
+
await schemaInput.addSource({
|
|
92
|
+
// Append __ALL_TYPES__ to make it easier to filter out top level
|
|
93
|
+
// while rendering
|
|
94
|
+
name: schema.sourceURI.toString() + "/__ALL_TYPES__",
|
|
95
|
+
uris: [schema.sourceURI],
|
|
96
|
+
schema: JSON.stringify(jsonSchema),
|
|
97
|
+
});
|
|
98
|
+
// Set the top-level types
|
|
99
|
+
for (const topLevel of Object.keys(schema.topLevelJsonSchemaTypes)) {
|
|
100
|
+
schemaInput.addTopLevel(
|
|
101
|
+
topLevel,
|
|
102
|
+
Ref.parse(`${schema.sourceURI}#/${topLevel}`),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
for (const [name, reference] of Object.entries(
|
|
106
|
+
schema.topLevelJsonSchemaLocalRefs,
|
|
107
|
+
)) {
|
|
108
|
+
schemaInput.addTopLevel(
|
|
109
|
+
name,
|
|
110
|
+
Ref.parse(`${schema.sourceURI}${reference}`),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
mergeSources(mergedSources, schema);
|
|
91
114
|
}
|
|
92
115
|
const inputData = new InputData();
|
|
93
116
|
inputData.addInput(schemaInput);
|
|
@@ -95,7 +118,7 @@ export class Generator {
|
|
|
95
118
|
// Update renderer options with the prepared schema
|
|
96
119
|
const rendererOptions = {
|
|
97
120
|
nexusOptions: {
|
|
98
|
-
nexusSchema:
|
|
121
|
+
nexusSchema: mergedSources,
|
|
99
122
|
firstFilenameSansExtensions: this.options.firstFilenameSansExtensions,
|
|
100
123
|
},
|
|
101
124
|
...this.options.rendererOptions,
|
|
@@ -103,62 +126,86 @@ export class Generator {
|
|
|
103
126
|
|
|
104
127
|
// Run quicktype and return
|
|
105
128
|
const returnValue: { [fileName: string]: string } = {};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
(
|
|
113
|
-
(
|
|
114
|
-
|
|
129
|
+
try {
|
|
130
|
+
const results = await quicktypeMultiFile({
|
|
131
|
+
inputData,
|
|
132
|
+
lang: this.options.lang,
|
|
133
|
+
rendererOptions,
|
|
134
|
+
});
|
|
135
|
+
results.forEach(
|
|
136
|
+
(contents, fileName) =>
|
|
137
|
+
(returnValue[fileName] = contents.lines.join("\n")),
|
|
138
|
+
);
|
|
139
|
+
} catch (error: unknown) {
|
|
140
|
+
if (
|
|
141
|
+
error instanceof Error &&
|
|
142
|
+
"properties" in error &&
|
|
143
|
+
(error as any).messageName === "SchemaFetchError"
|
|
144
|
+
) {
|
|
145
|
+
// If there is an error with schema fetching, the message
|
|
146
|
+
// produced by quicktype will use an empty string for the address.
|
|
147
|
+
const { address, base } = (error as any).properties;
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Could not fetch schema "${address}", referred to from ${base}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
115
154
|
return returnValue;
|
|
116
155
|
}
|
|
117
156
|
|
|
118
|
-
private
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
sharedJsonSchema: {
|
|
122
|
-
types: this.options.schema.types ?? {},
|
|
123
|
-
},
|
|
124
|
-
topLevelJsonSchemaTypes: {},
|
|
125
|
-
topLevelJsonSchemaLocalRefs: {},
|
|
157
|
+
private prepareSchemas(): PreparedSchemaSources {
|
|
158
|
+
const prepared: PreparedSchemaSources = {
|
|
159
|
+
sources: [],
|
|
126
160
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
161
|
+
|
|
162
|
+
for (const definitionSource of this.options.definitionSources) {
|
|
163
|
+
const schema: PreparedSchema = {
|
|
164
|
+
sourceURI: definitionSource.fileURI.toString(),
|
|
165
|
+
services: {},
|
|
166
|
+
sharedJsonSchema: {
|
|
167
|
+
types: definitionSource.schema.types ?? {},
|
|
168
|
+
},
|
|
169
|
+
topLevelJsonSchemaTypes: {},
|
|
170
|
+
topLevelJsonSchemaLocalRefs: {},
|
|
133
171
|
};
|
|
134
|
-
for (const [
|
|
135
|
-
|
|
172
|
+
for (const [serviceName, service] of Object.entries(
|
|
173
|
+
definitionSource.schema.services ?? {},
|
|
136
174
|
)) {
|
|
137
|
-
|
|
138
|
-
description:
|
|
175
|
+
schema.services[serviceName] = {
|
|
176
|
+
description: service.description,
|
|
177
|
+
operations: {},
|
|
139
178
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
for (const [operationName, operation] of Object.entries(
|
|
180
|
+
service.operations,
|
|
181
|
+
)) {
|
|
182
|
+
const schemaOp: PreparedOperation = {
|
|
183
|
+
description: operation.description,
|
|
184
|
+
};
|
|
185
|
+
schema.services[serviceName].operations[operationName] = schemaOp;
|
|
186
|
+
if (operation.input) {
|
|
187
|
+
schemaOp.input = this.prepareInOutType(
|
|
188
|
+
schema,
|
|
189
|
+
serviceName,
|
|
190
|
+
operationName,
|
|
191
|
+
operation.input,
|
|
192
|
+
"Input",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (operation.output) {
|
|
196
|
+
schemaOp.output = this.prepareInOutType(
|
|
197
|
+
schema,
|
|
198
|
+
serviceName,
|
|
199
|
+
operationName,
|
|
200
|
+
operation.output,
|
|
201
|
+
"Output",
|
|
202
|
+
);
|
|
203
|
+
}
|
|
158
204
|
}
|
|
159
205
|
}
|
|
206
|
+
prepared.sources.push(schema);
|
|
160
207
|
}
|
|
161
|
-
return
|
|
208
|
+
return prepared;
|
|
162
209
|
}
|
|
163
210
|
|
|
164
211
|
private prepareInOutType(
|
|
@@ -214,9 +261,34 @@ export class Generator {
|
|
|
214
261
|
}
|
|
215
262
|
}
|
|
216
263
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
264
|
+
function mergeSources(
|
|
265
|
+
destination: MergedSources,
|
|
266
|
+
source: PreparedSchema,
|
|
267
|
+
): void {
|
|
268
|
+
for (const name of Object.keys(source.topLevelJsonSchemaTypes)) {
|
|
269
|
+
if (name in destination.topLevelJsonSchemaTypes) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Duplicate type "${name}" defined across multiple definition files`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
for (const name of Object.keys(source.services)) {
|
|
276
|
+
if (name in destination.services) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`Duplicate service "${name}" defined across multiple definition files`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
Object.assign(
|
|
283
|
+
destination.topLevelJsonSchemaTypes,
|
|
284
|
+
source.topLevelJsonSchemaTypes,
|
|
285
|
+
);
|
|
286
|
+
Object.assign(destination.services, source.services);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
class LocalFetchingSchemaStore extends JSONSchemaStore {
|
|
290
|
+
async fetch(address: string): Promise<JSONSchema | undefined> {
|
|
291
|
+
const content = await readFile(new URL(address), "utf8");
|
|
292
|
+
return yaml.parse(content);
|
|
221
293
|
}
|
|
222
294
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,4 +5,5 @@ export { GoLanguageWithNexus } from "./language-go.js";
|
|
|
5
5
|
export { JavaLanguageWithNexus } from "./language-java.js";
|
|
6
6
|
export { PythonLanguageWithNexus } from "./language-python.js";
|
|
7
7
|
export { TypeScriptLanguageWithNexus } from "./language-typescript.js";
|
|
8
|
+
export type { DefinitionSource } from "./definition-source.js";
|
|
8
9
|
export { parseFiles } from "./parser.js";
|
package/src/language-go.ts
CHANGED
|
@@ -63,7 +63,7 @@ export class GoLanguageWithNexus extends GoTargetLanguage {
|
|
|
63
63
|
},
|
|
64
64
|
emitTopLevel(original, t, name) {
|
|
65
65
|
// Do not emit __ALL_TYPES__ placeholder
|
|
66
|
-
if (name.firstProposedName(new Map())
|
|
66
|
+
if (name.firstProposedName(new Map()).endsWith("/__ALL_TYPES__")) {
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
original(t, name);
|
|
@@ -220,7 +220,7 @@ class TypeScriptRenderAdapter extends RenderAdapter<TypeScriptRenderAccessible>
|
|
|
220
220
|
this.render.topLevels,
|
|
221
221
|
"none",
|
|
222
222
|
(t, name, pos) => {
|
|
223
|
-
if (!t.isPrimitive() || name
|
|
223
|
+
if (!t.isPrimitive() || name.endsWith("/__ALL_TYPES__")) {
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
226
|
|
package/src/parser.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { DefinitionSchema } from "./definition-schema.js";
|
|
2
|
+
import type { DefinitionSource } from "./definition-source.js";
|
|
2
3
|
|
|
3
4
|
import jsonSchema from "../schemas/nexus-rpc-gen.json" with { type: "json" };
|
|
4
5
|
import Ajv from "ajv";
|
|
5
6
|
import addFormats from "ajv-formats";
|
|
6
7
|
import yaml from "yaml";
|
|
7
8
|
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
8
10
|
|
|
9
11
|
const ajv = new Ajv({
|
|
10
12
|
allErrors: true,
|
|
@@ -17,14 +19,8 @@ const ajv = new Ajv({
|
|
|
17
19
|
|
|
18
20
|
addFormats(ajv);
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (files.length != 1) {
|
|
23
|
-
throw new Error("Must have only 1 file at this time");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Parse YAML
|
|
27
|
-
const document = yaml.parse(await readFile(files[0], "utf8"));
|
|
22
|
+
async function parseFile(file: string): Promise<DefinitionSource> {
|
|
23
|
+
const document = yaml.parse(await readFile(file, "utf8"));
|
|
28
24
|
|
|
29
25
|
// Validate. We recreate the validator because it carries state.
|
|
30
26
|
const valueFunction = await ajv.compileAsync<DefinitionSchema>(jsonSchema);
|
|
@@ -42,6 +38,37 @@ export async function parseFiles(files: string[]): Promise<DefinitionSchema> {
|
|
|
42
38
|
.join(", "),
|
|
43
39
|
);
|
|
44
40
|
}
|
|
41
|
+
return { fileURI: pathToFileURL(file), schema: document };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function parseFiles(files: string[]): Promise<DefinitionSource[]> {
|
|
45
|
+
if (files.length === 0) {
|
|
46
|
+
throw new Error("Must have at least 1 file");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const results = await Promise.allSettled(files.map(parseFile));
|
|
50
|
+
const errors: Error[] = [];
|
|
51
|
+
const sources: DefinitionSource[] = [];
|
|
52
|
+
|
|
53
|
+
for (let index = 0; index < results.length; index++) {
|
|
54
|
+
const result = results[index];
|
|
55
|
+
if (result.status === "rejected") {
|
|
56
|
+
errors.push(
|
|
57
|
+
new Error(`${files[index]}: ${result.reason}`, {
|
|
58
|
+
cause: result.reason,
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
} else {
|
|
62
|
+
sources.push(result.value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (errors.length > 0) {
|
|
67
|
+
throw new AggregateError(
|
|
68
|
+
errors,
|
|
69
|
+
`Failed to parse ${errors.length} file(s)`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
45
72
|
|
|
46
|
-
return
|
|
73
|
+
return sources;
|
|
47
74
|
}
|
package/src/render-adapter.ts
CHANGED
|
@@ -8,13 +8,12 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
getNexusRendererOptions,
|
|
10
10
|
type NexusRendererOptions,
|
|
11
|
-
type
|
|
11
|
+
type MergedSources,
|
|
12
12
|
} from "./generator.js";
|
|
13
13
|
import { proxyWithOverrides } from "./utility.js";
|
|
14
14
|
import type {
|
|
15
15
|
BlankLineConfig,
|
|
16
16
|
ForEachPosition,
|
|
17
|
-
Renderer,
|
|
18
17
|
} from "quicktype-core/dist/Renderer.js";
|
|
19
18
|
import type {
|
|
20
19
|
ClassProperty,
|
|
@@ -42,7 +41,7 @@ export abstract class RenderAdapter<AccessibleRenderer> {
|
|
|
42
41
|
this.nexusRendererOptions = getNexusRendererOptions(rendererOptions);
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
public get schema():
|
|
44
|
+
public get schema(): MergedSources {
|
|
46
45
|
return this.nexusRendererOptions.nexusSchema;
|
|
47
46
|
}
|
|
48
47
|
|