@nexus-rpc/gen-core 0.1.0-alpha0

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,111 @@
1
+ import { javaOptions, JavaRenderer, JavaTargetLanguage, Namer, Type, } from "quicktype-core";
2
+ import { RenderAdapter } from "./render-adapter.js";
3
+ import { splitDescription } from "./utility.js";
4
+ import { stringEscape } from "quicktype-core/dist/language/Java/utils.js";
5
+ // Change some defaults globally
6
+ javaOptions.packageName.definition.defaultValue = "com.example.nexusservices";
7
+ export class JavaLanguageWithNexus extends JavaTargetLanguage {
8
+ get defaultIndentation() {
9
+ // We want two-space indent to be default for Java
10
+ return " ";
11
+ }
12
+ makeRenderer(renderContext, untypedOptionValues) {
13
+ const adapter = new JavaRenderAdapter(super.makeRenderer(renderContext, untypedOptionValues), untypedOptionValues);
14
+ return adapter.makeRenderer({
15
+ emitSourceStructure(original) {
16
+ adapter.emitServices();
17
+ original();
18
+ },
19
+ emitConverterClass(original) {
20
+ // No converter class wanted
21
+ },
22
+ forbiddenNamesForGlobalNamespace(original) {
23
+ const returnValue = original();
24
+ // We need to adjust some forbidden names to include Object base class items
25
+ returnValue.push("clone", "equals", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait");
26
+ return returnValue;
27
+ },
28
+ startFile(original, basename) {
29
+ // Prepend a the package name for Java
30
+ const prepend = adapter.render._options.packageName.replaceAll(".", "/") + "/";
31
+ original([prepend, basename]);
32
+ // // Emit generated note
33
+ // adapter.render.emitLine("// Generated by nexus-rpc-gen. DO NOT EDIT!");
34
+ // adapter.render.ensureBlankLine();
35
+ },
36
+ emitPackageAndImports(original, imports) {
37
+ adapter.emitGeneratedComment();
38
+ original(imports);
39
+ },
40
+ });
41
+ }
42
+ }
43
+ class JavaRenderAdapter extends RenderAdapter {
44
+ emitServices() {
45
+ for (const [serviceName, serviceSchema] of Object.entries(this.schema.services)) {
46
+ this.emitService(serviceName, serviceSchema);
47
+ }
48
+ }
49
+ emitService(serviceName, serviceSchema) {
50
+ // Collect imports
51
+ const imports = ["io.nexusrpc.Operation", "io.nexusrpc.Service"];
52
+ for (const [_, op] of Object.entries(serviceSchema.operations)) {
53
+ if (op.input?.kind == "jsonSchema") {
54
+ const type = this.render.topLevels.get(op.input.name);
55
+ if (type) {
56
+ imports.push(...this.render.javaImport(type));
57
+ }
58
+ }
59
+ if (op.output?.kind == "jsonSchema") {
60
+ const type = this.render.topLevels.get(op.output.name);
61
+ if (type) {
62
+ imports.push(...this.render.javaImport(type));
63
+ }
64
+ }
65
+ }
66
+ // Create class
67
+ // TODO(cretz): Research addNameForTopLevel and such to prevent service name clash
68
+ const className = this.makeServiceTypeName(this.render.makeNamedTypeNamer().nameStyle(serviceName));
69
+ const packagePrepend = this.render._options.packageName.replaceAll(".", "/") + "/";
70
+ this.emitGeneratedComment();
71
+ this.render.emitFileHeader([packagePrepend, className], imports);
72
+ this.render.emitDescription(splitDescription(serviceSchema.description));
73
+ this.render.emitLine("@Service", className == serviceName
74
+ ? []
75
+ : ['(name="', stringEscape(serviceName), '")']);
76
+ const methodNamesInUse = {};
77
+ this.render.emitBlock(["public interface ", className], () => {
78
+ this.render.forEachWithBlankLines(Object.entries(serviceSchema.operations), "interposing", (op, opName, pos) => {
79
+ this.render.emitDescription(splitDescription(op.description));
80
+ const methodName = this.makeOperationFunctionName(this.render.namerForObjectProperty().nameStyle(opName), methodNamesInUse);
81
+ this.render.emitLine("@Operation", methodName == opName ? [] : ['(name="', stringEscape(opName), '")']);
82
+ const inType = this.getNexusType(op.input);
83
+ this.render.emitLine(this.getNexusType(op.output) ?? "void", " ", methodName, "(", inType ? [inType, " input"] : [], ");");
84
+ });
85
+ });
86
+ this.render.finishFile();
87
+ }
88
+ emitGeneratedComment() {
89
+ this.render.emitLine("// Generated by nexus-rpc-gen. DO NOT EDIT!");
90
+ this.render.ensureBlankLine();
91
+ }
92
+ emitPackageAndImports(imports) {
93
+ this.emitGeneratedComment();
94
+ this.render.emitPackageAndImports(imports);
95
+ }
96
+ getNexusType(reference) {
97
+ if (!reference) {
98
+ return undefined;
99
+ }
100
+ else if (reference.kind == "existing") {
101
+ return reference.name;
102
+ }
103
+ else {
104
+ const type = this.render.topLevels.get(reference.name);
105
+ if (!type) {
106
+ throw new Error(`Unable to find type for ${reference.name}`);
107
+ }
108
+ return this.render.javaType(false, type);
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,4 @@
1
+ import { PythonRenderer, PythonTargetLanguage, type LanguageName, type RenderContext, type RendererOptions } from "quicktype-core";
2
+ export declare class PythonLanguageWithNexus extends PythonTargetLanguage {
3
+ protected makeRenderer<Lang extends LanguageName = "python">(renderContext: RenderContext, untypedOptionValues: RendererOptions<Lang>): PythonRenderer;
4
+ }
@@ -0,0 +1,181 @@
1
+ import { ClassType, ConvenienceRenderer, Name, Namer, pythonOptions, PythonRenderer, PythonTargetLanguage, Type, } from "quicktype-core";
2
+ import { RenderAdapter } from "./render-adapter.js";
3
+ import { splitDescription } from "./utility.js";
4
+ // Change some defaults globally
5
+ pythonOptions.justTypes.definition.defaultValue = true;
6
+ pythonOptions.features.definition.defaultValue = "3.7";
7
+ pythonOptions.nicePropertyNames.definition.defaultValue = true;
8
+ pythonOptions.pydanticBaseModel.definition.defaultValue = true;
9
+ export class PythonLanguageWithNexus extends PythonTargetLanguage {
10
+ makeRenderer(renderContext, untypedOptionValues) {
11
+ const adapter = new PythonRenderAdapter(super.makeRenderer(renderContext, untypedOptionValues), untypedOptionValues);
12
+ adapter.assertValidOptions();
13
+ return adapter.makeRenderer({
14
+ emitSourceStructure(original, givenOutputFilename) {
15
+ // Generated comment
16
+ adapter.render.emitLine("# Generated by nexus-rpc-gen. DO NOT EDIT!");
17
+ adapter.render.ensureBlankLine();
18
+ // We need to add the future annotation
19
+ // TODO(cretz): Have option to remove this?
20
+ adapter.render.emitLine("from __future__ import annotations");
21
+ adapter.render.ensureBlankLine();
22
+ original(givenOutputFilename);
23
+ adapter.render.finishFile(adapter.makeFileName());
24
+ },
25
+ emitClosingCode(original) {
26
+ original();
27
+ // Emit services _after_ all types are present. We choose to be after
28
+ // the model types so we don't have any "ForwardRef"s to any models
29
+ // which is not supported by Nexus Python's handler type-checking.
30
+ adapter.emitServices();
31
+ },
32
+ emitClass(_original, t) {
33
+ adapter.emitClass(t);
34
+ },
35
+ emitImports(original) {
36
+ original();
37
+ adapter.emitAdditionalImports();
38
+ },
39
+ });
40
+ }
41
+ }
42
+ const emptyNameMap = new Map();
43
+ class PythonRenderAdapter extends RenderAdapter {
44
+ typeNamer;
45
+ propertyNamer;
46
+ constructor(render, rendererOptions) {
47
+ super(render, rendererOptions);
48
+ this.typeNamer = this.render.makeNamedTypeNamer();
49
+ this.propertyNamer = this.render.namerForObjectProperty();
50
+ }
51
+ assertValidOptions() {
52
+ // Currently, we only support specific Python options
53
+ if (!this.render.pyOptions.justTypes) {
54
+ throw new Error("Python option for just-types must be set");
55
+ }
56
+ if (!this.render.pyOptions.pydanticBaseModel) {
57
+ throw new Error("Python option for pydantic-model must be set");
58
+ }
59
+ if (!this.render.pyOptions.features.typeHints) {
60
+ throw new Error("Python option to include type hints must be set");
61
+ }
62
+ }
63
+ makeFileName() {
64
+ // If there is a single service, use that, otherwise use the
65
+ // filename sans extensions to build it
66
+ const services = Object.entries(this.schema.services);
67
+ const name = services.length == 1
68
+ ? services[0][0]
69
+ : this.nexusRendererOptions.firstFilenameSansExtensions;
70
+ return this.propertyNamer.nameStyle(name) + ".py";
71
+ }
72
+ emitAdditionalImports() {
73
+ // We have to emit imports for existing types with dots. Unlike the existing
74
+ // withImport/emitImports, we do not want from X import Y, we want just
75
+ // import and we will fully-qualify the types at the usage site.
76
+ const toImport = [];
77
+ for (const svcSchema of Object.values(this.schema.services)) {
78
+ for (const opSchema of Object.values(svcSchema.operations)) {
79
+ for (const type of [opSchema.input, opSchema.output]) {
80
+ if (type?.kind == "existing") {
81
+ const lastDot = type.name.lastIndexOf(".");
82
+ if (lastDot >= 0 &&
83
+ !toImport.includes(type.name.slice(0, lastDot))) {
84
+ toImport.push(type.name.slice(0, lastDot));
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ toImport.sort();
91
+ for (const module_ of toImport) {
92
+ this.render.emitLine("import ", module_);
93
+ }
94
+ }
95
+ emitServices() {
96
+ for (const [serviceName, serviceSchema] of Object.entries(this.schema.services)) {
97
+ this.emitService(serviceName, serviceSchema);
98
+ }
99
+ }
100
+ emitService(serviceName, serviceSchema) {
101
+ this.render.ensureBlankLine(2);
102
+ const typeName = this.makeServiceTypeName(this.typeNamer.nameStyle(serviceName));
103
+ // Decorator
104
+ this.render.emitLine("@", this.render.withImport("nexusrpc", "service"), serviceName == typeName
105
+ ? []
106
+ : ["(name=", this.render.string(serviceName), ")"]);
107
+ // Service class with each property
108
+ this.render.emitBlock(["class ", typeName, ":"], () => {
109
+ this.render.emitDescription(splitDescription(serviceSchema.description));
110
+ if (Object.entries(serviceSchema.operations).length == 0) {
111
+ this.render.emitLine("pass");
112
+ }
113
+ const propertyNamesInUse = {};
114
+ this.render.forEachWithBlankLines(Object.entries(serviceSchema.operations), "interposing", (op, opName, pos) => {
115
+ const propertyName = this.makeOperationFunctionName(this.propertyNamer.nameStyle(opName), propertyNamesInUse);
116
+ this.render.emitLine(propertyName, ": ", this.render.withImport("nexusrpc", "Operation"), "[", this.getNexusType(op.input) ?? "None", ", ", this.getNexusType(op.output) ?? "None", "]", opName == propertyName
117
+ ? []
118
+ : [
119
+ " = ",
120
+ this.render.withImport("nexusrpc", "Operation"),
121
+ "(name=",
122
+ this.render.string(opName),
123
+ ")",
124
+ ]);
125
+ this.render.emitDescription(splitDescription(op.description));
126
+ });
127
+ });
128
+ }
129
+ emitClass(t) {
130
+ this.render.declareType(t, () => {
131
+ if (t.getProperties().size === 0) {
132
+ this.render.emitLine("pass");
133
+ return;
134
+ }
135
+ this.render.forEachClassProperty(t, "none", (name, jsonName, cp) => {
136
+ // Get the type string and append a pydantic Field to it if the name
137
+ // doesn't match the JSON name.
138
+ let typeSource = this.render.pythonType(cp.type, true);
139
+ // const fieldName = name.namingFunction.nameStyle(name.firstProposedName(emptyNameMap));
140
+ const fieldName = this.render.sourcelikeToString(name);
141
+ if (fieldName != jsonName) {
142
+ // Ellipsis means no default. In optional situations, typeSource has a
143
+ // trailing " = None" which we need to remove and set as default
144
+ let fieldDefault = "...";
145
+ if (Array.isArray(typeSource) && typeSource.at(-1) == " = None") {
146
+ typeSource = typeSource.slice(0, -1);
147
+ fieldDefault = "None";
148
+ }
149
+ typeSource = [
150
+ typeSource,
151
+ " = ",
152
+ this.render.withImport("pydantic", "Field"),
153
+ "(",
154
+ fieldDefault,
155
+ ", serialization_alias=",
156
+ this.render.string(jsonName),
157
+ ")",
158
+ ];
159
+ }
160
+ this.render.emitLine(name, ": ", typeSource);
161
+ this.render.emitDescription(this.render.descriptionForClassProperty(t, jsonName));
162
+ });
163
+ this.render.ensureBlankLine();
164
+ });
165
+ }
166
+ getNexusType(reference) {
167
+ if (!reference) {
168
+ return undefined;
169
+ }
170
+ else if (reference.kind == "existing") {
171
+ return reference.name;
172
+ }
173
+ else {
174
+ const type = this.render.topLevels.get(reference.name);
175
+ if (!type) {
176
+ throw new Error(`Unable to find type for ${reference.name}`);
177
+ }
178
+ return this.render.pythonType(type, false);
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,5 @@
1
+ import { TypeScriptRenderer, TypeScriptTargetLanguage, type LanguageName, type RenderContext, type RendererOptions } from "quicktype-core";
2
+ export declare class TypeScriptLanguageWithNexus extends TypeScriptTargetLanguage {
3
+ protected get defaultIndentation(): string;
4
+ protected makeRenderer<Lang extends LanguageName = "typescript">(renderContext: RenderContext, untypedOptionValues: RendererOptions<Lang>): TypeScriptRenderer;
5
+ }
@@ -0,0 +1,179 @@
1
+ import { ClassType, Name, tsFlowOptions, Type, TypeScriptRenderer, TypeScriptTargetLanguage, } from "quicktype-core";
2
+ import { RenderAdapter } from "./render-adapter.js";
3
+ import { splitDescription } from "./utility.js";
4
+ import { utf16StringEscape } from "quicktype-core/dist/support/Strings.js";
5
+ // Change some defaults globally
6
+ tsFlowOptions.justTypes.definition.defaultValue = true;
7
+ export class TypeScriptLanguageWithNexus extends TypeScriptTargetLanguage {
8
+ get defaultIndentation() {
9
+ // We want two-space indent to be default for TypeScript
10
+ return " ";
11
+ }
12
+ makeRenderer(renderContext, untypedOptionValues) {
13
+ const adapter = new TypeScriptRenderAdapter(super.makeRenderer(renderContext, untypedOptionValues), untypedOptionValues);
14
+ return adapter.makeRenderer({
15
+ emitSourceStructure(original) {
16
+ adapter.emitServices();
17
+ original();
18
+ adapter.render.finishFile(adapter.makeFileName());
19
+ },
20
+ emitTypes(original) {
21
+ // We cannot use original emitTypes, see override for reason why
22
+ adapter.emitTypes();
23
+ },
24
+ });
25
+ }
26
+ }
27
+ class TypeScriptRenderAdapter extends RenderAdapter {
28
+ _existingTypes;
29
+ makeFileName() {
30
+ // If there is a single service, use that, otherwise use the
31
+ // filename sans extensions to build it
32
+ const services = Object.entries(this.schema.services);
33
+ if (services.length == 1) {
34
+ return `${services[0][0]}.ts`;
35
+ }
36
+ return `${this.nexusRendererOptions.firstFilenameSansExtensions}.ts`;
37
+ }
38
+ // Key is full string as given in schema
39
+ get existingTypes() {
40
+ if (this._existingTypes === undefined) {
41
+ this._existingTypes = {};
42
+ const inUse = {};
43
+ for (const serviceSchema of Object.values(this.schema.services)) {
44
+ for (const opSchema of Object.values(serviceSchema.operations)) {
45
+ for (const type of [opSchema.input, opSchema.output]) {
46
+ if (type?.kind == "existing") {
47
+ // TODO(cretz): Generics with qualified type args that need to be imported?
48
+ const lastHash = type.name.lastIndexOf("#");
49
+ const existingType = {
50
+ type: type.name.slice(lastHash + 1),
51
+ from: lastHash == -1 ? undefined : type.name.slice(0, lastHash),
52
+ };
53
+ // Alias it while it already exists
54
+ let alias = existingType.type;
55
+ for (let index = 1; Object.hasOwn(inUse, alias) ||
56
+ this.topLevelNameInUse(alias, true); index++) {
57
+ alias = `${existingType.type}${index}`;
58
+ }
59
+ if (alias != existingType.type) {
60
+ existingType.alias = alias;
61
+ }
62
+ this._existingTypes[type.name] = existingType;
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return this._existingTypes;
69
+ }
70
+ emitServices() {
71
+ // If there are no services, do nothing
72
+ if (Object.entries(this.schema.services).length == 0) {
73
+ return;
74
+ }
75
+ // Generated comment
76
+ this.render.emitLine("// Generated by nexus-rpc-gen. DO NOT EDIT!");
77
+ this.render.ensureBlankLine();
78
+ // Import Nexus
79
+ this.render.emitLine('import * as nexus from "nexus-rpc";');
80
+ // Import all "existing" types
81
+ const existingTypesByFrom = {};
82
+ for (const existingType of Object.values(this.existingTypes)) {
83
+ if (existingType.from) {
84
+ if (!Object.hasOwn(existingTypesByFrom, existingType.from)) {
85
+ existingTypesByFrom[existingType.from] = [];
86
+ }
87
+ existingTypesByFrom[existingType.from].push(existingType);
88
+ }
89
+ }
90
+ // TODO(cretz): Better sorting?
91
+ for (const [from, existingTypes] of Object.entries(existingTypesByFrom).toSorted((a, b) => a[0].localeCompare(b[0]))) {
92
+ existingTypes.sort((a, b) => a.type.localeCompare(b.type));
93
+ const pieces = existingTypes.map((t) => {
94
+ let piece = `type ${t.type}`;
95
+ if (t.alias)
96
+ piece += ` as ${t.alias}`;
97
+ return piece;
98
+ });
99
+ this.render.emitLine("import { ", pieces.join(", "), ' } from "', utf16StringEscape(from), '";');
100
+ }
101
+ // Emit each service
102
+ for (const [serviceName, serviceSchema] of Object.entries(this.schema.services)) {
103
+ this.emitService(serviceName, serviceSchema);
104
+ }
105
+ }
106
+ emitService(serviceName, serviceSchema) {
107
+ this.render.ensureBlankLine();
108
+ const constName = this.makeServiceTypeName(this.render.nameStyle(serviceName, false));
109
+ this.render.emitDescription(splitDescription(serviceSchema.description));
110
+ this.render.emitBlock([
111
+ "export const ",
112
+ constName,
113
+ ' = nexus.service("',
114
+ utf16StringEscape(serviceName),
115
+ '", ',
116
+ ], ");", () => {
117
+ const propertyNamesInUse = {};
118
+ this.render.forEachWithBlankLines(Object.entries(serviceSchema.operations), "interposing", (op, opName, pos) => {
119
+ this.render.emitDescription(splitDescription(op.description));
120
+ const propertyName = this.makeOperationFunctionName(this.render.nameStyle(opName, false), propertyNamesInUse);
121
+ const opArguments = opName == propertyName
122
+ ? []
123
+ : ['{ name: "', utf16StringEscape(opName), '" }'];
124
+ this.render.emitLine(propertyName, ": nexus.operation<", this.getNexusType(op.input) ?? "void", ", ", this.getNexusType(op.output) ?? "void", ">(", opArguments, "),");
125
+ });
126
+ });
127
+ }
128
+ emitTypes() {
129
+ // We cannot use original emitTypes because it renders all top level types
130
+ // as non-export and includes __ALL_TYPES__, but we want export and don't
131
+ // want that pseudo type. So we copy what Quicktype did mostly, with some
132
+ // alterations.
133
+ // Primitives
134
+ this.render.forEachWithBlankLines(this.render.topLevels, "none", (t, name, pos) => {
135
+ if (!t.isPrimitive() || name == "__ALL_TYPES__") {
136
+ return;
137
+ }
138
+ this.render.ensureBlankLine();
139
+ this.render.emitDescription(this.render.descriptionForType(t));
140
+ this.render.emitLine("export type ", name, " = ", this.render.sourceFor(t).source, ";");
141
+ });
142
+ // Named types
143
+ this.render.forEachNamedType("leading-and-interposing", (c, n) => this.render.emitClass(c, n), (enm, n) => this.render.emitEnum(enm, n), (u, n) => this.render.emitUnion(u, n));
144
+ }
145
+ exportTopLevelPrimitives() {
146
+ // TypeScript rendered by default does not export primitive type aliases, so
147
+ // we must. First, collect set to export
148
+ const toExport = Object.keys(this.schema.topLevelJsonSchemaTypes).filter((name) => this.render.topLevels.get(name)?.isPrimitive());
149
+ // Render if any
150
+ if (toExport.length > 0) {
151
+ this.render.ensureBlankLine();
152
+ this.render.emitLine("export { ", toExport.join(", "), " };");
153
+ }
154
+ }
155
+ getNexusType(reference) {
156
+ if (!reference) {
157
+ return undefined;
158
+ }
159
+ else if (reference.kind == "existing") {
160
+ const type = this.existingTypes[reference.name];
161
+ return type.alias ?? type.type;
162
+ }
163
+ else {
164
+ const type = this.render.topLevels.get(reference.name);
165
+ if (!type) {
166
+ throw new Error(`Unable to find type for ${reference.name}`);
167
+ }
168
+ // If the type is primitive, use the alias
169
+ if (type.isPrimitive()) {
170
+ return reference.name;
171
+ }
172
+ return this.render.sourceFor(type).source;
173
+ }
174
+ }
175
+ opNameForbidden(name) {
176
+ // We also want to forbid any Object functions
177
+ return super.opNameForbidden(name) || name in {};
178
+ }
179
+ }
@@ -0,0 +1,2 @@
1
+ import type { DefinitionSchema } from "./definition-schema.js";
2
+ export declare function parseFiles(files: string[]): Promise<DefinitionSchema>;
package/dist/parser.js ADDED
@@ -0,0 +1,34 @@
1
+ import jsonSchema from "../../../../schemas/nexus-rpc-gen.json" with { type: "json" };
2
+ import Ajv from "ajv";
3
+ import addFormats from "ajv-formats";
4
+ import yaml from "yaml";
5
+ import { readFile } from "node:fs/promises";
6
+ const ajv = new Ajv({
7
+ allErrors: true,
8
+ strict: false,
9
+ loadSchema: async (uri) => {
10
+ // TODO(cretz): This
11
+ throw new Error(`Unable to load remote schema at ${uri} at this time`);
12
+ },
13
+ });
14
+ addFormats(ajv);
15
+ export async function parseFiles(files) {
16
+ // TODO(cretz): Multi-file
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"));
22
+ // Validate. We recreate the validator because it carries state.
23
+ const valueFunction = await ajv.compileAsync(jsonSchema);
24
+ if (!valueFunction(document)) {
25
+ for (const error of valueFunction.errors ?? []) {
26
+ console.log(error);
27
+ }
28
+ throw new Error(`Found ${valueFunction.errors?.length} error(s): ` +
29
+ valueFunction.errors
30
+ ?.map((error) => `${error.instancePath || "(root)"}: ${error.message} (${JSON.stringify(error.params)})`)
31
+ .join(", "));
32
+ }
33
+ return document;
34
+ }
@@ -0,0 +1,44 @@
1
+ import { ConvenienceRenderer, Name, Namer, type RendererOptions, type Sourcelike } from "quicktype-core";
2
+ import { type NexusRendererOptions, type PreparedSchema } from "./generator.js";
3
+ import type { BlankLineConfig, ForEachPosition } from "quicktype-core/dist/Renderer.js";
4
+ import type { ClassProperty, ClassType, EnumType, ObjectType, Type, TypeGraph, UnionType } from "quicktype-core/dist/Type/index.js";
5
+ import type { ForbiddenWordsInfo } from "quicktype-core/dist/ConvenienceRenderer.js";
6
+ type AnyFunction = (...arguments_: any[]) => any;
7
+ export declare abstract class RenderAdapter<AccessibleRenderer> {
8
+ readonly render: AccessibleRenderer & RenderAccessible & ConvenienceRenderer;
9
+ protected readonly nexusRendererOptions: NexusRendererOptions;
10
+ private readonly serviceTypeNamesInUse;
11
+ constructor(render: ConvenienceRenderer, rendererOptions: RendererOptions);
12
+ get schema(): PreparedSchema;
13
+ makeRenderer<T extends ConvenienceRenderer>(overrides: {
14
+ [K in keyof AccessibleRenderer]?: AccessibleRenderer[K] extends AnyFunction ? (this: AccessibleRenderer, original: AccessibleRenderer[K], ...arguments_: Parameters<AccessibleRenderer[K]>) => ReturnType<AccessibleRenderer[K]> : never;
15
+ }): T;
16
+ makeServiceTypeName(idealName: string): string;
17
+ makeOperationFunctionName(idealName: string, inUse: Record<string, boolean>): string;
18
+ topLevelNameInUse(name: string, includeServices: boolean): boolean;
19
+ opNameForbidden(name: string): boolean;
20
+ }
21
+ export interface RenderAccessible {
22
+ readonly typeGraph: TypeGraph;
23
+ descriptionForClassProperty(o: ObjectType, name: string): string[] | undefined;
24
+ descriptionForType(t: Type): string[] | undefined;
25
+ emitDescription(description: Sourcelike[] | undefined): void;
26
+ emitGatheredSource(items: Sourcelike[]): void;
27
+ emitLineOnce(...lineParts: Sourcelike[]): void;
28
+ emitSourceStructure(givenOutputFilename: string): void;
29
+ emitTable(tableArray: Sourcelike[][]): void;
30
+ finishFile(filename: string): void;
31
+ forbiddenForObjectProperties(_o: ObjectType, _className: Name): ForbiddenWordsInfo;
32
+ forbiddenNamesForGlobalNamespace(): readonly string[];
33
+ forEachClassProperty(o: ObjectType, blankLocations: BlankLineConfig, f: (name: Name, jsonName: string, p: ClassProperty, position: ForEachPosition) => void): void;
34
+ forEachNamedType(blankLocations: BlankLineConfig, objectFunction: ((c: ClassType, className: Name, position: ForEachPosition) => void) | ((o: ObjectType, objectName: Name, position: ForEachPosition) => void), enumFunction: (enm: EnumType, enumName: Name, position: ForEachPosition) => void, unionFunction: (u: UnionType, unionName: Name, position: ForEachPosition) => void): void;
35
+ forEachTopLevel(blankLocations: BlankLineConfig, f: (t: Type, name: Name, position: ForEachPosition) => void, predicate?: (t: Type) => boolean): boolean;
36
+ forEachWithBlankLines<K, V>(iterable: Iterable<[K, V]>, blankLineConfig: BlankLineConfig, emitter: (v: V, k: K, position: ForEachPosition) => void): boolean;
37
+ gatherSource(emitter: () => void): Sourcelike[];
38
+ makeNamedTypeNamer(): Namer;
39
+ makeNameForTopLevel(_t: Type, givenName: string, _maybeNamedType: Type | undefined): Name;
40
+ nameForNamedType(t: Type): Name;
41
+ namerForObjectProperty(o: ObjectType, p: ClassProperty): Namer | null;
42
+ sourcelikeToString(source: Sourcelike): string;
43
+ }
44
+ export {};
@@ -0,0 +1,88 @@
1
+ import { ConvenienceRenderer, Name, Namer, } from "quicktype-core";
2
+ import { getNexusRendererOptions, } from "./generator.js";
3
+ import { proxyWithOverrides } from "./utility.js";
4
+ import { FixedName } from "quicktype-core/dist/Naming.js";
5
+ export class RenderAdapter {
6
+ render;
7
+ nexusRendererOptions;
8
+ serviceTypeNamesInUse = {};
9
+ constructor(render, rendererOptions) {
10
+ this.render = render;
11
+ this.nexusRendererOptions = getNexusRendererOptions(rendererOptions);
12
+ }
13
+ get schema() {
14
+ return this.nexusRendererOptions.nexusSchema;
15
+ }
16
+ makeRenderer(overrides) {
17
+ return proxyWithOverrides(this.render, overrides);
18
+ }
19
+ makeServiceTypeName(idealName) {
20
+ // If name in use but not with services, suffix "Service"
21
+ if (this.topLevelNameInUse(idealName, false)) {
22
+ idealName += "Service";
23
+ }
24
+ // If name in use, append number, starting with 2
25
+ if (this.topLevelNameInUse(idealName, true)) {
26
+ for (let index = 2;; index++) {
27
+ if (!this.topLevelNameInUse(`${idealName}${index}`, true)) {
28
+ idealName += `${index}`;
29
+ break;
30
+ }
31
+ }
32
+ }
33
+ this.serviceTypeNamesInUse[idealName] = true;
34
+ return idealName;
35
+ }
36
+ makeOperationFunctionName(idealName, inUse) {
37
+ // If forbidden, suffix with "Operation"
38
+ if (this.opNameForbidden(idealName)) {
39
+ idealName += "Operation";
40
+ }
41
+ // Append numbers starting with 2 if already in use
42
+ if (Object.hasOwn(inUse, idealName)) {
43
+ for (let index = 2;; index++) {
44
+ if (!Object.hasOwn(inUse, `${idealName}${index}`)) {
45
+ idealName += `${index}`;
46
+ break;
47
+ }
48
+ }
49
+ }
50
+ inUse[idealName] = true;
51
+ return idealName;
52
+ }
53
+ topLevelNameInUse(name, includeServices) {
54
+ // Considered in use if forbidden
55
+ if (this.render.forbiddenNamesForGlobalNamespace().includes(name)) {
56
+ return true;
57
+ }
58
+ // Check services
59
+ if (includeServices && Object.hasOwn(this.serviceTypeNamesInUse, name)) {
60
+ return true;
61
+ }
62
+ let inNames = false;
63
+ this.render.names.forEach((value) => {
64
+ if (value == name) {
65
+ inNames = true;
66
+ }
67
+ });
68
+ return inNames;
69
+ }
70
+ opNameForbidden(name) {
71
+ // TODO(cretz): hacking undefined in for object type ok?
72
+ const info = this.render.forbiddenForObjectProperties(undefined, new FixedName(name));
73
+ if (info.includeGlobalForbidden && this.topLevelNameInUse(name, true)) {
74
+ return true;
75
+ }
76
+ for (const badName of info.names) {
77
+ if (badName instanceof Name) {
78
+ if (name == this.render.names.get(badName)) {
79
+ return true;
80
+ }
81
+ }
82
+ else if (name == badName) {
83
+ return true;
84
+ }
85
+ }
86
+ return false;
87
+ }
88
+ }
@@ -0,0 +1,10 @@
1
+ type AnyFunction = (...arguments_: any[]) => any;
2
+ /**
3
+ * A helper that proxies an instance and overrides selected methods.
4
+ * The overrides receive both the original method and its typed parameters.
5
+ */
6
+ export declare function proxyWithOverrides<T extends object>(instance: T, overrides: {
7
+ [K in keyof T]?: T[K] extends AnyFunction ? (this: T, original: T[K], ...arguments_: Parameters<T[K]>) => ReturnType<T[K]> : never;
8
+ }): T;
9
+ export declare function splitDescription(string_: string | undefined): string[] | undefined;
10
+ export {};