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,329 @@
1
+ import type { GraphQLType } from "../helpers";
2
+ import type { ZodType } from "zod";
3
+ import BaseArcheType from "../../core/ArcheType";
4
+ import { logger } from "../../core/Logger";
5
+
6
+ export interface TypeGenerationResult {
7
+ typeDef: string;
8
+ fieldType?: string;
9
+ resolverWrapper?: (resolver: Function) => Function;
10
+ inputTypeName?: string;
11
+ }
12
+
13
+ export interface TypeGenerationContext {
14
+ isInput: boolean;
15
+ operationName: string;
16
+ definedInputTypes: Set<string>;
17
+ scalarTypes: Set<string>;
18
+ }
19
+
20
+ export interface TypeGenerationStrategy {
21
+ canHandle(meta: any): boolean;
22
+ generateTypeDef(meta: any, context: TypeGenerationContext): TypeGenerationResult;
23
+ generateResolver?(originalResolver: Function, meta: any): Function;
24
+ }
25
+
26
+ /**
27
+ * Strategy for Zod schema-based input generation
28
+ */
29
+ export class ZodTypeStrategy implements TypeGenerationStrategy {
30
+ canHandle(meta: any): boolean {
31
+ return !!(meta && typeof meta === 'object' && '_def' in meta);
32
+ }
33
+
34
+ generateTypeDef(meta: ZodType, context: TypeGenerationContext): TypeGenerationResult {
35
+ // Extracted logic from existing generator for Zod inputs
36
+ const inputName = `${context.operationName}Input`;
37
+ let originalInput = meta;
38
+
39
+ // Handle optional schemas
40
+ let innerInput: any = meta;
41
+ const ZodOptional = (require('zod')).ZodOptional;
42
+ const wasOptional = meta instanceof ZodOptional;
43
+ if (wasOptional && 'unwrap' in (meta as any)) {
44
+ innerInput = (meta as any).unwrap();
45
+ }
46
+
47
+ // Preprocess schema for unions with scalar literals
48
+ const innerDef = (innerInput as any)._def;
49
+ const shape = innerDef && (typeof innerDef.shape === 'function' ? innerDef.shape() : innerDef.shape);
50
+ if (shape) {
51
+ const processedShape: any = {};
52
+ for (const [key, value] of Object.entries(shape)) {
53
+ const fieldSchema = value as any;
54
+ const typeName = fieldSchema._def?.typeName || fieldSchema._def?.type;
55
+
56
+ if (typeName === 'ZodUnion' || typeName === 'union') {
57
+ const options = fieldSchema._def?.options || [];
58
+ let foundScalarLiteral = null;
59
+
60
+ for (const option of options) {
61
+ const optionTypeName = option._def?.typeName || option._def?.type;
62
+ if (optionTypeName === 'ZodLiteral' || optionTypeName === 'literal') {
63
+ const value = option._def?.value ?? (option._def?.values ? option._def.values[0] : undefined);
64
+ if (typeof value === 'string' && context.scalarTypes.has(value)) {
65
+ foundScalarLiteral = option;
66
+ break;
67
+ }
68
+ }
69
+ }
70
+
71
+ processedShape[key] = foundScalarLiteral || fieldSchema;
72
+ } else {
73
+ processedShape[key] = fieldSchema;
74
+ }
75
+ }
76
+
77
+ innerInput = (require('zod')).object(processedShape);
78
+ }
79
+
80
+ // Add __typename field if extend is available
81
+ const z = require('zod');
82
+ if (innerInput && typeof (innerInput as any).extend === 'function') {
83
+ innerInput = (innerInput as any).extend({ __typename: z.literal(inputName).nullish() });
84
+ }
85
+ if (wasOptional) {
86
+ meta = innerInput.optional();
87
+ } else {
88
+ meta = innerInput;
89
+ }
90
+
91
+ const { weave, ZodWeaver } = require('@gqloom/core');
92
+ const { printSchema } = require('graphql');
93
+ const gqlInputSchema = weave(ZodWeaver, meta as ZodType) as any;
94
+ const schemaString = printSchema(gqlInputSchema);
95
+
96
+ const typeNames: string[] = [];
97
+ schemaString.replace(/type (\w+)/g, (_match: string, name: string) => {
98
+ typeNames.push(name);
99
+ return _match;
100
+ });
101
+
102
+ let inputTypeDefs = schemaString.replace(/\btype\b/g, 'input');
103
+ inputTypeDefs = inputTypeDefs.replace(/input (\w+)/g, (_match: string, name: string) => {
104
+ if (name.endsWith('Input')) {
105
+ return `input ${name}`;
106
+ } else {
107
+ return `input ${name}Input`;
108
+ }
109
+ });
110
+
111
+ inputTypeDefs = inputTypeDefs.replace(/: (\[?)(\w+)([!\[\]]*)(\s|$)/g, (_match: string, bracketStart: string, type: string, suffix: string, end: string) => {
112
+ if (typeNames.includes(type)) {
113
+ return `: ${bracketStart}${type.endsWith('Input') ? type : type + 'Input'}${suffix}${end}`;
114
+ } else {
115
+ return _match;
116
+ }
117
+ });
118
+
119
+ // Deduplicate
120
+ const deduplicatedInputTypeDefs = this.deduplicateInputTypes(inputTypeDefs, context.definedInputTypes);
121
+ let typeDef = deduplicatedInputTypeDefs + '\n';
122
+
123
+ // Post-process for literal scalars
124
+ let schemaToTraverse: any = originalInput;
125
+ const defType = (schemaToTraverse._def as any)?.typeName || (schemaToTraverse._def as any)?.type;
126
+ if (defType === 'ZodOptional' || defType === 'optional') {
127
+ schemaToTraverse = (schemaToTraverse as any)._def.innerType;
128
+ }
129
+
130
+ const literalFields: Record<string, string> = {};
131
+ this.traverseZod(schemaToTraverse, literalFields, context.scalarTypes);
132
+
133
+ for (const [fieldPath, scalarName] of Object.entries(literalFields)) {
134
+ const fieldName = fieldPath.split('.').pop()!;
135
+ typeDef = typeDef.replace(new RegExp(`(\\s+${fieldName}:\\s+)String!`, 'g'), `$1${scalarName}!`);
136
+ }
137
+
138
+ // Determine nullability based on whether the input was optional
139
+ const inputNullability = wasOptional ? '' : '!';
140
+
141
+ return {
142
+ typeDef,
143
+ fieldType: `(input: ${inputName}${inputNullability})`,
144
+ inputTypeName: inputName,
145
+ resolverWrapper: (resolver) => this.wrapResolverWithZodValidation(resolver, originalInput)
146
+ };
147
+ }
148
+
149
+ private deduplicateInputTypes(inputTypeDefs: string, definedTypes: Set<string>): string {
150
+ const lines = inputTypeDefs.split('\n');
151
+ const result: string[] = [];
152
+ let currentType = '';
153
+ let currentTypeName = '';
154
+ let inTypeDefinition = false;
155
+
156
+ for (const line of lines) {
157
+ if (line.startsWith('input ')) {
158
+ currentTypeName = line.split(' ')[1] ?? '';
159
+ if (definedTypes.has(currentTypeName)) {
160
+ inTypeDefinition = false;
161
+ currentType = '';
162
+ continue;
163
+ } else {
164
+ definedTypes.add(currentTypeName);
165
+ inTypeDefinition = true;
166
+ currentType = line + '\n';
167
+ }
168
+ } else if (inTypeDefinition) {
169
+ currentType += line + '\n';
170
+ if (line.trim() === '}') {
171
+ result.push(currentType);
172
+ inTypeDefinition = false;
173
+ currentType = '';
174
+ }
175
+ } else if (!inTypeDefinition && line.trim()) {
176
+ result.push(line + '\n');
177
+ }
178
+ }
179
+
180
+ return result.join('');
181
+ }
182
+
183
+ private traverseZod(obj: any, literalFields: Record<string, string>, scalarTypes: Set<string>, path: string[] = []) {
184
+ if (!obj || !obj._def) return;
185
+ const typeName = (obj._def as any).typeName || (obj._def as any).type;
186
+ if (typeName === 'ZodLiteral' || typeName === 'literal') {
187
+ const defObj = obj._def as any;
188
+ const value = defObj.value ?? (defObj.values ? defObj.values[0] : undefined);
189
+ if (typeof value === 'string' && scalarTypes.has(value)) {
190
+ literalFields[path.join('.')] = value;
191
+ }
192
+ } else if (typeName === 'ZodUnion' || typeName === 'union') {
193
+ const options = (obj._def as any).options || [];
194
+ for (const option of options) {
195
+ const optionTypeName = (option._def as any)?.typeName || (option._def as any)?.type;
196
+ if (optionTypeName === 'ZodLiteral' || optionTypeName === 'literal') {
197
+ const defObj = option._def as any;
198
+ const value = defObj.value ?? (defObj.values ? defObj.values[0] : undefined);
199
+ if (typeof value === 'string' && scalarTypes.has(value)) {
200
+ literalFields[path.join('.')] = value;
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ } else if (typeName === 'ZodObject' || typeName === 'object') {
206
+ const shape = typeof obj._def.shape === 'function' ? obj._def.shape() : obj._def.shape;
207
+ if (shape) {
208
+ for (const [key, value] of Object.entries(shape)) {
209
+ this.traverseZod(value, literalFields, scalarTypes, [...path, key]);
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ private wrapResolverWithZodValidation(resolver: Function, zodSchema: ZodType): Function {
216
+ return async (_: any, args: any, context: any, info: any) => {
217
+ try {
218
+ const inputArgs = args.input || args;
219
+ const validated = zodSchema.parse(inputArgs);
220
+ return await resolver(validated, context, info);
221
+ } catch (error) {
222
+ if (error instanceof (require('zod')).ZodError) {
223
+ const { handleGraphQLError } = await import("../../core/ErrorHandler");
224
+ handleGraphQLError(error);
225
+ }
226
+ throw error;
227
+ }
228
+ };
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Strategy for archetype output types
234
+ */
235
+ export class ArchetypeTypeStrategy implements TypeGenerationStrategy {
236
+ canHandle(meta: any): boolean {
237
+ return meta instanceof BaseArcheType || (Array.isArray(meta) && meta[0] instanceof BaseArcheType);
238
+ }
239
+
240
+ generateTypeDef(meta: BaseArcheType | BaseArcheType[], context: TypeGenerationContext): TypeGenerationResult {
241
+ const { getArchetypeTypeName } = require("../../core/ArcheType");
242
+
243
+ if (Array.isArray(meta)) {
244
+ const archetypeInstance = meta[0];
245
+ const typeName = getArchetypeTypeName(archetypeInstance);
246
+ if (typeName) {
247
+ return { typeDef: '', fieldType: `: [${typeName}]` };
248
+ } else {
249
+ logger.warn(`Invalid array output type for ${context.operationName}, expected archetype instance`);
250
+ return { typeDef: '', fieldType: `: [Any]` };
251
+ }
252
+ } else {
253
+ const typeName = getArchetypeTypeName(meta);
254
+ if (typeName) {
255
+ return { typeDef: '', fieldType: `: ${typeName}` };
256
+ } else {
257
+ logger.warn(`Could not determine type name for archetype in ${context.operationName}`);
258
+ return { typeDef: '', fieldType: `: Any` };
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Strategy for legacy Record<string, GraphQLType> format
266
+ */
267
+ export class LegacyTypeStrategy implements TypeGenerationStrategy {
268
+ canHandle(meta: any): boolean {
269
+ return meta && typeof meta === 'object' && !('_def' in meta) && !this.isArchetype(meta);
270
+ }
271
+
272
+ private isArchetype(meta: any): boolean {
273
+ return meta instanceof BaseArcheType || (Array.isArray(meta) && meta[0] instanceof BaseArcheType);
274
+ }
275
+
276
+ generateTypeDef(meta: Record<string, GraphQLType>, context: TypeGenerationContext): TypeGenerationResult {
277
+ if (context.isInput) {
278
+ // Input type
279
+ const inputName = `${context.operationName}Input`;
280
+ const typeDef = `input ${inputName} {\n${Object.entries(meta).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
281
+ return { typeDef, fieldType: `(input: ${inputName}!)`, inputTypeName: inputName };
282
+ } else {
283
+ // Output type
284
+ const outputName = `${context.operationName}Output`;
285
+ const typeDef = `type ${outputName} {\n${Object.entries(meta).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
286
+ return { typeDef, fieldType: `: ${outputName}` };
287
+ }
288
+ }
289
+
290
+ private isInputLike(meta: Record<string, GraphQLType>): boolean {
291
+ // Heuristic: if operationName ends with 'Input' or has input-like fields, treat as input
292
+ // For now, assume if it's used as input in context, but since we don't have context, check field names
293
+ return true; // Default to input for legacy, adjust based on usage
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Strategy for primitive string-based types
299
+ */
300
+ export class PrimitiveTypeStrategy implements TypeGenerationStrategy {
301
+ canHandle(meta: any): boolean {
302
+ return typeof meta === 'string';
303
+ }
304
+
305
+ generateTypeDef(meta: string, context: TypeGenerationContext): TypeGenerationResult {
306
+ return { typeDef: '', fieldType: `: ${meta}` };
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Factory to select the appropriate strategy
312
+ */
313
+ export class TypeGenerationStrategyFactory {
314
+ private static strategies: TypeGenerationStrategy[] = [
315
+ new ZodTypeStrategy(),
316
+ new ArchetypeTypeStrategy(),
317
+ new LegacyTypeStrategy(),
318
+ new PrimitiveTypeStrategy()
319
+ ];
320
+
321
+ static getStrategy(meta: any): TypeGenerationStrategy {
322
+ for (const strategy of this.strategies) {
323
+ if (strategy.canHandle(meta)) {
324
+ return strategy;
325
+ }
326
+ }
327
+ throw new Error(`No strategy found for meta: ${JSON.stringify(meta)}`);
328
+ }
329
+ }
package/gql/types.ts CHANGED
@@ -24,6 +24,7 @@ export interface GraphQLField {
24
24
 
25
25
 
26
26
  // TODO: Remove this when we have a better way to define GraphQL type
27
+ // Current usage is for custom input types in Operation decorators
27
28
  export enum GraphQLFieldTypes {
28
29
  ID = "ID",
29
30
  ID_REQUIRED = "ID!",
@@ -0,0 +1,220 @@
1
+ import type { ZodType } from "zod";
2
+
3
+ /**
4
+ * Generate a structural signature for a Zod schema to enable type matching.
5
+ * The signature is based on field names and their types, sorted alphabetically.
6
+ * This allows us to detect when two schemas are structurally equivalent,
7
+ * even if they were created through different transformations (.omit(), .extend(), etc.)
8
+ */
9
+ export function generateZodStructuralSignature(schema: ZodType | any): string {
10
+ if (!schema || !schema._def) return 'unknown';
11
+
12
+ const typeName = schema._def.typeName || schema._def.type;
13
+
14
+ // Handle object types - the main case for input type matching
15
+ if (typeName === 'ZodObject' || typeName === 'object') {
16
+ let shape: Record<string, any> | undefined;
17
+
18
+ try {
19
+ shape = typeof schema._def.shape === 'function'
20
+ ? schema._def.shape()
21
+ : schema._def.shape;
22
+ } catch {
23
+ // If shape access fails, try alternative methods
24
+ if (schema.shape && typeof schema.shape === 'object') {
25
+ shape = schema.shape;
26
+ }
27
+ }
28
+
29
+ if (!shape || Object.keys(shape).length === 0) {
30
+ return 'object:{}';
31
+ }
32
+
33
+ // Sort keys for consistent hashing
34
+ const fieldSignatures = Object.entries(shape)
35
+ .filter(([key]) => key !== '__typename') // Exclude __typename from signature
36
+ .sort(([a], [b]) => a.localeCompare(b))
37
+ .map(([key, value]) => `${key}:${generateZodStructuralSignature(value as ZodType)}`)
38
+ .join(',');
39
+
40
+ return `object:{${fieldSignatures}}`;
41
+ }
42
+
43
+ // Primitive types
44
+ if (typeName === 'ZodNumber' || typeName === 'number') return 'number';
45
+ if (typeName === 'ZodString' || typeName === 'string') return 'string';
46
+ if (typeName === 'ZodBoolean' || typeName === 'boolean') return 'boolean';
47
+ if (typeName === 'ZodDate' || typeName === 'date') return 'date';
48
+ if (typeName === 'ZodAny' || typeName === 'any') return 'any';
49
+ if (typeName === 'ZodUnknown' || typeName === 'unknown') return 'unknown';
50
+ if (typeName === 'ZodVoid' || typeName === 'void') return 'void';
51
+ if (typeName === 'ZodNull' || typeName === 'null') return 'null';
52
+ if (typeName === 'ZodUndefined' || typeName === 'undefined') return 'undefined';
53
+ if (typeName === 'ZodNaN' || typeName === 'nan') return 'nan';
54
+ if (typeName === 'ZodBigInt' || typeName === 'bigint') return 'bigint';
55
+ if (typeName === 'ZodSymbol' || typeName === 'symbol') return 'symbol';
56
+
57
+ // Wrapper types - unwrap and include wrapper info
58
+ if (typeName === 'ZodOptional' || typeName === 'optional') {
59
+ const inner = generateZodStructuralSignature(schema._def.innerType);
60
+ return `optional:${inner}`;
61
+ }
62
+
63
+ if (typeName === 'ZodNullable' || typeName === 'nullable') {
64
+ const inner = generateZodStructuralSignature(schema._def.innerType);
65
+ return `nullable:${inner}`;
66
+ }
67
+
68
+ if (typeName === 'ZodDefault' || typeName === 'default') {
69
+ const inner = generateZodStructuralSignature(schema._def.innerType);
70
+ return `default:${inner}`;
71
+ }
72
+
73
+ if (typeName === 'ZodEffects' || typeName === 'effects') {
74
+ const inner = generateZodStructuralSignature(schema._def.schema);
75
+ return `effects:${inner}`;
76
+ }
77
+
78
+ if (typeName === 'ZodReadonly' || typeName === 'readonly') {
79
+ const inner = generateZodStructuralSignature(schema._def.innerType);
80
+ return `readonly:${inner}`;
81
+ }
82
+
83
+ if (typeName === 'ZodBranded' || typeName === 'branded') {
84
+ const inner = generateZodStructuralSignature(schema._def.type);
85
+ return `branded:${inner}`;
86
+ }
87
+
88
+ if (typeName === 'ZodCatch' || typeName === 'catch') {
89
+ const inner = generateZodStructuralSignature(schema._def.innerType);
90
+ return `catch:${inner}`;
91
+ }
92
+
93
+ if (typeName === 'ZodPipeline' || typeName === 'pipeline') {
94
+ const inner = generateZodStructuralSignature(schema._def.in);
95
+ const outer = generateZodStructuralSignature(schema._def.out);
96
+ return `pipeline:${inner}->${outer}`;
97
+ }
98
+
99
+ // Array type
100
+ if (typeName === 'ZodArray' || typeName === 'array') {
101
+ const elementType = schema._def.type;
102
+ const elementSignature = generateZodStructuralSignature(elementType);
103
+ return `array:${elementSignature}`;
104
+ }
105
+
106
+ // Tuple type
107
+ if (typeName === 'ZodTuple' || typeName === 'tuple') {
108
+ const items = schema._def.items || [];
109
+ const itemSignatures = items.map((item: ZodType) => generateZodStructuralSignature(item)).join(',');
110
+ const rest = schema._def.rest ? `,rest:${generateZodStructuralSignature(schema._def.rest)}` : '';
111
+ return `tuple:[${itemSignatures}${rest}]`;
112
+ }
113
+
114
+ // Union type - sort options for consistent signature
115
+ if (typeName === 'ZodUnion' || typeName === 'union') {
116
+ const options = schema._def.options || [];
117
+ const optionSignatures = options
118
+ .map((opt: ZodType) => generateZodStructuralSignature(opt))
119
+ .sort()
120
+ .join('|');
121
+ return `union:(${optionSignatures})`;
122
+ }
123
+
124
+ // Discriminated union - include discriminator key
125
+ if (typeName === 'ZodDiscriminatedUnion' || typeName === 'discriminatedUnion') {
126
+ const discriminator = schema._def.discriminator;
127
+ const options = schema._def.options || [];
128
+ const optionSignatures = options
129
+ .map((opt: ZodType) => generateZodStructuralSignature(opt))
130
+ .sort()
131
+ .join('|');
132
+ return `discUnion:${discriminator}:(${optionSignatures})`;
133
+ }
134
+
135
+ // Intersection type
136
+ if (typeName === 'ZodIntersection' || typeName === 'intersection') {
137
+ const left = generateZodStructuralSignature(schema._def.left);
138
+ const right = generateZodStructuralSignature(schema._def.right);
139
+ return `intersection:(${left}&${right})`;
140
+ }
141
+
142
+ // Literal type - include the literal value
143
+ if (typeName === 'ZodLiteral' || typeName === 'literal') {
144
+ const value = schema._def.value ?? (schema._def.values ? schema._def.values[0] : undefined);
145
+ return `literal:${JSON.stringify(value)}`;
146
+ }
147
+
148
+ // Enum type
149
+ if (typeName === 'ZodEnum' || typeName === 'enum') {
150
+ const values = schema._def.values || [];
151
+ return `enum:[${values.sort().join(',')}]`;
152
+ }
153
+
154
+ // Native enum type
155
+ if (typeName === 'ZodNativeEnum' || typeName === 'nativeEnum') {
156
+ const values = Object.values(schema._def.values || {}).sort();
157
+ return `nativeEnum:[${values.join(',')}]`;
158
+ }
159
+
160
+ // Record type
161
+ if (typeName === 'ZodRecord' || typeName === 'record') {
162
+ const keyType = generateZodStructuralSignature(schema._def.keyType);
163
+ const valueType = generateZodStructuralSignature(schema._def.valueType);
164
+ return `record:<${keyType},${valueType}>`;
165
+ }
166
+
167
+ // Map type
168
+ if (typeName === 'ZodMap' || typeName === 'map') {
169
+ const keyType = generateZodStructuralSignature(schema._def.keyType);
170
+ const valueType = generateZodStructuralSignature(schema._def.valueType);
171
+ return `map:<${keyType},${valueType}>`;
172
+ }
173
+
174
+ // Set type
175
+ if (typeName === 'ZodSet' || typeName === 'set') {
176
+ const valueType = generateZodStructuralSignature(schema._def.valueType);
177
+ return `set:<${valueType}>`;
178
+ }
179
+
180
+ // Promise type
181
+ if (typeName === 'ZodPromise' || typeName === 'promise') {
182
+ const inner = generateZodStructuralSignature(schema._def.type);
183
+ return `promise:${inner}`;
184
+ }
185
+
186
+ // Function type
187
+ if (typeName === 'ZodFunction' || typeName === 'function') {
188
+ const args = generateZodStructuralSignature(schema._def.args);
189
+ const returns = generateZodStructuralSignature(schema._def.returns);
190
+ return `function:(${args})=>${returns}`;
191
+ }
192
+
193
+ // Lazy type - be careful with infinite recursion
194
+ if (typeName === 'ZodLazy' || typeName === 'lazy') {
195
+ // For lazy types, we can't fully resolve without risking infinite recursion
196
+ // Use a placeholder that indicates it's a lazy type
197
+ return 'lazy:deferred';
198
+ }
199
+
200
+ // Fallback for unknown types
201
+ return typeName || 'unknown';
202
+ }
203
+
204
+ /**
205
+ * Normalize a signature for comparison.
206
+ * Currently just returns the signature as-is, but can be extended
207
+ * to handle canonicalization if needed.
208
+ */
209
+ export function normalizeSignature(signature: string): string {
210
+ return signature;
211
+ }
212
+
213
+ /**
214
+ * Compare two schemas for structural equivalence.
215
+ */
216
+ export function areStructurallyEquivalent(schema1: ZodType, schema2: ZodType): boolean {
217
+ const sig1 = normalizeSignature(generateZodStructuralSignature(schema1));
218
+ const sig2 = normalizeSignature(generateZodStructuralSignature(schema2));
219
+ return sig1 === sig2;
220
+ }
@@ -0,0 +1 @@
1
+ export { generateZodStructuralSignature, normalizeSignature, areStructurallyEquivalent } from './TypeSignature';
@@ -0,0 +1,80 @@
1
+ import { GraphVisitor } from "./GraphVisitor";
2
+ import { TypeNode, OperationNode, FieldNode, InputNode, ScalarNode } from "../graph/GraphNode";
3
+ import { weaveAllArchetypes, getAllArchetypeSchemas } from "../../core/ArcheType";
4
+
5
+ /**
6
+ * Visitor that pre-processes archetype schemas before other visitors run.
7
+ * Ensures all archetype GraphQL schemas are generated and cached.
8
+ */
9
+ export class ArchetypePreprocessorVisitor extends GraphVisitor {
10
+ private processedArchetypes: Set<string> = new Set();
11
+ private archetypeSchemas: any[] = [];
12
+
13
+ override beforeVisit(): void {
14
+ // Pre-weave all archetypes to ensure schemas are generated
15
+ try {
16
+ const schema = weaveAllArchetypes();
17
+ if (schema) {
18
+ this.archetypeSchemas = getAllArchetypeSchemas();
19
+ }
20
+ } catch (error) {
21
+ console.warn("Failed to pre-process archetype schemas:", error);
22
+ }
23
+ }
24
+
25
+ visitTypeNode(node: TypeNode): void {
26
+ // Check if this is an archetype type
27
+ if (node.metadata.isArchetype) {
28
+ this.processedArchetypes.add(node.name);
29
+ }
30
+ }
31
+
32
+ visitOperationNode(node: OperationNode): void {
33
+ // Operations may reference archetype types
34
+ // We don't need to do anything special here
35
+ }
36
+
37
+ visitFieldNode(node: FieldNode): void {
38
+ // Fields may be part of archetype types
39
+ }
40
+
41
+ visitInputNode(node: InputNode): void {
42
+ // Input nodes don't need archetype preprocessing
43
+ }
44
+
45
+ visitScalarNode(node: ScalarNode): void {
46
+ // Scalar nodes don't need archetype preprocessing
47
+ }
48
+
49
+ getResults(): {
50
+ processedArchetypes: string[];
51
+ archetypeSchemas: any[];
52
+ } {
53
+ return {
54
+ processedArchetypes: Array.from(this.processedArchetypes),
55
+ archetypeSchemas: [...this.archetypeSchemas]
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Check if an archetype has been processed
61
+ */
62
+ isArchetypeProcessed(archetypeName: string): boolean {
63
+ return this.processedArchetypes.has(archetypeName);
64
+ }
65
+
66
+ /**
67
+ * Get all processed archetype names
68
+ */
69
+ getProcessedArchetypes(): string[] {
70
+ return Array.from(this.processedArchetypes);
71
+ }
72
+
73
+ /**
74
+ * Clear all processed data
75
+ */
76
+ clear(): void {
77
+ this.processedArchetypes.clear();
78
+ this.archetypeSchemas = [];
79
+ }
80
+ }