@cosmneo/onion-lasagna 0.4.0 → 0.4.1

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,285 @@
1
+ import {
2
+ collectFields,
3
+ generateFieldId,
4
+ isSchemaDefinition
5
+ } from "./chunk-BG2FY27M.js";
6
+
7
+ // src/presentation/graphql/sdl/generate.ts
8
+ function generateGraphQLSDL(schema, config) {
9
+ const fields = isSchemaDefinition(schema) ? schema.fields : schema;
10
+ const collectedFields = collectFields(fields);
11
+ const includeDescriptions = config?.includeDescriptions ?? true;
12
+ const includeDeprecations = config?.includeDeprecations ?? true;
13
+ const queries = [];
14
+ const mutations = [];
15
+ const subscriptions = [];
16
+ for (const { key, field } of collectedFields) {
17
+ const fieldId = generateFieldId(key);
18
+ if (field.operation === "query") {
19
+ queries.push({ fieldId, field });
20
+ } else if (field.operation === "mutation") {
21
+ mutations.push({ fieldId, field });
22
+ } else if (field.operation === "subscription") {
23
+ subscriptions.push({ fieldId, field });
24
+ }
25
+ }
26
+ const namedTypes = /* @__PURE__ */ new Map();
27
+ const lines = [];
28
+ if (config?.preamble) {
29
+ lines.push(config.preamble);
30
+ lines.push("");
31
+ }
32
+ if (queries.length > 0) {
33
+ lines.push("type Query {");
34
+ for (const { fieldId, field } of queries) {
35
+ const fieldLine = buildFieldLine(
36
+ fieldId,
37
+ field,
38
+ namedTypes,
39
+ includeDescriptions,
40
+ includeDeprecations
41
+ );
42
+ lines.push(fieldLine);
43
+ }
44
+ lines.push("}");
45
+ lines.push("");
46
+ }
47
+ if (mutations.length > 0) {
48
+ lines.push("type Mutation {");
49
+ for (const { fieldId, field } of mutations) {
50
+ const fieldLine = buildFieldLine(
51
+ fieldId,
52
+ field,
53
+ namedTypes,
54
+ includeDescriptions,
55
+ includeDeprecations
56
+ );
57
+ lines.push(fieldLine);
58
+ }
59
+ lines.push("}");
60
+ lines.push("");
61
+ }
62
+ if (subscriptions.length > 0) {
63
+ lines.push("type Subscription {");
64
+ for (const { fieldId, field } of subscriptions) {
65
+ const fieldLine = buildFieldLine(
66
+ fieldId,
67
+ field,
68
+ namedTypes,
69
+ includeDescriptions,
70
+ includeDeprecations
71
+ );
72
+ lines.push(fieldLine);
73
+ }
74
+ lines.push("}");
75
+ lines.push("");
76
+ }
77
+ for (const [, typeBody] of namedTypes) {
78
+ lines.push(typeBody);
79
+ lines.push("");
80
+ }
81
+ return lines.join("\n").trimEnd() + "\n";
82
+ }
83
+ function buildFieldLine(fieldId, field, namedTypes, includeDescriptions, includeDeprecations) {
84
+ const parts = [];
85
+ if (includeDescriptions && field.docs.description) {
86
+ const escaped = field.docs.description.replace(/"""/g, '\\"""');
87
+ parts.push(` """${escaped}"""`);
88
+ }
89
+ const inputTypeName = field.input ? `${capitalize(fieldId)}Input` : void 0;
90
+ const outputRootName = field.output ? `${capitalize(fieldId)}Output` : void 0;
91
+ if (field.input && inputTypeName) {
92
+ const jsonSchema = field.input.toJsonSchema();
93
+ registerInputType(inputTypeName, jsonSchema, namedTypes);
94
+ }
95
+ let outputTypeName;
96
+ if (field.output && outputRootName) {
97
+ const jsonSchema = field.output.toJsonSchema();
98
+ outputTypeName = registerOutputType(outputRootName, jsonSchema, namedTypes);
99
+ }
100
+ let signature = ` ${fieldId}`;
101
+ if (inputTypeName) {
102
+ signature += `(input: ${inputTypeName}!)`;
103
+ }
104
+ signature += ": ";
105
+ signature += outputTypeName ?? "JSON";
106
+ if (includeDeprecations && field.docs.deprecated) {
107
+ const reason = field.docs.deprecationReason;
108
+ signature += reason ? ` @deprecated(reason: "${escapeSDLString(reason)}")` : " @deprecated";
109
+ }
110
+ if (parts.length > 0) {
111
+ return parts.join("\n") + "\n" + signature;
112
+ }
113
+ return signature;
114
+ }
115
+ function registerInputType(typeName, jsonSchema, namedTypes) {
116
+ if (namedTypes.has(typeName)) return typeName;
117
+ namedTypes.set(typeName, "");
118
+ const properties = jsonSchema.properties ?? {};
119
+ const required = new Set(
120
+ Array.isArray(jsonSchema.required) ? jsonSchema.required : []
121
+ );
122
+ const lines = [`input ${typeName} {`];
123
+ for (const [propName, propSchema] of Object.entries(properties)) {
124
+ const graphqlType = jsonSchemaToGraphQLType(
125
+ propSchema,
126
+ `${typeName}_${capitalize(propName)}`,
127
+ namedTypes,
128
+ "input"
129
+ );
130
+ const isRequired = required.has(propName);
131
+ lines.push(` ${propName}: ${graphqlType}${isRequired ? "!" : ""}`);
132
+ }
133
+ lines.push("}");
134
+ namedTypes.set(typeName, lines.join("\n"));
135
+ return typeName;
136
+ }
137
+ function registerOutputType(typeName, jsonSchema, namedTypes) {
138
+ if (namedTypes.has(typeName)) return typeName;
139
+ if (jsonSchema.type === "array") {
140
+ const items = jsonSchema.items;
141
+ if (!items) return "[JSON]";
142
+ const itemTypeName = jsonSchemaToGraphQLType(items, `${typeName}_Item`, namedTypes, "output");
143
+ return `[${itemTypeName}]`;
144
+ }
145
+ const variants = pickVariants(jsonSchema);
146
+ if (variants) {
147
+ return registerUnionType(typeName, variants, namedTypes);
148
+ }
149
+ namedTypes.set(typeName, "");
150
+ const properties = jsonSchema.properties ?? {};
151
+ if (Object.keys(properties).length === 0) {
152
+ namedTypes.delete(typeName);
153
+ return "JSON";
154
+ }
155
+ const required = new Set(
156
+ Array.isArray(jsonSchema.required) ? jsonSchema.required : []
157
+ );
158
+ const lines = [`type ${typeName} {`];
159
+ for (const [propName, propSchema] of Object.entries(properties)) {
160
+ const graphqlType = jsonSchemaToGraphQLType(
161
+ propSchema,
162
+ `${typeName}_${capitalize(propName)}`,
163
+ namedTypes,
164
+ "output"
165
+ );
166
+ const isRequired = required.has(propName);
167
+ lines.push(` ${propName}: ${graphqlType}${isRequired ? "!" : ""}`);
168
+ }
169
+ lines.push("}");
170
+ namedTypes.set(typeName, lines.join("\n"));
171
+ return typeName;
172
+ }
173
+ function registerUnionType(typeName, variants, namedTypes) {
174
+ if (namedTypes.has(typeName)) return typeName;
175
+ namedTypes.set(typeName, "");
176
+ const memberNames = [];
177
+ const seen = /* @__PURE__ */ new Set();
178
+ for (let i = 0; i < variants.length; i++) {
179
+ const variant = variants[i];
180
+ if (!variant || !isObjectSchema(variant)) {
181
+ namedTypes.delete(typeName);
182
+ return "JSON";
183
+ }
184
+ const discriminator = pickDiscriminatorLabel(variant);
185
+ let memberName = discriminator ? `${typeName}_${pascalize(discriminator)}` : `${typeName}_Member${i + 1}`;
186
+ let suffix = 2;
187
+ while (seen.has(memberName)) {
188
+ memberName = `${typeName}_${pascalize(discriminator ?? "Member")}${suffix++}`;
189
+ }
190
+ seen.add(memberName);
191
+ registerOutputType(memberName, variant, namedTypes);
192
+ memberNames.push(memberName);
193
+ }
194
+ namedTypes.set(typeName, `union ${typeName} = ${memberNames.join(" | ")}`);
195
+ return typeName;
196
+ }
197
+ function pickVariants(schema) {
198
+ if (Array.isArray(schema.oneOf)) return schema.oneOf;
199
+ if (Array.isArray(schema.anyOf)) return schema.anyOf;
200
+ return null;
201
+ }
202
+ function isObjectSchema(schema) {
203
+ if (!schema || typeof schema !== "object") return false;
204
+ if (schema.type === "object") return true;
205
+ if (schema.properties && typeof schema.properties === "object") return true;
206
+ return false;
207
+ }
208
+ function pickDiscriminatorLabel(schema) {
209
+ const properties = schema.properties ?? {};
210
+ for (const propSchema of Object.values(properties)) {
211
+ if (!propSchema || typeof propSchema !== "object") continue;
212
+ const constValue = propSchema.const;
213
+ if (typeof constValue === "string" && constValue.length > 0) {
214
+ return constValue;
215
+ }
216
+ const enumValue = propSchema.enum;
217
+ if (Array.isArray(enumValue) && enumValue.length === 1 && typeof enumValue[0] === "string" && enumValue[0].length > 0) {
218
+ return enumValue[0];
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+ function jsonSchemaToGraphQLType(schema, parentTypeName, namedTypes, kind) {
224
+ if (!schema || typeof schema !== "object") return "JSON";
225
+ if (schema.enum && Array.isArray(schema.enum)) {
226
+ return "String";
227
+ }
228
+ const variants = pickVariants(schema);
229
+ if (variants) {
230
+ if (kind === "input") return "JSON";
231
+ return registerUnionType(parentTypeName, variants, namedTypes);
232
+ }
233
+ const type = schema.type;
234
+ switch (type) {
235
+ case "string":
236
+ return "String";
237
+ case "integer":
238
+ return "Int";
239
+ case "number":
240
+ return "Float";
241
+ case "boolean":
242
+ return "Boolean";
243
+ case "array": {
244
+ const items = schema.items;
245
+ if (!items) return "[JSON]";
246
+ const itemType = jsonSchemaToGraphQLType(
247
+ items,
248
+ // Singular-ish name for array element types — drop a trailing
249
+ // 's' when present so `Tags[]` becomes `…_Tag`. Cheap heuristic;
250
+ // fine for the conventional case.
251
+ parentTypeName.replace(/s$/, ""),
252
+ namedTypes,
253
+ kind
254
+ );
255
+ return `[${itemType}]`;
256
+ }
257
+ case "object":
258
+ if (kind === "input") {
259
+ return registerInputType(parentTypeName, schema, namedTypes);
260
+ }
261
+ return registerOutputType(parentTypeName, schema, namedTypes);
262
+ default:
263
+ if (isObjectSchema(schema)) {
264
+ if (kind === "input") {
265
+ return registerInputType(parentTypeName, schema, namedTypes);
266
+ }
267
+ return registerOutputType(parentTypeName, schema, namedTypes);
268
+ }
269
+ return "JSON";
270
+ }
271
+ }
272
+ function capitalize(str) {
273
+ return str.charAt(0).toUpperCase() + str.slice(1);
274
+ }
275
+ function pascalize(str) {
276
+ return str.split(/[^a-zA-Z0-9]+/g).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
277
+ }
278
+ function escapeSDLString(str) {
279
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
280
+ }
281
+
282
+ export {
283
+ generateGraphQLSDL
284
+ };
285
+ //# sourceMappingURL=chunk-EJNADL7J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presentation/graphql/sdl/generate.ts"],"sourcesContent":["/**\n * @fileoverview GraphQL SDL generation from schema definitions.\n *\n * The `generateGraphQLSDL` function creates a complete GraphQL Schema\n * Definition Language string from a schema definition.\n *\n * Input/output schemas are converted to JSON Schema via `toJsonSchema()`,\n * then mapped to GraphQL type definitions.\n *\n * Supported JSON Schema → GraphQL mappings:\n * - `type: 'string' | 'integer' | 'number' | 'boolean'` → scalars\n * - `type: 'array'` → `[T]` (recursing into `items`)\n * - `enum` → `String` (named enums are a future improvement)\n * - `type: 'object'` with `properties` → emitted as a named GraphQL\n * `type`/`input` and reused via the named-types map. Nested object\n * properties get hierarchical names (`OuterType_Property`) so the\n * SDL stays valid without dropping to a `JSON` scalar.\n * - `oneOf` / `anyOf` of object schemas (zod's `discriminatedUnion`\n * and plain `union`) → emitted as a GraphQL `union` of named member\n * types. Output types only — GraphQL spec forbids unions in inputs.\n *\n * Truly unrepresentable shapes (e.g. unions of mixed scalars) still\n * fall back to `JSON`. Empty objects (no properties) also fall back so\n * we don't emit invalid empty SDL types.\n *\n * @module graphql/sdl/generate\n */\n\nimport type { SchemaAdapter, JsonSchema } from '../../http/schema/types';\nimport type {\n GraphQLSchemaConfig,\n GraphQLSchemaDefinition,\n GraphQLFieldDefinition,\n} from '../field/types';\nimport { isSchemaDefinition, collectFields } from '../field/types';\nimport { generateFieldId } from '../field/utils';\nimport type { GraphQLSDLConfig } from './types';\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Generates a GraphQL SDL string from a schema definition.\n *\n * Walks the schema structure, extracts JSON schemas from all field\n * definitions, and builds a complete SDL with Query, Mutation, and\n * type definitions.\n *\n * @param schema - Schema definition or schema config\n * @param config - Optional SDL generation configuration\n * @returns Complete GraphQL SDL string\n *\n * @example Basic usage\n * ```typescript\n * import { generateGraphQLSDL } from '@cosmneo/onion-lasagna/graphql/sdl';\n *\n * const sdl = generateGraphQLSDL(projectSchema, {\n * preamble: 'scalar DateTime',\n * });\n *\n * console.log(sdl);\n * // type Query {\n * // projectsGet(input: ProjectsGetInput!): ProjectsGetOutput\n * // projectsList: ProjectsListOutput\n * // }\n * // ...\n * ```\n */\nexport function generateGraphQLSDL<T extends GraphQLSchemaConfig>(\n schema: T | GraphQLSchemaDefinition<T>,\n config?: GraphQLSDLConfig,\n): string {\n const fields = isSchemaDefinition(schema) ? schema.fields : schema;\n const collectedFields = collectFields(fields);\n\n const includeDescriptions = config?.includeDescriptions ?? true;\n const includeDeprecations = config?.includeDeprecations ?? true;\n\n // Partition fields by operation type\n const queries: { fieldId: string; field: GraphQLFieldDefinition }[] = [];\n const mutations: { fieldId: string; field: GraphQLFieldDefinition }[] = [];\n const subscriptions: { fieldId: string; field: GraphQLFieldDefinition }[] = [];\n\n for (const { key, field } of collectedFields) {\n const fieldId = generateFieldId(key);\n if (field.operation === 'query') {\n queries.push({ fieldId, field });\n } else if (field.operation === 'mutation') {\n mutations.push({ fieldId, field });\n } else if (field.operation === 'subscription') {\n subscriptions.push({ fieldId, field });\n }\n }\n\n // Collect all named types that need to be emitted. Insertion order is\n // preserved (Map), so emitted types follow the order in which the\n // generator first encountered them.\n const namedTypes: Map<string, string> = new Map();\n const lines: string[] = [];\n\n // Preamble\n if (config?.preamble) {\n lines.push(config.preamble);\n lines.push('');\n }\n\n // Build Query type\n if (queries.length > 0) {\n lines.push('type Query {');\n for (const { fieldId, field } of queries) {\n const fieldLine = buildFieldLine(\n fieldId,\n field,\n namedTypes,\n includeDescriptions,\n includeDeprecations,\n );\n lines.push(fieldLine);\n }\n lines.push('}');\n lines.push('');\n }\n\n // Build Mutation type\n if (mutations.length > 0) {\n lines.push('type Mutation {');\n for (const { fieldId, field } of mutations) {\n const fieldLine = buildFieldLine(\n fieldId,\n field,\n namedTypes,\n includeDescriptions,\n includeDeprecations,\n );\n lines.push(fieldLine);\n }\n lines.push('}');\n lines.push('');\n }\n\n // Build Subscription type\n if (subscriptions.length > 0) {\n lines.push('type Subscription {');\n for (const { fieldId, field } of subscriptions) {\n const fieldLine = buildFieldLine(\n fieldId,\n field,\n namedTypes,\n includeDescriptions,\n includeDeprecations,\n );\n lines.push(fieldLine);\n }\n lines.push('}');\n lines.push('');\n }\n\n // Emit named types (inputs and outputs)\n for (const [, typeBody] of namedTypes) {\n lines.push(typeBody);\n lines.push('');\n }\n\n return lines.join('\\n').trimEnd() + '\\n';\n}\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\n/**\n * Builds a single field line for a root type (Query/Mutation/Subscription).\n */\nfunction buildFieldLine(\n fieldId: string,\n field: GraphQLFieldDefinition,\n namedTypes: Map<string, string>,\n includeDescriptions: boolean,\n includeDeprecations: boolean,\n): string {\n const parts: string[] = [];\n\n // Description as SDL doc string (escaped to prevent triple-quote injection)\n if (includeDescriptions && field.docs.description) {\n const escaped = field.docs.description.replace(/\"\"\"/g, '\\\\\"\"\"');\n parts.push(` \"\"\"${escaped}\"\"\"`);\n }\n\n // Build args and return type\n const inputTypeName = field.input ? `${capitalize(fieldId)}Input` : undefined;\n const outputRootName = field.output ? `${capitalize(fieldId)}Output` : undefined;\n\n // Register named input type\n if (field.input && inputTypeName) {\n const jsonSchema = (field.input as SchemaAdapter).toJsonSchema();\n registerInputType(inputTypeName, jsonSchema, namedTypes);\n }\n\n // Register named output type — `outputTypeName` is what the field's\n // return type signature uses, which differs from `outputRootName` when\n // the schema is a union (the field references the union name; the\n // member object types live alongside it).\n let outputTypeName: string | undefined;\n if (field.output && outputRootName) {\n const jsonSchema = (field.output as SchemaAdapter).toJsonSchema();\n outputTypeName = registerOutputType(outputRootName, jsonSchema, namedTypes);\n }\n\n // Build field signature\n let signature = ` ${fieldId}`;\n\n if (inputTypeName) {\n signature += `(input: ${inputTypeName}!)`;\n }\n\n signature += ': ';\n signature += outputTypeName ?? 'JSON';\n\n // Deprecation directive\n if (includeDeprecations && field.docs.deprecated) {\n const reason = field.docs.deprecationReason;\n signature += reason ? ` @deprecated(reason: \"${escapeSDLString(reason)}\")` : ' @deprecated';\n }\n\n if (parts.length > 0) {\n return parts.join('\\n') + '\\n' + signature;\n }\n return signature;\n}\n\n/**\n * Builds a GraphQL input type from a JSON schema, recursing into nested\n * objects (which become their own named input types). Returns the\n * registered type name. Falls back to registering a `JSON`-only marker\n * if the schema has no usable structure.\n */\nfunction registerInputType(\n typeName: string,\n jsonSchema: JsonSchema,\n namedTypes: Map<string, string>,\n): string {\n if (namedTypes.has(typeName)) return typeName;\n\n // Reserve the slot upfront so cycles don't recurse forever.\n namedTypes.set(typeName, '');\n\n const properties = (jsonSchema.properties ?? {}) as Record<string, JsonSchema>;\n const required = new Set(\n Array.isArray(jsonSchema.required) ? (jsonSchema.required as string[]) : [],\n );\n\n const lines: string[] = [`input ${typeName} {`];\n for (const [propName, propSchema] of Object.entries(properties)) {\n const graphqlType = jsonSchemaToGraphQLType(\n propSchema,\n `${typeName}_${capitalize(propName)}`,\n namedTypes,\n 'input',\n );\n const isRequired = required.has(propName);\n lines.push(` ${propName}: ${graphqlType}${isRequired ? '!' : ''}`);\n }\n lines.push('}');\n\n namedTypes.set(typeName, lines.join('\\n'));\n return typeName;\n}\n\n/**\n * Builds a GraphQL output type (or union) from a JSON schema. Returns\n * the name to use in the field signature. For object schemas this is\n * `typeName`; for `oneOf` / `anyOf` schemas it's also `typeName` but\n * registered as a `union` whose members are named hierarchically.\n */\nfunction registerOutputType(\n typeName: string,\n jsonSchema: JsonSchema,\n namedTypes: Map<string, string>,\n): string {\n if (namedTypes.has(typeName)) return typeName;\n\n // Array-rooted output — emit no named wrapper for the array itself,\n // recurse into the element schema and return `[Element]`. The element\n // type is named after `typeName_Item` so callers writing a list-style\n // query against `meTeamList: [TeamListOutput_Item]` get a meaningful\n // SDL name.\n if (jsonSchema.type === 'array') {\n const items = jsonSchema.items as JsonSchema | undefined;\n if (!items) return '[JSON]';\n const itemTypeName = jsonSchemaToGraphQLType(items, `${typeName}_Item`, namedTypes, 'output');\n return `[${itemTypeName}]`;\n }\n\n // Union (oneOf / anyOf) at the root output position.\n const variants = pickVariants(jsonSchema);\n if (variants) {\n return registerUnionType(typeName, variants, namedTypes);\n }\n\n // Reserve before recursing.\n namedTypes.set(typeName, '');\n\n const properties = (jsonSchema.properties ?? {}) as Record<string, JsonSchema>;\n if (Object.keys(properties).length === 0) {\n // Nothing to emit; fall back to a JSON-shaped placeholder so the\n // field stays usable. We undo the reservation by removing the entry\n // so the caller's signature uses `JSON` instead.\n namedTypes.delete(typeName);\n return 'JSON';\n }\n\n const required = new Set(\n Array.isArray(jsonSchema.required) ? (jsonSchema.required as string[]) : [],\n );\n const lines: string[] = [`type ${typeName} {`];\n for (const [propName, propSchema] of Object.entries(properties)) {\n const graphqlType = jsonSchemaToGraphQLType(\n propSchema,\n `${typeName}_${capitalize(propName)}`,\n namedTypes,\n 'output',\n );\n const isRequired = required.has(propName);\n lines.push(` ${propName}: ${graphqlType}${isRequired ? '!' : ''}`);\n }\n lines.push('}');\n\n namedTypes.set(typeName, lines.join('\\n'));\n return typeName;\n}\n\n/**\n * Build a GraphQL union for an `oneOf` / `anyOf` schema. Each branch\n * must be an object schema; non-object branches make the union\n * unrepresentable and we fall back to `JSON`. Member types are named\n * after the discriminator literal when one is present (zod's\n * `discriminatedUnion` produces a `const` literal on the discriminator\n * field), otherwise positional (`Member1`, `Member2`).\n */\nfunction registerUnionType(\n typeName: string,\n variants: JsonSchema[],\n namedTypes: Map<string, string>,\n): string {\n if (namedTypes.has(typeName)) return typeName;\n\n // Reserve.\n namedTypes.set(typeName, '');\n\n const memberNames: string[] = [];\n const seen = new Set<string>();\n for (let i = 0; i < variants.length; i++) {\n const variant = variants[i];\n if (!variant || !isObjectSchema(variant)) {\n // Mixed-type union — bail. Drop the reservation so the field\n // signature falls back to `JSON`.\n namedTypes.delete(typeName);\n return 'JSON';\n }\n const discriminator = pickDiscriminatorLabel(variant);\n let memberName = discriminator\n ? `${typeName}_${pascalize(discriminator)}`\n : `${typeName}_Member${i + 1}`;\n // Ensure uniqueness within this union (two branches with the same\n // discriminator would be a Zod modelling bug, but we keep the\n // generator robust).\n let suffix = 2;\n while (seen.has(memberName)) {\n memberName = `${typeName}_${pascalize(discriminator ?? 'Member')}${suffix++}`;\n }\n seen.add(memberName);\n registerOutputType(memberName, variant, namedTypes);\n memberNames.push(memberName);\n }\n\n namedTypes.set(typeName, `union ${typeName} = ${memberNames.join(' | ')}`);\n return typeName;\n}\n\n/**\n * Returns the variant list for a `oneOf` / `anyOf` schema, or `null` if\n * the schema doesn't carry one. `allOf` is not treated as a union — it\n * means \"intersect all\" and the JSON schema produced by Zod for plain\n * objects with shared fields would use it; we leave that as a future\n * improvement.\n */\nfunction pickVariants(schema: JsonSchema): JsonSchema[] | null {\n if (Array.isArray(schema.oneOf)) return schema.oneOf as JsonSchema[];\n if (Array.isArray(schema.anyOf)) return schema.anyOf as JsonSchema[];\n return null;\n}\n\n/**\n * Treat a schema as an object if it has `type: 'object'` or carries\n * `properties` (zod sometimes omits the explicit type when the shape is\n * obvious).\n */\nfunction isObjectSchema(schema: JsonSchema): boolean {\n if (!schema || typeof schema !== 'object') return false;\n if (schema.type === 'object') return true;\n if (schema.properties && typeof schema.properties === 'object') return true;\n return false;\n}\n\n/**\n * Find the literal value of a discriminator-style property in an object\n * schema. Zod's `z.discriminatedUnion('kind', [...])` emits each branch\n * with `properties.kind = { type: 'string', const: 'MEMBER' }` (or\n * `enum: ['MEMBER']`). We surface that literal so union member type\n * names can carry domain meaning instead of positional indexes.\n */\nfunction pickDiscriminatorLabel(schema: JsonSchema): string | null {\n const properties = (schema.properties ?? {}) as Record<string, JsonSchema>;\n for (const propSchema of Object.values(properties)) {\n if (!propSchema || typeof propSchema !== 'object') continue;\n const constValue = (propSchema as { const?: unknown }).const;\n if (typeof constValue === 'string' && constValue.length > 0) {\n return constValue;\n }\n const enumValue = (propSchema as { enum?: unknown[] }).enum;\n if (\n Array.isArray(enumValue) &&\n enumValue.length === 1 &&\n typeof enumValue[0] === 'string' &&\n enumValue[0].length > 0\n ) {\n return enumValue[0];\n }\n }\n return null;\n}\n\n/**\n * Converts a JSON schema fragment to a GraphQL type expression.\n *\n * For nested object shapes the function registers a new named type via\n * the supplied `namedTypes` map and returns its name. Arrays produce\n * `[ItemType]` and recurse into the element schema. Scalars map to the\n * GraphQL built-ins. Anything we can't represent (e.g. mixed-type\n * unions, schemas with no shape information) falls back to the `JSON`\n * scalar so the field stays queryable as an opaque value.\n *\n * `kind: 'input'` walks down through `registerInputType` so nested\n * structures stay on the input-types side; `kind: 'output'` mirrors\n * that for output types and unions.\n */\nfunction jsonSchemaToGraphQLType(\n schema: JsonSchema,\n parentTypeName: string,\n namedTypes: Map<string, string>,\n kind: 'input' | 'output',\n): string {\n if (!schema || typeof schema !== 'object') return 'JSON';\n\n // Handle enum (single-value enums and full enums alike → String for\n // now; named GraphQL enums are a future improvement).\n if (schema.enum && Array.isArray(schema.enum)) {\n return 'String';\n }\n\n // Union — only meaningful at output-position. Inputs collapse to JSON\n // because the GraphQL spec forbids unions in input types.\n const variants = pickVariants(schema);\n if (variants) {\n if (kind === 'input') return 'JSON';\n return registerUnionType(parentTypeName, variants, namedTypes);\n }\n\n const type = schema.type as string | undefined;\n\n switch (type) {\n case 'string':\n return 'String';\n case 'integer':\n return 'Int';\n case 'number':\n return 'Float';\n case 'boolean':\n return 'Boolean';\n case 'array': {\n const items = schema.items as JsonSchema | undefined;\n if (!items) return '[JSON]';\n const itemType = jsonSchemaToGraphQLType(\n items,\n // Singular-ish name for array element types — drop a trailing\n // 's' when present so `Tags[]` becomes `…_Tag`. Cheap heuristic;\n // fine for the conventional case.\n parentTypeName.replace(/s$/, ''),\n namedTypes,\n kind,\n );\n return `[${itemType}]`;\n }\n case 'object':\n if (kind === 'input') {\n return registerInputType(parentTypeName, schema, namedTypes);\n }\n return registerOutputType(parentTypeName, schema, namedTypes);\n default:\n // Object without explicit type but has properties.\n if (isObjectSchema(schema)) {\n if (kind === 'input') {\n return registerInputType(parentTypeName, schema, namedTypes);\n }\n return registerOutputType(parentTypeName, schema, namedTypes);\n }\n return 'JSON';\n }\n}\n\n/**\n * Capitalizes the first letter of a string.\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Pascal-case a string by uppercasing each segment between non-alnum\n * runs. Used to turn discriminator literals (which may be `kebab-case`,\n * `snake_case`, or `UPPER_SNAKE`) into safe GraphQL type-name segments.\n */\nfunction pascalize(str: string): string {\n return str\n .split(/[^a-zA-Z0-9]+/g)\n .filter((part) => part.length > 0)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\n .join('');\n}\n\n/**\n * Escapes a string for use in SDL quoted string literals.\n * Handles backslashes, quotes, newlines, and triple-quote injection.\n */\nfunction escapeSDLString(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t');\n}\n"],"mappings":"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","names":[]}
@@ -1029,14 +1029,15 @@ function buildFieldLine(fieldId, field, namedTypes, includeDescriptions, include
1029
1029
  parts.push(` """${escaped}"""`);
1030
1030
  }
1031
1031
  const inputTypeName = field.input ? `${capitalize(fieldId)}Input` : void 0;
1032
- const outputTypeName = field.output ? `${capitalize(fieldId)}Output` : void 0;
1032
+ const outputRootName = field.output ? `${capitalize(fieldId)}Output` : void 0;
1033
1033
  if (field.input && inputTypeName) {
1034
1034
  const jsonSchema = field.input.toJsonSchema();
1035
- namedTypes.set(inputTypeName, buildInputType(inputTypeName, jsonSchema));
1035
+ registerInputType(inputTypeName, jsonSchema, namedTypes);
1036
1036
  }
1037
- if (field.output && outputTypeName) {
1037
+ let outputTypeName;
1038
+ if (field.output && outputRootName) {
1038
1039
  const jsonSchema = field.output.toJsonSchema();
1039
- namedTypes.set(outputTypeName, buildOutputType(outputTypeName, jsonSchema));
1040
+ outputTypeName = registerOutputType(outputRootName, jsonSchema, namedTypes);
1040
1041
  }
1041
1042
  let signature = ` ${fieldId}`;
1042
1043
  if (inputTypeName) {
@@ -1053,45 +1054,124 @@ function buildFieldLine(fieldId, field, namedTypes, includeDescriptions, include
1053
1054
  }
1054
1055
  return signature;
1055
1056
  }
1056
- function buildInputType(typeName, jsonSchema) {
1057
+ function registerInputType(typeName, jsonSchema, namedTypes) {
1058
+ if (namedTypes.has(typeName)) return typeName;
1059
+ namedTypes.set(typeName, "");
1060
+ const properties = jsonSchema.properties ?? {};
1061
+ const required = new Set(
1062
+ Array.isArray(jsonSchema.required) ? jsonSchema.required : []
1063
+ );
1057
1064
  const lines = [`input ${typeName} {`];
1058
- if (jsonSchema.properties && typeof jsonSchema.properties === "object") {
1059
- const required = new Set(
1060
- Array.isArray(jsonSchema.required) ? jsonSchema.required : []
1065
+ for (const [propName, propSchema] of Object.entries(properties)) {
1066
+ const graphqlType = jsonSchemaToGraphQLType(
1067
+ propSchema,
1068
+ `${typeName}_${capitalize(propName)}`,
1069
+ namedTypes,
1070
+ "input"
1061
1071
  );
1062
- for (const [propName, propSchema] of Object.entries(
1063
- jsonSchema.properties
1064
- )) {
1065
- const graphqlType = jsonSchemaToGraphQLType(propSchema);
1066
- const isRequired = required.has(propName);
1067
- lines.push(` ${propName}: ${graphqlType}${isRequired ? "!" : ""}`);
1068
- }
1072
+ const isRequired = required.has(propName);
1073
+ lines.push(` ${propName}: ${graphqlType}${isRequired ? "!" : ""}`);
1069
1074
  }
1070
1075
  lines.push("}");
1071
- return lines.join("\n");
1076
+ namedTypes.set(typeName, lines.join("\n"));
1077
+ return typeName;
1072
1078
  }
1073
- function buildOutputType(typeName, jsonSchema) {
1079
+ function registerOutputType(typeName, jsonSchema, namedTypes) {
1080
+ if (namedTypes.has(typeName)) return typeName;
1081
+ if (jsonSchema.type === "array") {
1082
+ const items = jsonSchema.items;
1083
+ if (!items) return "[JSON]";
1084
+ const itemTypeName = jsonSchemaToGraphQLType(items, `${typeName}_Item`, namedTypes, "output");
1085
+ return `[${itemTypeName}]`;
1086
+ }
1087
+ const variants = pickVariants(jsonSchema);
1088
+ if (variants) {
1089
+ return registerUnionType(typeName, variants, namedTypes);
1090
+ }
1091
+ namedTypes.set(typeName, "");
1092
+ const properties = jsonSchema.properties ?? {};
1093
+ if (Object.keys(properties).length === 0) {
1094
+ namedTypes.delete(typeName);
1095
+ return "JSON";
1096
+ }
1097
+ const required = new Set(
1098
+ Array.isArray(jsonSchema.required) ? jsonSchema.required : []
1099
+ );
1074
1100
  const lines = [`type ${typeName} {`];
1075
- if (jsonSchema.properties && typeof jsonSchema.properties === "object") {
1076
- const required = new Set(
1077
- Array.isArray(jsonSchema.required) ? jsonSchema.required : []
1101
+ for (const [propName, propSchema] of Object.entries(properties)) {
1102
+ const graphqlType = jsonSchemaToGraphQLType(
1103
+ propSchema,
1104
+ `${typeName}_${capitalize(propName)}`,
1105
+ namedTypes,
1106
+ "output"
1078
1107
  );
1079
- for (const [propName, propSchema] of Object.entries(
1080
- jsonSchema.properties
1081
- )) {
1082
- const graphqlType = jsonSchemaToGraphQLType(propSchema);
1083
- const isRequired = required.has(propName);
1084
- lines.push(` ${propName}: ${graphqlType}${isRequired ? "!" : ""}`);
1085
- }
1108
+ const isRequired = required.has(propName);
1109
+ lines.push(` ${propName}: ${graphqlType}${isRequired ? "!" : ""}`);
1086
1110
  }
1087
1111
  lines.push("}");
1088
- return lines.join("\n");
1112
+ namedTypes.set(typeName, lines.join("\n"));
1113
+ return typeName;
1114
+ }
1115
+ function registerUnionType(typeName, variants, namedTypes) {
1116
+ if (namedTypes.has(typeName)) return typeName;
1117
+ namedTypes.set(typeName, "");
1118
+ const memberNames = [];
1119
+ const seen = /* @__PURE__ */ new Set();
1120
+ for (let i = 0; i < variants.length; i++) {
1121
+ const variant = variants[i];
1122
+ if (!variant || !isObjectSchema(variant)) {
1123
+ namedTypes.delete(typeName);
1124
+ return "JSON";
1125
+ }
1126
+ const discriminator = pickDiscriminatorLabel(variant);
1127
+ let memberName = discriminator ? `${typeName}_${pascalize(discriminator)}` : `${typeName}_Member${i + 1}`;
1128
+ let suffix = 2;
1129
+ while (seen.has(memberName)) {
1130
+ memberName = `${typeName}_${pascalize(discriminator ?? "Member")}${suffix++}`;
1131
+ }
1132
+ seen.add(memberName);
1133
+ registerOutputType(memberName, variant, namedTypes);
1134
+ memberNames.push(memberName);
1135
+ }
1136
+ namedTypes.set(typeName, `union ${typeName} = ${memberNames.join(" | ")}`);
1137
+ return typeName;
1138
+ }
1139
+ function pickVariants(schema) {
1140
+ if (Array.isArray(schema.oneOf)) return schema.oneOf;
1141
+ if (Array.isArray(schema.anyOf)) return schema.anyOf;
1142
+ return null;
1143
+ }
1144
+ function isObjectSchema(schema) {
1145
+ if (!schema || typeof schema !== "object") return false;
1146
+ if (schema.type === "object") return true;
1147
+ if (schema.properties && typeof schema.properties === "object") return true;
1148
+ return false;
1089
1149
  }
1090
- function jsonSchemaToGraphQLType(schema) {
1150
+ function pickDiscriminatorLabel(schema) {
1151
+ const properties = schema.properties ?? {};
1152
+ for (const propSchema of Object.values(properties)) {
1153
+ if (!propSchema || typeof propSchema !== "object") continue;
1154
+ const constValue = propSchema.const;
1155
+ if (typeof constValue === "string" && constValue.length > 0) {
1156
+ return constValue;
1157
+ }
1158
+ const enumValue = propSchema.enum;
1159
+ if (Array.isArray(enumValue) && enumValue.length === 1 && typeof enumValue[0] === "string" && enumValue[0].length > 0) {
1160
+ return enumValue[0];
1161
+ }
1162
+ }
1163
+ return null;
1164
+ }
1165
+ function jsonSchemaToGraphQLType(schema, parentTypeName, namedTypes, kind) {
1091
1166
  if (!schema || typeof schema !== "object") return "JSON";
1092
1167
  if (schema.enum && Array.isArray(schema.enum)) {
1093
1168
  return "String";
1094
1169
  }
1170
+ const variants = pickVariants(schema);
1171
+ if (variants) {
1172
+ if (kind === "input") return "JSON";
1173
+ return registerUnionType(parentTypeName, variants, namedTypes);
1174
+ }
1095
1175
  const type = schema.type;
1096
1176
  switch (type) {
1097
1177
  case "string":
@@ -1104,19 +1184,29 @@ function jsonSchemaToGraphQLType(schema) {
1104
1184
  return "Boolean";
1105
1185
  case "array": {
1106
1186
  const items = schema.items;
1107
- if (items) {
1108
- return `[${jsonSchemaToGraphQLType(items)}]`;
1109
- }
1110
- return "[JSON]";
1187
+ if (!items) return "[JSON]";
1188
+ const itemType = jsonSchemaToGraphQLType(
1189
+ items,
1190
+ // Singular-ish name for array element types — drop a trailing
1191
+ // 's' when present so `Tags[]` becomes `…_Tag`. Cheap heuristic;
1192
+ // fine for the conventional case.
1193
+ parentTypeName.replace(/s$/, ""),
1194
+ namedTypes,
1195
+ kind
1196
+ );
1197
+ return `[${itemType}]`;
1111
1198
  }
1112
1199
  case "object":
1113
- return "JSON";
1114
- default:
1115
- if (schema.oneOf || schema.anyOf || schema.allOf) {
1116
- return "JSON";
1200
+ if (kind === "input") {
1201
+ return registerInputType(parentTypeName, schema, namedTypes);
1117
1202
  }
1118
- if (schema.properties) {
1119
- return "JSON";
1203
+ return registerOutputType(parentTypeName, schema, namedTypes);
1204
+ default:
1205
+ if (isObjectSchema(schema)) {
1206
+ if (kind === "input") {
1207
+ return registerInputType(parentTypeName, schema, namedTypes);
1208
+ }
1209
+ return registerOutputType(parentTypeName, schema, namedTypes);
1120
1210
  }
1121
1211
  return "JSON";
1122
1212
  }
@@ -1124,6 +1214,9 @@ function jsonSchemaToGraphQLType(schema) {
1124
1214
  function capitalize(str) {
1125
1215
  return str.charAt(0).toUpperCase() + str.slice(1);
1126
1216
  }
1217
+ function pascalize(str) {
1218
+ return str.split(/[^a-zA-Z0-9]+/g).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
1219
+ }
1127
1220
  function escapeSDLString(str) {
1128
1221
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1129
1222
  }