bunsane 0.1.4 → 0.2.0

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.
Files changed (257) hide show
  1. package/.claude/settings.local.json +47 -0
  2. package/.claude/skills/update-memory.md +74 -0
  3. package/.prettierrc +4 -0
  4. package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
  5. package/.serena/memories/architecture.md +154 -0
  6. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
  7. package/.serena/memories/code_style_and_conventions.md +76 -0
  8. package/.serena/memories/project_overview.md +43 -0
  9. package/.serena/memories/schema-dsl-plan.md +107 -0
  10. package/.serena/memories/suggested_commands.md +80 -0
  11. package/.serena/memories/typescript-compilation-status.md +54 -0
  12. package/.serena/project.yml +114 -0
  13. package/TODO.md +1 -7
  14. package/bun.lock +150 -4
  15. package/bunfig.toml +10 -0
  16. package/config/cache.config.ts +77 -0
  17. package/config/upload.config.ts +4 -5
  18. package/core/App.ts +870 -123
  19. package/core/ArcheType.ts +2268 -377
  20. package/core/BatchLoader.ts +181 -71
  21. package/core/Config.ts +153 -0
  22. package/core/Decorators.ts +4 -1
  23. package/core/Entity.ts +621 -92
  24. package/core/EntityHookManager.ts +1 -1
  25. package/core/EntityInterface.ts +3 -1
  26. package/core/EntityManager.ts +1 -13
  27. package/core/ErrorHandler.ts +8 -2
  28. package/core/Logger.ts +9 -0
  29. package/core/Middleware.ts +34 -0
  30. package/core/RequestContext.ts +5 -1
  31. package/core/RequestLoaders.ts +227 -93
  32. package/core/SchedulerManager.ts +193 -52
  33. package/core/cache/CacheAnalytics.ts +399 -0
  34. package/core/cache/CacheFactory.ts +145 -0
  35. package/core/cache/CacheManager.ts +520 -0
  36. package/core/cache/CacheProvider.ts +34 -0
  37. package/core/cache/CacheWarmer.ts +157 -0
  38. package/core/cache/CompressionUtils.ts +110 -0
  39. package/core/cache/MemoryCache.ts +251 -0
  40. package/core/cache/MultiLevelCache.ts +180 -0
  41. package/core/cache/NoOpCache.ts +53 -0
  42. package/core/cache/RedisCache.ts +464 -0
  43. package/core/cache/TTLStrategy.ts +254 -0
  44. package/core/cache/index.ts +6 -0
  45. package/core/components/BaseComponent.ts +120 -0
  46. package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
  47. package/core/components/Decorators.ts +88 -0
  48. package/core/components/Interfaces.ts +7 -0
  49. package/core/components/index.ts +5 -0
  50. package/core/decorators/EntityHooks.ts +0 -3
  51. package/core/decorators/IndexedField.ts +26 -0
  52. package/core/decorators/ScheduledTask.ts +0 -47
  53. package/core/events/EntityLifecycleEvents.ts +1 -1
  54. package/core/health.ts +112 -0
  55. package/core/metadata/definitions/ArcheType.ts +14 -0
  56. package/core/metadata/definitions/Component.ts +9 -0
  57. package/core/metadata/definitions/gqlObject.ts +1 -1
  58. package/core/metadata/index.ts +42 -1
  59. package/core/metadata/metadata-storage.ts +28 -2
  60. package/core/middleware/AccessLog.ts +59 -0
  61. package/core/middleware/RequestId.ts +38 -0
  62. package/core/middleware/SecurityHeaders.ts +62 -0
  63. package/core/middleware/index.ts +3 -0
  64. package/core/scheduler/DistributedLock.ts +266 -0
  65. package/core/scheduler/index.ts +15 -0
  66. package/core/validateEnv.ts +92 -0
  67. package/database/DatabaseHelper.ts +416 -40
  68. package/database/IndexingStrategy.ts +342 -0
  69. package/database/PreparedStatementCache.ts +226 -0
  70. package/database/index.ts +32 -7
  71. package/database/sqlHelpers.ts +14 -2
  72. package/endpoints/archetypes.ts +362 -0
  73. package/endpoints/components.ts +58 -0
  74. package/endpoints/entity.ts +80 -0
  75. package/endpoints/index.ts +27 -0
  76. package/endpoints/query.ts +93 -0
  77. package/endpoints/stats.ts +76 -0
  78. package/endpoints/tables.ts +212 -0
  79. package/endpoints/types.ts +155 -0
  80. package/gql/ArchetypeOperations.ts +32 -86
  81. package/gql/Generator.ts +27 -315
  82. package/gql/GeneratorV2.ts +37 -0
  83. package/gql/builders/InputTypeBuilder.ts +99 -0
  84. package/gql/builders/ResolverBuilder.ts +234 -0
  85. package/gql/builders/TypeDefBuilder.ts +105 -0
  86. package/gql/builders/index.ts +3 -0
  87. package/gql/decorators/Upload.ts +1 -1
  88. package/gql/depthLimit.ts +85 -0
  89. package/gql/graph/GraphNode.ts +224 -0
  90. package/gql/graph/SchemaGraph.ts +278 -0
  91. package/gql/helpers.ts +8 -2
  92. package/gql/index.ts +56 -4
  93. package/gql/middleware.ts +79 -0
  94. package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
  95. package/gql/orchestration/index.ts +1 -0
  96. package/gql/scanner/ServiceScanner.ts +347 -0
  97. package/gql/schema/index.ts +458 -0
  98. package/gql/strategies/TypeGenerationStrategy.ts +329 -0
  99. package/gql/types.ts +1 -0
  100. package/gql/utils/TypeSignature.ts +220 -0
  101. package/gql/utils/index.ts +1 -0
  102. package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
  103. package/gql/visitors/DeduplicationVisitor.ts +82 -0
  104. package/gql/visitors/GraphVisitor.ts +78 -0
  105. package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
  106. package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
  107. package/gql/visitors/TypeCollectorVisitor.ts +79 -0
  108. package/gql/visitors/VisitorComposer.ts +96 -0
  109. package/gql/visitors/index.ts +7 -0
  110. package/package.json +59 -37
  111. package/plugins/index.ts +2 -2
  112. package/query/CTENode.ts +97 -0
  113. package/query/ComponentInclusionNode.ts +689 -0
  114. package/query/FilterBuilder.ts +127 -0
  115. package/query/FilterBuilderRegistry.ts +202 -0
  116. package/query/OrNode.ts +517 -0
  117. package/query/OrQuery.ts +42 -0
  118. package/query/Query.ts +1022 -0
  119. package/query/QueryContext.ts +170 -0
  120. package/query/QueryDAG.ts +122 -0
  121. package/query/QueryNode.ts +65 -0
  122. package/query/SourceNode.ts +53 -0
  123. package/query/builders/FullTextSearchBuilder.ts +236 -0
  124. package/query/index.ts +21 -0
  125. package/scheduler/index.ts +40 -8
  126. package/service/Service.ts +2 -1
  127. package/service/ServiceRegistry.ts +6 -5
  128. package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
  129. package/storage/S3StorageProvider.ts +316 -0
  130. package/{core/storage → storage}/StorageProvider.ts +7 -3
  131. package/studio/bun.lock +482 -0
  132. package/studio/index.html +13 -0
  133. package/studio/package.json +39 -0
  134. package/studio/postcss.config.js +6 -0
  135. package/studio/src/components/DataTable.tsx +211 -0
  136. package/studio/src/components/Layout.tsx +13 -0
  137. package/studio/src/components/PageContainer.tsx +9 -0
  138. package/studio/src/components/PageHeader.tsx +13 -0
  139. package/studio/src/components/SearchBar.tsx +57 -0
  140. package/studio/src/components/Sidebar.tsx +294 -0
  141. package/studio/src/components/ui/button.tsx +56 -0
  142. package/studio/src/components/ui/checkbox.tsx +26 -0
  143. package/studio/src/components/ui/input.tsx +25 -0
  144. package/studio/src/hooks/useDataTable.ts +131 -0
  145. package/studio/src/index.css +36 -0
  146. package/studio/src/lib/api.ts +186 -0
  147. package/studio/src/lib/utils.ts +13 -0
  148. package/studio/src/main.tsx +17 -0
  149. package/studio/src/pages/ArcheType.tsx +239 -0
  150. package/studio/src/pages/Components.tsx +124 -0
  151. package/studio/src/pages/EntityInspector.tsx +302 -0
  152. package/studio/src/pages/QueryRunner.tsx +246 -0
  153. package/studio/src/pages/Table.tsx +94 -0
  154. package/studio/src/pages/Welcome.tsx +241 -0
  155. package/studio/src/routes.tsx +45 -0
  156. package/studio/src/store/archeTypeSettings.ts +30 -0
  157. package/studio/src/store/studio.ts +65 -0
  158. package/studio/src/utils/columnHelpers.tsx +114 -0
  159. package/studio/studio-instructions.md +81 -0
  160. package/studio/tailwind.config.js +77 -0
  161. package/studio/tsconfig.json +24 -0
  162. package/studio/utils.ts +54 -0
  163. package/studio/vite.config.js +19 -0
  164. package/swagger/generator.ts +1 -1
  165. package/tests/e2e/http.test.ts +126 -0
  166. package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
  167. package/tests/fixtures/components/TestOrder.ts +23 -0
  168. package/tests/fixtures/components/TestProduct.ts +23 -0
  169. package/tests/fixtures/components/TestUser.ts +20 -0
  170. package/tests/fixtures/components/index.ts +6 -0
  171. package/tests/graphql/SchemaGeneration.test.ts +90 -0
  172. package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
  173. package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
  174. package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
  175. package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
  176. package/tests/integration/entity/Entity.persistence.test.ts +333 -0
  177. package/tests/integration/query/Query.exec.test.ts +523 -0
  178. package/tests/pglite-setup.ts +61 -0
  179. package/tests/setup.ts +164 -0
  180. package/tests/stress/BenchmarkRunner.ts +203 -0
  181. package/tests/stress/DataSeeder.ts +190 -0
  182. package/tests/stress/StressTestReporter.ts +229 -0
  183. package/tests/stress/cursor-perf-test.ts +171 -0
  184. package/tests/stress/fixtures/StressTestComponents.ts +58 -0
  185. package/tests/stress/index.ts +7 -0
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
  187. package/tests/unit/BatchLoader.test.ts +82 -0
  188. package/tests/unit/archetype/ArcheType.test.ts +107 -0
  189. package/tests/unit/cache/CacheManager.test.ts +347 -0
  190. package/tests/unit/cache/MemoryCache.test.ts +260 -0
  191. package/tests/unit/cache/RedisCache.test.ts +411 -0
  192. package/tests/unit/entity/Entity.components.test.ts +244 -0
  193. package/tests/unit/entity/Entity.test.ts +345 -0
  194. package/tests/unit/gql/depthLimit.test.ts +203 -0
  195. package/tests/unit/gql/operationMiddleware.test.ts +293 -0
  196. package/tests/unit/health/Health.test.ts +129 -0
  197. package/tests/unit/middleware/AccessLog.test.ts +37 -0
  198. package/tests/unit/middleware/Middleware.test.ts +98 -0
  199. package/tests/unit/middleware/RequestId.test.ts +54 -0
  200. package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
  201. package/tests/unit/query/FilterBuilder.test.ts +111 -0
  202. package/tests/unit/query/Query.test.ts +308 -0
  203. package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
  204. package/tests/unit/schema/schema-integration.test.ts +426 -0
  205. package/tests/unit/schema/schema.test.ts +580 -0
  206. package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
  207. package/tests/unit/upload/RestUpload.test.ts +267 -0
  208. package/tests/unit/validateEnv.test.ts +82 -0
  209. package/tests/utils/entity-tracker.ts +57 -0
  210. package/tests/utils/index.ts +13 -0
  211. package/tests/utils/test-context.ts +149 -0
  212. package/tsconfig.json +5 -1
  213. package/types/archetype.types.ts +6 -0
  214. package/types/hooks.types.ts +1 -1
  215. package/types/query.types.ts +110 -0
  216. package/types/scheduler.types.ts +68 -7
  217. package/types/upload.types.ts +1 -0
  218. package/{core → upload}/FileValidator.ts +10 -1
  219. package/upload/RestUpload.ts +130 -0
  220. package/{core/components → upload}/UploadComponent.ts +11 -11
  221. package/{core → upload}/UploadManager.ts +3 -3
  222. package/upload/index.ts +23 -7
  223. package/utils/UploadHelper.ts +27 -6
  224. package/utils/cronParser.ts +16 -6
  225. package/.github/workflows/deploy-docs.yml +0 -57
  226. package/core/Components.ts +0 -202
  227. package/core/EntityCache.ts +0 -15
  228. package/core/Query.ts +0 -880
  229. package/docs/README.md +0 -149
  230. package/docs/_coverpage.md +0 -36
  231. package/docs/_sidebar.md +0 -23
  232. package/docs/api/core.md +0 -568
  233. package/docs/api/hooks.md +0 -554
  234. package/docs/api/index.md +0 -222
  235. package/docs/api/query.md +0 -678
  236. package/docs/api/service.md +0 -744
  237. package/docs/core-concepts/archetypes.md +0 -512
  238. package/docs/core-concepts/components.md +0 -498
  239. package/docs/core-concepts/entity.md +0 -314
  240. package/docs/core-concepts/hooks.md +0 -683
  241. package/docs/core-concepts/query.md +0 -588
  242. package/docs/core-concepts/services.md +0 -647
  243. package/docs/examples/code-examples.md +0 -425
  244. package/docs/getting-started.md +0 -337
  245. package/docs/index.html +0 -97
  246. package/tests/bench/insert.bench.ts +0 -60
  247. package/tests/bench/relations.bench.ts +0 -270
  248. package/tests/bench/sorting.bench.ts +0 -416
  249. package/tests/component-hooks-simple.test.ts +0 -117
  250. package/tests/component-hooks.test.ts +0 -1461
  251. package/tests/component.test.ts +0 -339
  252. package/tests/errorHandling.test.ts +0 -155
  253. package/tests/hooks.test.ts +0 -667
  254. package/tests/query-sorting.test.ts +0 -101
  255. package/tests/query.test.ts +0 -81
  256. package/tests/relations.test.ts +0 -170
  257. package/tests/scheduler.test.ts +0 -724
@@ -0,0 +1,851 @@
1
+ import type { TypeGenerationStrategy } from "../strategies/TypeGenerationStrategy";
2
+ import { ZodTypeStrategy } from "../strategies/TypeGenerationStrategy";
3
+ import { GraphVisitor } from "./GraphVisitor";
4
+ import type { TypeNode, OperationNode, FieldNode, InputNode, ScalarNode } from "../graph/GraphNode";
5
+ import { logger as MainLogger } from "../../core/Logger";
6
+ import * as z from "zod";
7
+ import { ZodWeaver, asObjectType } from "@gqloom/zod";
8
+ import { weave } from "@gqloom/core";
9
+ import type { ZodType } from "zod";
10
+ import { GraphQLSchema, printSchema } from "graphql";
11
+ import BaseArcheType from "../../core/ArcheType";
12
+ import { getMetadataStorage } from "../../core/metadata";
13
+ import { getArchetypeSchema } from "../../core/ArcheType";
14
+ import { isSchemaInput, collectNestedTypeDefs, type SchemaType } from "../schema";
15
+
16
+ const logger = MainLogger.child({ scope: 'SchemaGeneratorVisitor' });
17
+
18
+ /**
19
+ * Visitor that generates the final GraphQL schema type definitions string.
20
+ * Replicates the V1 generation logic exactly to ensure identical output.
21
+ */
22
+ export class SchemaGeneratorVisitor extends GraphVisitor {
23
+ private typeDefs: string = '\n ';
24
+ private queryFields: string[] = [];
25
+ private mutationFields: string[] = [];
26
+ private subscriptionFields: string[] = [];
27
+ private definedTypes: Set<string> = new Set();
28
+ private finalized: boolean = false;
29
+ private inputTypes: string = '';
30
+ private scalarTypes: Set<string> = new Set();
31
+
32
+ constructor() {
33
+ super();
34
+ }
35
+
36
+ /**
37
+ * Add archetype type definitions before visiting nodes (like V1 does)
38
+ */
39
+ override beforeVisit(): void {
40
+ // Import required functions
41
+ const { weaveAllArchetypes, getAllArchetypeSchemas } = require('../../core/ArcheType');
42
+
43
+ // Add archetype types first (exactly like V1)
44
+ const fullSchema = weaveAllArchetypes();
45
+ if (fullSchema) {
46
+ const typeDefinitions = this.extractTypeDefinitions(fullSchema);
47
+ this.typeDefs += this.deduplicateTypeDefs(typeDefinitions.join(''));
48
+ } else {
49
+ const schemas = getAllArchetypeSchemas();
50
+ for (const { graphqlSchema } of schemas) {
51
+ const typeDefinitions = this.extractTypeDefinitions(graphqlSchema);
52
+ this.typeDefs += this.deduplicateTypeDefs(typeDefinitions.join(''));
53
+ }
54
+ }
55
+
56
+ // Add default scalars
57
+ this.typeDefs += 'scalar Upload\n';
58
+ this.typeDefs += 'scalar Date\n';
59
+ }
60
+
61
+ /**
62
+ * Extract type definitions from a GraphQL schema string (V1's extractTypeDefinitions)
63
+ */
64
+ private extractTypeDefinitions(schemaString: string): string[] {
65
+ const lines = schemaString.split('\n');
66
+ const typeDefinitions: string[] = [];
67
+ let currentType = '';
68
+ let inTypeDefinition = false;
69
+
70
+ for (const line of lines) {
71
+ const trimmed = line.trim();
72
+ if (trimmed.startsWith('type ') || trimmed.startsWith('input ') || trimmed.startsWith('enum ') ||
73
+ trimmed.startsWith('scalar ') || trimmed.startsWith('interface ') || trimmed.startsWith('union ')) {
74
+ if (inTypeDefinition) {
75
+ typeDefinitions.push(currentType);
76
+ }
77
+ currentType = line + '\n';
78
+ inTypeDefinition = true;
79
+ } else if (inTypeDefinition) {
80
+ currentType += line + '\n';
81
+ if (trimmed === '}') {
82
+ typeDefinitions.push(currentType);
83
+ currentType = '';
84
+ inTypeDefinition = false;
85
+ }
86
+ }
87
+ }
88
+ if (inTypeDefinition) {
89
+ typeDefinitions.push(currentType);
90
+ }
91
+ return typeDefinitions;
92
+ }
93
+
94
+ /**
95
+ * Deduplicate all type definitions (V1's deduplicateTypeDefs)
96
+ */
97
+ private deduplicateTypeDefs(typeDefs: string): string {
98
+ const lines = typeDefs.split('\n');
99
+ const typeDefinitions = new Map<string, string>();
100
+ let currentType = '';
101
+ let currentTypeName = '';
102
+ let inTypeDefinition = false;
103
+
104
+ for (const line of lines) {
105
+ const trimmed = line.trim();
106
+ const typeMatch = trimmed.match(/^(type|input|enum|scalar|interface|union)\s+(\w+)/);
107
+
108
+ if (typeMatch) {
109
+ // Save previous type if we were in one
110
+ if (inTypeDefinition && currentTypeName && !typeDefinitions.has(currentTypeName)) {
111
+ typeDefinitions.set(currentTypeName, currentType);
112
+ }
113
+
114
+ // Start new type
115
+ currentTypeName = typeMatch[2] || '';
116
+ currentType = line + '\n';
117
+ inTypeDefinition = true;
118
+ } else if (inTypeDefinition) {
119
+ currentType += line + '\n';
120
+
121
+ // Check if this is the closing brace
122
+ if (trimmed === '}') {
123
+ if (!typeDefinitions.has(currentTypeName)) {
124
+ typeDefinitions.set(currentTypeName, currentType);
125
+ }
126
+ currentType = '';
127
+ currentTypeName = '';
128
+ inTypeDefinition = false;
129
+ }
130
+ } else {
131
+ // Other lines, like comments or empty lines
132
+ if (!typeDefinitions.has('__other')) {
133
+ typeDefinitions.set('__other', '');
134
+ }
135
+ typeDefinitions.set('__other', typeDefinitions.get('__other')! + line + '\n');
136
+ }
137
+ }
138
+
139
+ // Handle last type if file doesn't end with closing brace
140
+ if (inTypeDefinition && currentTypeName && !typeDefinitions.has(currentTypeName)) {
141
+ typeDefinitions.set(currentTypeName, currentType);
142
+ }
143
+
144
+ const result = Array.from(typeDefinitions.values()).join('');
145
+ return result;
146
+ }
147
+
148
+ visitScalarNode(node: ScalarNode): void {
149
+ if (!this.definedTypes.has(node.id)) {
150
+ this.typeDefs += `scalar ${node.name}\n`;
151
+ this.scalarTypes.add(node.name);
152
+ this.definedTypes.add(node.id);
153
+ logger.trace(`Added scalar type: ${node.name}`);
154
+ }
155
+ }
156
+
157
+ visitTypeNode(node: TypeNode): void {
158
+ // Archetype TypeNodes are handled by archetype weaving in beforeVisit().
159
+ // TypeNodes from @GraphQLObjectType must be emitted here if not already defined.
160
+ if (!this.definedTypes.has(node.name)) {
161
+ this.typeDefs += node.typeDef + '\n';
162
+ this.definedTypes.add(node.name);
163
+ logger.trace(`Added type definition: ${node.name}`);
164
+ }
165
+ }
166
+
167
+ visitOperationNode(node: OperationNode): void {
168
+ // operationType is a property of OperationNode, not in metadata
169
+ const operationType = node.operationType;
170
+
171
+ // Build proper field definition from metadata (V1 style)
172
+ const fieldDef = this.buildFieldDefinition(node);
173
+
174
+ // OperationType enum values are QUERY, MUTATION, SUBSCRIPTION (all caps)
175
+ if (operationType === 'QUERY') {
176
+ this.queryFields.push(fieldDef);
177
+ logger.trace(`Added query field: ${fieldDef}`);
178
+ } else if (operationType === 'MUTATION') {
179
+ this.mutationFields.push(fieldDef);
180
+ logger.trace(`Added mutation field: ${fieldDef}`);
181
+ } else if (operationType === 'SUBSCRIPTION') {
182
+ this.subscriptionFields.push(fieldDef);
183
+ logger.trace(`Added subscription field: ${fieldDef}`);
184
+ }
185
+ }
186
+
187
+ visitSubscriptionNode(node: OperationNode): void {
188
+ // Build proper field definition from metadata (V1 style)
189
+ const fieldDef = this.buildFieldDefinition(node);
190
+
191
+ this.subscriptionFields.push(fieldDef);
192
+ logger.trace(`Added subscription field: ${fieldDef}`);
193
+ }
194
+
195
+ visitFieldNode(node: FieldNode): void {
196
+ // Field nodes don't directly contribute to schema, they're used by resolvers
197
+ logger.trace(`Visited field node: ${node.id}`);
198
+ }
199
+
200
+ visitInputNode(node: InputNode): void {
201
+ if (!this.definedTypes.has(node.name)) {
202
+ this.inputTypes += node.typeDef + '\n';
203
+ this.definedTypes.add(node.name);
204
+ logger.trace(`Added input type: ${node.name}`);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Build a complete GraphQL field definition exactly like V1.
210
+ * Format: "fieldName(input: InputType!): OutputType"
211
+ */
212
+ private buildFieldDefinition(node: OperationNode): string {
213
+ const name = node.name;
214
+ const { input, output, scalarTypes } = node.metadata;
215
+ let fieldDef = name;
216
+
217
+ // Handle input: Schema DSL → Zod (legacy) → Record (fallback)
218
+ if (input) {
219
+ const inputTypeName = `${name}Input`;
220
+
221
+ if (isSchemaInput(input)) {
222
+ // Schema DSL path
223
+ const result = this.generateInputFromSchema(input as Record<string, SchemaType>, inputTypeName);
224
+ if (!this.definedTypes.has(inputTypeName)) {
225
+ this.typeDefs += result.typeDefs;
226
+ this.definedTypes.add(inputTypeName);
227
+ }
228
+ fieldDef += `(input: ${inputTypeName}!)`;
229
+ } else if (input && typeof input === 'object' && '_def' in input) {
230
+ // Legacy Zod schema path (deprecated — use Schema DSL `t.` API instead)
231
+ logger.warn(`Operation "${name}" uses a Zod schema as input. This is deprecated and will be removed in a future version. Use the Schema DSL (t.string(), t.object(), etc.) instead.`);
232
+ const inputTypeDef = this.generateInputTypeFromZod(input as ZodType, inputTypeName, scalarTypes || new Set());
233
+ if (inputTypeDef) {
234
+ this.typeDefs += inputTypeDef;
235
+ }
236
+
237
+ let inputNullability = '!';
238
+ try {
239
+ const allowsUndefined = !!(input && typeof (input as any).safeParse === 'function' && (input as any).safeParse(undefined).success);
240
+ const allowsNull = !!(input && typeof (input as any).safeParse === 'function' && (input as any).safeParse(null).success);
241
+ if (allowsUndefined || allowsNull) inputNullability = '';
242
+ } catch (e) {
243
+ inputNullability = '!';
244
+ }
245
+
246
+ fieldDef += `(input: ${inputTypeName}${inputNullability})`;
247
+ } else if (typeof input === 'object') {
248
+ // Fallback: Record<string, GraphQLType> format
249
+ const inputTypeDef = `input ${inputTypeName} {\n${Object.entries(input).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
250
+ if (!this.definedTypes.has(inputTypeName)) {
251
+ this.typeDefs += inputTypeDef;
252
+ this.definedTypes.add(inputTypeName);
253
+ }
254
+ fieldDef += `(input: ${inputTypeName}!)`;
255
+ }
256
+ }
257
+
258
+ // Handle output exactly like V1
259
+ const outputType = this.extractOutputType(output);
260
+ fieldDef += `: ${outputType}`;
261
+
262
+ return fieldDef;
263
+ }
264
+
265
+ /**
266
+ * Extract output type exactly like V1
267
+ */
268
+ private extractOutputType(output: any): string {
269
+ if (typeof output === 'string') {
270
+ return output;
271
+ } else if (Array.isArray(output)) {
272
+ // Handle array of archetypes: [serviceAreaArcheType]
273
+ const archetypeInstance = output[0];
274
+ const typeName = this.getArchetypeTypeName(archetypeInstance);
275
+ if (typeName) {
276
+ return `[${typeName}]`;
277
+ } else {
278
+ logger.warn(`Invalid array output type, expected archetype instance`);
279
+ return '[Any]';
280
+ }
281
+ } else if (output instanceof BaseArcheType) {
282
+ // Handle single archetype instance: serviceAreaArcheType
283
+ const typeName = this.getArchetypeTypeName(output);
284
+ if (typeName) {
285
+ return typeName;
286
+ } else {
287
+ logger.warn(`Could not determine type name for archetype`);
288
+ return 'Any';
289
+ }
290
+ } else if (typeof output === 'object' && output !== null) {
291
+ // Legacy object output format - V1 would generate an output type
292
+ // For now, return String as fallback (V1 would create outputName+"Output")
293
+ return 'String';
294
+ } else {
295
+ // Default case when output is not specified - assume String (like V1)
296
+ return 'String';
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Get archetype type name exactly like V1's getArchetypeTypeName
302
+ */
303
+ private getArchetypeTypeName(archetypeInstance: any): string | null {
304
+ if (!archetypeInstance || !(archetypeInstance instanceof BaseArcheType)) {
305
+ return null;
306
+ }
307
+
308
+ const storage = getMetadataStorage();
309
+ const className = archetypeInstance.constructor.name;
310
+
311
+ // Look up the archetype metadata by class name to get the custom name
312
+ const archetypeMetadata = storage.archetypes.find(a => a.target?.name === className);
313
+
314
+ if (archetypeMetadata?.name) {
315
+ // Use the custom name from @ArcheType("CustomName") decorator
316
+ logger.trace(`Found custom archetype name: ${archetypeMetadata.name} for class ${className}`);
317
+
318
+ // Ensure schema is generated and cached
319
+ try {
320
+ if (!getArchetypeSchema(archetypeMetadata.name)) {
321
+ archetypeInstance.getZodObjectSchema();
322
+ }
323
+ } catch (error) {
324
+ logger.warn(`Failed to generate schema for archetype ${archetypeMetadata.name}: ${error}`);
325
+ }
326
+
327
+ return archetypeMetadata.name;
328
+ }
329
+
330
+ // Fallback: infer from class name
331
+ const inferredName = className.replace(/ArcheType$/, '');
332
+ logger.trace(`Using inferred archetype name: ${inferredName} for class ${className}`);
333
+
334
+ try {
335
+ if (!getArchetypeSchema(inferredName)) {
336
+ archetypeInstance.getZodObjectSchema();
337
+ }
338
+ } catch (error) {
339
+ logger.warn(`Failed to generate schema for archetype ${inferredName}: ${error}`);
340
+ }
341
+
342
+ return inferredName;
343
+ }
344
+
345
+ /**
346
+ * Generate input type from Schema DSL definitions.
347
+ * Collects nested type definitions (depth-first) and builds a Zod schema for validation.
348
+ */
349
+ private generateInputFromSchema(
350
+ input: Record<string, SchemaType>,
351
+ inputName: string,
352
+ ): { typeDefs: string; zodSchema: ZodType } {
353
+ const collected = collectNestedTypeDefs(input);
354
+
355
+ // Build the top-level input type
356
+ const fields = Object.entries(input)
357
+ .map(([key, schema]) => ` ${key}: ${schema.toGraphQL()}`)
358
+ .join("\n");
359
+ collected.set(inputName, `input ${inputName} {\n${fields}\n}`);
360
+
361
+ // Build Zod schema for runtime validation
362
+ const zodShape: Record<string, ZodType> = {};
363
+ for (const [key, schema] of Object.entries(input)) {
364
+ zodShape[key] = schema.toZod();
365
+ }
366
+
367
+ return {
368
+ typeDefs: Array.from(collected.values()).join("\n\n") + "\n",
369
+ zodSchema: z.object(zodShape),
370
+ };
371
+ }
372
+
373
+ /**
374
+ * Generate input type from Zod schema exactly like V1
375
+ * Returns the generated typedef string, or empty string if already defined
376
+ */
377
+ private generateInputTypeFromZod(zodSchema: ZodType, inputName: string, scalarTypes: Set<string>): string {
378
+ try {
379
+ // Store original input for validation (before any preprocessing)
380
+ let originalInput: any = zodSchema;
381
+
382
+ // Add __typename to input zod object, handling optional schemas
383
+ let innerInput: any = zodSchema;
384
+ const wasOptional = zodSchema instanceof z.ZodOptional;
385
+ if (wasOptional) {
386
+ innerInput = (zodSchema as any).unwrap();
387
+ }
388
+
389
+ // Preprocess schema: Replace z.union containing scalar literals with z.string() recursively
390
+ // This is needed because GQLoom's weave doesn't handle z.union or z.literal well
391
+ // We'll replace the String type with the actual scalar type after weaving
392
+ const scalarFieldNames = new Map<string, string>(); // Track which fields should be scalars (full path)
393
+
394
+ // Recursive function to process schemas and handle all Zod wrapper types
395
+ const processSchema = (schema: any, path: string[] = []): any => {
396
+ if (!schema) return schema;
397
+
398
+ // Handle the case where _def is missing or empty (can happen with .omit()/.extend())
399
+ if (!schema._def || (typeof schema._def === 'object' && Object.keys(schema._def).length === 0)) {
400
+ // Try to identify the schema type by checking other properties
401
+ // In Zod v4, some schemas have a 'parse' method and other identifying features
402
+ const hasShape = typeof schema.shape === 'object' || typeof schema.shape === 'function';
403
+ const hasElement = typeof schema.element === 'object';
404
+ const schemaStr = typeof schema === 'string' ? `"${schema}"` : (typeof schema === 'object' ? JSON.stringify(schema).slice(0, 200) : String(schema));
405
+ logger.trace(`Schema at path ${path.join('.')} has empty _def, hasShape=${hasShape}, hasElement=${hasElement}, type=${typeof schema}, value=${schemaStr}`);
406
+
407
+ // If the schema is actually a string literal value (not a Zod schema), it's likely a z.literal value
408
+ if (typeof schema === 'string') {
409
+ logger.trace(`Found string literal at ${path.join('.')}: "${schema}", converting to z.string()`);
410
+ return z.string();
411
+ }
412
+
413
+ // If the schema is an object without _def, it might be the raw element value, not a Zod schema
414
+ // For arrays from .extend(), the element type might be lost - use z.any() as fallback
415
+ if (typeof schema === 'object' && !schema._def) {
416
+ // Check if it's an array-like object (has numeric keys)
417
+ const numericKeys = Object.keys(schema).filter(k => !isNaN(Number(k)));
418
+ if (numericKeys.length > 0) {
419
+ // This might be a tuple or array with positional elements - treat as z.any()
420
+ logger.trace(`Found array-like object at ${path.join('.')}, using z.any()`);
421
+ return z.any();
422
+ }
423
+
424
+ // If it looks like an object with a shape, try to process it as an object
425
+ if (hasShape) {
426
+ const shape = typeof schema.shape === 'function' ? schema.shape() : schema.shape;
427
+ if (shape && Object.keys(shape).length > 0) {
428
+ const processedShape: any = {};
429
+ for (const [key, value] of Object.entries(shape)) {
430
+ processedShape[key] = processSchema(value, [...path, key]);
431
+ }
432
+ const newSchema = z.object(processedShape);
433
+ const baseInputName = inputName.replace(/Input$/, '');
434
+ const nestedTypeName = `${baseInputName}_${path.join('_')}Input`;
435
+ const registered = newSchema.register(asObjectType, { name: nestedTypeName });
436
+ logger.trace(`Recovered broken object schema at ${path.join('.')}, registered as ${nestedTypeName}`);
437
+ return registered;
438
+ }
439
+ }
440
+ }
441
+
442
+ // Last resort - return z.any() to prevent crashes
443
+ logger.trace(`Could not recover schema at ${path.join('.')}, using z.any() as fallback`);
444
+ return z.any();
445
+ }
446
+
447
+ const typeName = schema._def?.typeName || schema._def?.type;
448
+
449
+ // Handle wrapper types (optional, nullable, default, etc.) - process inner type and re-wrap
450
+ if (typeName === 'ZodOptional' || typeName === 'optional') {
451
+ const inner = processSchema(schema._def.innerType, path);
452
+ return inner.optional();
453
+ }
454
+ if (typeName === 'ZodNullable' || typeName === 'nullable') {
455
+ const inner = processSchema(schema._def.innerType, path);
456
+ return inner.nullable();
457
+ }
458
+ if (typeName === 'ZodDefault' || typeName === 'default') {
459
+ const inner = processSchema(schema._def.innerType, path);
460
+ return inner.default(schema._def.defaultValue());
461
+ }
462
+ if (typeName === 'ZodEffects' || typeName === 'effects') {
463
+ // Effects (transform, refine, etc.) - process the inner schema
464
+ const inner = processSchema(schema._def.schema, path);
465
+ // Return as-is since we can't easily re-apply effects
466
+ return inner;
467
+ }
468
+ if (typeName === 'ZodArray' || typeName === 'array') {
469
+ // Arrays with complex nested objects can have broken _def.type
470
+ // In such cases, return the original schema and let GQLoom handle it
471
+ const elementSchema = schema._def.type;
472
+ if (!elementSchema || !elementSchema._def || Object.keys(elementSchema._def).length === 0) {
473
+ logger.trace(`Array at path ${path.join('.')} has broken element schema, returning original to let GQLoom handle it`);
474
+ return schema;
475
+ }
476
+
477
+ const elementTypeName = elementSchema._def.typeName || elementSchema._def.type;
478
+ logger.trace(`Array at path ${path.join('.')} (inputName: ${inputName}): element typeName = ${elementTypeName}`);
479
+
480
+ // For array elements, process and register nested objects
481
+ const arrayElementPath = path.length > 0 ? path : ['Item'];
482
+ const inner = processSchema(elementSchema, arrayElementPath);
483
+ logger.trace(`Processed array element at path ${path.join('.')}, inner type: ${inner._def?.typeName || inner._def?.type}, has _zod: ${!!(inner as any)._zod}`);
484
+ return z.array(inner);
485
+ }
486
+
487
+ // Handle unions containing scalar literals
488
+ if (typeName === 'ZodUnion' || typeName === 'union') {
489
+ const options = schema._def?.options || [];
490
+ let foundScalarName = null;
491
+
492
+ for (const option of options) {
493
+ const optionTypeName = option._def?.typeName || option._def?.type;
494
+ if (optionTypeName === 'ZodLiteral' || optionTypeName === 'literal') {
495
+ const value = option._def?.value ?? (option._def?.values ? option._def.values[0] : undefined);
496
+ if (typeof value === 'string' && scalarTypes.has(value)) {
497
+ foundScalarName = value;
498
+ break;
499
+ }
500
+ }
501
+ }
502
+
503
+ if (foundScalarName) {
504
+ scalarFieldNames.set(path.join('.') || path[path.length - 1] || 'root', foundScalarName);
505
+ return z.string();
506
+ }
507
+
508
+ // Process each option in the union recursively (for non-scalar unions)
509
+ const processedOptions = options.map((opt: any) => processSchema(opt, path));
510
+ // Can't easily recreate union, return original if no scalar found
511
+ return schema;
512
+ }
513
+
514
+ // Handle objects recursively - ALWAYS register nested objects with GQLoom
515
+ // because after .omit()/.extend() the schema tree loses _zod metadata
516
+ if (typeName === 'ZodObject' || typeName === 'object') {
517
+ // Only check for matching registered types on NESTED objects (path.length > 0)
518
+ // Root-level input types should always use their own generated name
519
+ // This prevents operations like reverseGeocoding({ latitude, longitude }) from
520
+ // incorrectly matching ST_PointInput
521
+ if (path.length > 0) {
522
+ const { findMatchingInputType } = require('../../core/ArcheType');
523
+ const matchingTypeName = findMatchingInputType(schema);
524
+
525
+ if (matchingTypeName) {
526
+ // Reuse the registered type - register with the existing type name
527
+ const registeredSchema = schema.register(asObjectType, { name: matchingTypeName });
528
+ logger.trace(`Matched nested object to registered type: ${matchingTypeName} at path ${path.join('.')}`);
529
+ return registeredSchema;
530
+ }
531
+ }
532
+
533
+ let shape;
534
+ try {
535
+ shape = typeof schema._def.shape === 'function' ? schema._def.shape() : schema._def.shape;
536
+ } catch (error) {
537
+ // Zod v4 can throw errors when accessing .shape() on schemas modified with .partial().extend()
538
+ // In this case, try alternative methods to access the shape
539
+ logger.warn(`Failed to access shape at path ${path.join('.')}: ${error instanceof Error ? error.message : String(error)}`);
540
+
541
+ // Try alternative: use the schema's shape property directly (without calling _def.shape)
542
+ try {
543
+ if (schema.shape && typeof schema.shape === 'object') {
544
+ shape = schema.shape;
545
+ } else if (schema._shape && typeof schema._shape === 'object') {
546
+ shape = schema._shape;
547
+ }
548
+ } catch (altError) {
549
+ logger.warn(`Alternative shape access also failed: ${altError instanceof Error ? altError.message : String(altError)}`);
550
+ }
551
+
552
+ // If all attempts failed, return the original schema and let GQLoom handle it
553
+ if (!shape) {
554
+ logger.trace(`Could not access shape, returning original schema at path ${path.join('.')}`);
555
+ return schema;
556
+ }
557
+ }
558
+
559
+ if (shape) {
560
+ const processedShape: any = {};
561
+ for (const [key, value] of Object.entries(shape)) {
562
+ const processed = processSchema(value, [...path, key]);
563
+ processedShape[key] = processed;
564
+ }
565
+ // Always create new object and register with GQLoom
566
+ // This ensures all nested objects have proper _zod metadata
567
+ const newSchema = z.object(processedShape);
568
+
569
+ // Check again after processing children - the processed schema might match a registered type
570
+ // Only for nested objects (path.length > 0), not root-level inputs
571
+ if (path.length > 0) {
572
+ const { findMatchingInputType } = require('../../core/ArcheType');
573
+ const processedMatchingTypeName = findMatchingInputType(newSchema);
574
+ if (processedMatchingTypeName) {
575
+ const registeredSchema = newSchema.register(asObjectType, { name: processedMatchingTypeName });
576
+ logger.trace(`Matched processed nested object to registered type: ${processedMatchingTypeName} at path ${path.join('.')}`);
577
+ return registeredSchema;
578
+ }
579
+ }
580
+
581
+ // Remove 'Input' suffix from inputName to avoid duplication when building nested type name
582
+ const baseInputName = inputName.replace(/Input$/, '');
583
+ const nestedTypeName = path.length > 0 ? `${baseInputName}_${path.join('_')}Input` : inputName;
584
+ const registeredSchema = newSchema.register(asObjectType, { name: nestedTypeName });
585
+ logger.trace(`Registered nested object ${nestedTypeName} for input ${inputName}, path: ${path.join('.')}, has _zod: ${!!(registeredSchema as any)._zod}`);
586
+ return registeredSchema;
587
+ }
588
+ }
589
+
590
+ return schema;
591
+ };
592
+
593
+ innerInput = processSchema(innerInput);
594
+
595
+ // Debug: log the processed schema structure for problematic inputs
596
+ if (inputName === 'uploadDriverDocumentInput' || inputName === 'getFaresInput') {
597
+ try {
598
+ const debugSchema = (s: any, indent: string = ''): string => {
599
+ if (!s) return 'null';
600
+ const def = s._def || {};
601
+ const typeName = def.typeName || def.type || 'unknown';
602
+ let result = `${typeName}`;
603
+ if (typeName === 'ZodObject' || typeName === 'object') {
604
+ const shape = typeof def.shape === 'function' ? def.shape() : def.shape;
605
+ if (shape) {
606
+ const entries = Object.entries(shape);
607
+ result += ` { ${entries.map(([k, v]) => `${k}: ${debugSchema(v, '')}`).join(', ')} }`;
608
+ }
609
+ } else if (typeName === 'ZodArray' || typeName === 'array') {
610
+ const elem = def.type;
611
+ result += `[${debugSchema(elem, '')}]`;
612
+ } else if (typeName === 'ZodOptional' || typeName === 'optional') {
613
+ result = `Optional<${debugSchema(def.innerType, '')}>`;
614
+ }
615
+ result += `(_zod:${!!(s as any)._zod})`;
616
+ return result;
617
+ };
618
+ logger.trace(`DEBUG getFaresInput structure: ${debugSchema(innerInput)}`);
619
+ } catch (e) {
620
+ logger.trace(`DEBUG getFaresInput error: ${e}`);
621
+ }
622
+ }
623
+
624
+ // DO NOT call .extend() or .register() after processSchema - they create new schema instances
625
+ // and can destroy the _zod metadata on nested objects that we just registered.
626
+ // The weave() function will handle the root level registration automatically.
627
+
628
+ const input = wasOptional ? innerInput.optional() : innerInput;
629
+
630
+ logger.trace(`Weaving Zod schema for ${inputName}`);
631
+ const gqlInputSchema = weave(ZodWeaver, input as ZodType) as GraphQLSchema;
632
+ const schemaString = printSchema(gqlInputSchema);
633
+ logger.trace(`Schema string for ${inputName}: ${schemaString}`);
634
+
635
+ // Collect custom type names
636
+ const typeNames: string[] = [];
637
+ schemaString.replace(/type (\w+)/g, (match, name) => {
638
+ typeNames.push(name);
639
+ return match;
640
+ });
641
+
642
+ // Convert all type definitions to input types with Input suffix for non-Input types
643
+ let inputTypeDefs = schemaString.replace(/\btype\b/g, 'input');
644
+ inputTypeDefs = inputTypeDefs.replace(/input (\w+)/g, (match, name) => {
645
+ if (name.endsWith('Input')) {
646
+ return `input ${name}`;
647
+ } else {
648
+ return `input ${name}Input`;
649
+ }
650
+ });
651
+
652
+ // Update field types for custom types
653
+ inputTypeDefs = inputTypeDefs.replace(/: (\[?)(\w+)([!\[\]]*)(\s|$)/g, (match, bracketStart, type, suffix, end) => {
654
+ if (typeNames.includes(type)) {
655
+ return `: ${bracketStart}${type.endsWith('Input') ? type : type + 'Input'}${suffix}${end}`;
656
+ } else {
657
+ return match;
658
+ }
659
+ });
660
+
661
+ // Replace String with scalar types for fields we tracked during preprocessing
662
+ for (const [fieldPath, scalarName] of scalarFieldNames.entries()) {
663
+ const fieldName = fieldPath.split('.').pop()!;
664
+ inputTypeDefs = inputTypeDefs.replace(
665
+ new RegExp(`(\\s+${fieldName}:\\s+)String(!?)`, 'g'),
666
+ `$1${scalarName}$2`
667
+ );
668
+ }
669
+
670
+ // Post-process to handle z.literal scalars
671
+ // Use the original input before __typename was added
672
+ let schemaToTraverse: any = originalInput;
673
+
674
+ // Unwrap optional if needed
675
+ const defType = (schemaToTraverse._def as any)?.typeName || (schemaToTraverse._def as any)?.type;
676
+ if (defType === 'ZodOptional' || defType === 'optional') {
677
+ schemaToTraverse = (schemaToTraverse as any)._def.innerType;
678
+ }
679
+
680
+ // Find all z.literal fields that match scalar names
681
+ const literalFields: Record<string, string> = {};
682
+ function traverseZod(obj: any, path: string[] = []) {
683
+ if (!obj || !obj._def) {
684
+ return;
685
+ }
686
+ const typeName = (obj._def as any).typeName || (obj._def as any).type;
687
+ if (typeName === 'ZodLiteral' || typeName === 'literal') {
688
+ // Zod v3 uses 'value', Zod v4 uses 'values' array
689
+ const defObj = obj._def as any;
690
+ const value = defObj.value ?? (defObj.values ? defObj.values[0] : undefined);
691
+ if (typeof value === 'string' && scalarTypes.has(value)) {
692
+ literalFields[path.join('.')] = value;
693
+ }
694
+ } else if (typeName === 'ZodUnion' || typeName === 'union') {
695
+ // Handle z.union - check if any option is a literal scalar
696
+ const options = (obj._def as any).options || [];
697
+ for (const option of options) {
698
+ const optionTypeName = (option._def as any)?.typeName || (option._def as any)?.type;
699
+ if (optionTypeName === 'ZodLiteral' || optionTypeName === 'literal') {
700
+ const defObj = option._def as any;
701
+ const value = defObj.value ?? (defObj.values ? defObj.values[0] : undefined);
702
+ if (typeof value === 'string' && scalarTypes.has(value)) {
703
+ literalFields[path.join('.')] = value;
704
+ break; // Found a scalar literal, use it
705
+ }
706
+ }
707
+ }
708
+ } else if (typeName === 'ZodObject' || typeName === 'object') {
709
+ const shape = typeof obj._def.shape === 'function' ? obj._def.shape() : obj._def.shape;
710
+ if (shape) {
711
+ for (const [key, value] of Object.entries(shape)) {
712
+ traverseZod(value, [...path, key]);
713
+ }
714
+ }
715
+ }
716
+ }
717
+ traverseZod(schemaToTraverse);
718
+
719
+ // Replace in typeDefs
720
+ for (const [fieldPath, scalarName] of Object.entries(literalFields)) {
721
+ const fieldName = fieldPath.split('.').pop()!;
722
+ inputTypeDefs = inputTypeDefs.replace(new RegExp(`(\\s+${fieldName}:\\s+)String!`, 'g'), `$1${scalarName}!`);
723
+ }
724
+
725
+ // Deduplicate input types - deduplicateInputTypes updates this.definedTypes and returns only new types
726
+ const deduplicatedInputTypeDefs = this.deduplicateInputTypes(inputTypeDefs);
727
+
728
+ logger.trace(`Successfully generated input types for ${inputName}`);
729
+ return deduplicatedInputTypeDefs;
730
+ } catch (error) {
731
+ logger.error(`Failed to weave Zod schema for ${inputName}: ${error}`);
732
+ logger.error(`Error stack: ${error instanceof Error ? error.stack : 'No stack'}`);
733
+ // Fallback: generate basic input type only if not already defined
734
+ if (!this.definedTypes.has(inputName)) {
735
+ this.definedTypes.add(inputName);
736
+ return `input ${inputName} { _placeholder: String }\n`;
737
+ }
738
+ return '';
739
+ }
740
+ }
741
+
742
+ /**
743
+ * Deduplicate input type definitions exactly like V1's deduplicateInputTypes
744
+ */
745
+ private deduplicateInputTypes(inputTypeDefs: string): string {
746
+ const lines = inputTypeDefs.split('\n');
747
+ const result: string[] = [];
748
+ let currentType = '';
749
+ let currentTypeName = '';
750
+ let inTypeDefinition = false;
751
+
752
+ for (const line of lines) {
753
+ const trimmed = line.trim();
754
+
755
+ // Check if this is the start of an input/enum definition
756
+ const typeMatch = trimmed.match(/^(input|enum)\s+(\w+)/);
757
+
758
+ if (typeMatch) {
759
+ // Save previous type if we were in one
760
+ if (inTypeDefinition && currentTypeName && !this.definedTypes.has(currentTypeName)) {
761
+ result.push(currentType);
762
+ this.definedTypes.add(currentTypeName);
763
+ logger.trace(`Added input type definition: ${currentTypeName}`);
764
+ } else if (inTypeDefinition && currentTypeName && this.definedTypes.has(currentTypeName)) {
765
+ logger.trace(`Skipped duplicate input type definition: ${currentTypeName}`);
766
+ }
767
+
768
+ // Start new type
769
+ currentTypeName = typeMatch[2] || '';
770
+ currentType = line + '\n';
771
+ inTypeDefinition = true;
772
+ } else if (inTypeDefinition) {
773
+ currentType += line + '\n';
774
+
775
+ // Check if this is the closing brace
776
+ if (trimmed === '}' || trimmed === '') {
777
+ // End of type definition (closing brace or empty line after enum)
778
+ if (trimmed === '}' && !this.definedTypes.has(currentTypeName)) {
779
+ result.push(currentType);
780
+ this.definedTypes.add(currentTypeName);
781
+ logger.trace(`Added input type definition: ${currentTypeName}`);
782
+ } else if (trimmed === '}' && this.definedTypes.has(currentTypeName)) {
783
+ logger.trace(`Skipped duplicate input type definition: ${currentTypeName}`);
784
+ }
785
+ currentType = '';
786
+ currentTypeName = '';
787
+ inTypeDefinition = false;
788
+ }
789
+ } else {
790
+ // Not in a type definition, just add the line (could be comments, etc.)
791
+ if (trimmed !== '') {
792
+ result.push(line + '\n');
793
+ }
794
+ }
795
+ }
796
+
797
+ // Handle last type if file doesn't end with closing brace
798
+ if (inTypeDefinition && currentTypeName && !this.definedTypes.has(currentTypeName)) {
799
+ result.push(currentType);
800
+ this.definedTypes.add(currentTypeName);
801
+ logger.trace(`Added input type definition: ${currentTypeName}`);
802
+ }
803
+
804
+ return result.join('');
805
+ }
806
+
807
+ /**
808
+ * Get the complete typeDefs string exactly like V1
809
+ */
810
+ getTypeDefs(): string {
811
+ // Only finalize once to avoid duplicating Query/Mutation/Subscription types
812
+ if (!this.finalized) {
813
+ // Add Query/Mutation/Subscription types
814
+ if (this.queryFields.length > 0) {
815
+ // Sort fields alphabetically (like V1)
816
+ this.queryFields.sort();
817
+ this.typeDefs += `type Query {\n${this.queryFields.map(f => ` ${f}`).join('\n')}\n}\n`;
818
+ } else {
819
+ // Always add at least an empty Query type for GraphQL schema validity
820
+ this.typeDefs += `type Query {\n _empty: String\n}\n`;
821
+ }
822
+
823
+ if (this.mutationFields.length > 0) {
824
+ // Sort fields alphabetically (like V1)
825
+ this.mutationFields.sort();
826
+ this.typeDefs += `type Mutation {\n${this.mutationFields.map(f => ` ${f}`).join('\n')}\n}\n`;
827
+ }
828
+
829
+ if (this.subscriptionFields.length > 0) {
830
+ // Sort fields alphabetically (like V1)
831
+ this.subscriptionFields.sort();
832
+ this.typeDefs += `type Subscription {\n${this.subscriptionFields.map(f => ` ${f}`).join('\n')}\n}\n`;
833
+ }
834
+
835
+ this.finalized = true;
836
+ }
837
+
838
+ return this.typeDefs;
839
+ }
840
+
841
+ /**
842
+ * Get results in the format expected by the orchestrator
843
+ */
844
+ getResults(): { typeDefs: string; inputTypes?: string; scalarTypes?: string[] } {
845
+ return {
846
+ typeDefs: this.getTypeDefs(),
847
+ inputTypes: this.inputTypes,
848
+ scalarTypes: Array.from(this.scalarTypes)
849
+ };
850
+ }
851
+ }