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
package/gql/Generator.ts CHANGED
@@ -1,18 +1,11 @@
1
- import { GraphQLSchema, GraphQLError, printSchema } from "graphql";
2
- import { createSchema } from "graphql-yoga";
3
- import { logger as MainLogger } from "core/Logger";
1
+ import { logger as MainLogger } from "../core/Logger";
4
2
  import type { GraphQLType } from "./helpers";
5
- import { generateArchetypeOperations } from "./ArchetypeOperations";
6
- import { getArchetypeSchema } from "../core/ArcheType";
3
+ import type { SchemaType } from "./schema";
7
4
  import BaseArcheType from "../core/ArcheType";
8
- import { getMetadataStorage } from "../core/metadata";
9
- import type { BaseService } from "service";
10
- import { type ZodType } from "zod";
11
- import { ZodWeaver } from "@gqloom/zod";
12
- import { weave } from "@gqloom/core";
13
- import * as z from "zod";
5
+ import type { BaseService } from "../service";
14
6
 
15
7
  const logger = MainLogger.child({ scope: "GraphQLGenerator" });
8
+
16
9
  export interface GraphQLObjectTypeMeta {
17
10
  name: string;
18
11
  fields: Record<string, GraphQLType>;
@@ -22,7 +15,14 @@ export interface GraphQLOperationMeta<T extends BaseArcheType | BaseArcheType[]
22
15
  type: "Query" | "Mutation";
23
16
  propertyKey?: string;
24
17
  name?: string;
25
- input?: Record<string, GraphQLType> | any;
18
+ input?: Record<string, SchemaType> | Record<string, GraphQLType> | any;
19
+ output: GraphQLType | Record<string, GraphQLType> | T;
20
+ }
21
+
22
+ export interface GraphQLSubscriptionMeta<T extends BaseArcheType | BaseArcheType[] | string = string> {
23
+ propertyKey?: string;
24
+ name?: string;
25
+ input?: Record<string, SchemaType> | Record<string, GraphQLType> | any;
26
26
  output: GraphQLType | Record<string, GraphQLType> | T;
27
27
  }
28
28
 
@@ -58,6 +58,11 @@ export function GraphQLOperation<T extends BaseArcheType | BaseArcheType[] | str
58
58
  };
59
59
  }
60
60
 
61
+ /**
62
+ * @deprecated Use ArcheTypeFunction instead
63
+ * @param meta
64
+ * @returns
65
+ */
61
66
  export function GraphQLField(meta: GraphQLFieldMeta) {
62
67
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
63
68
  if (!target.__graphqlFields) target.__graphqlFields = [];
@@ -65,308 +70,15 @@ export function GraphQLField(meta: GraphQLFieldMeta) {
65
70
  };
66
71
  }
67
72
 
68
- /**
69
- * Helper function to get the registered GraphQL type name from an archetype instance.
70
- * This respects the custom name set via @ArcheType("CustomName") decorator.
71
- * Falls back to inferring from class name if not found in registry.
72
- */
73
- function getArchetypeTypeName(archetypeInstance: any): string | null {
74
- if (!archetypeInstance || !(archetypeInstance instanceof BaseArcheType)) {
75
- return null;
76
- }
77
-
78
- const storage = getMetadataStorage();
79
- const className = archetypeInstance.constructor.name;
80
-
81
- // Look up the archetype metadata by class name to get the custom name
82
- const archetypeMetadata = storage.archetypes.find(a => a.target?.name === className);
83
-
84
- if (archetypeMetadata?.name) {
85
- // Use the custom name from @ArcheType("CustomName") decorator
86
- logger.trace(`Found custom archetype name: ${archetypeMetadata.name} for class ${className}`);
87
-
88
- // Ensure schema is generated and cached
89
- try {
90
- if (!getArchetypeSchema(archetypeMetadata.name)) {
91
- archetypeInstance.getZodObjectSchema();
92
- }
93
- } catch (error) {
94
- logger.warn(`Failed to generate schema for archetype ${archetypeMetadata.name}: ${error}`);
95
- }
96
-
97
- return archetypeMetadata.name;
98
- }
99
-
100
- // Fallback: infer from class name
101
- const inferredName = className.replace(/ArcheType$/, '');
102
- logger.trace(`Using inferred archetype name: ${inferredName} for class ${className}`);
103
-
104
- try {
105
- if (!getArchetypeSchema(inferredName)) {
106
- archetypeInstance.getZodObjectSchema();
107
- }
108
- } catch (error) {
109
- logger.warn(`Failed to generate schema for archetype ${inferredName}: ${error}`);
110
- }
111
-
112
- return inferredName;
113
- }
114
-
115
- export function generateGraphQLSchema(services: any[], options?: { enableArchetypeOperations?: boolean }): { schema: GraphQLSchema | null; resolvers: any } {
116
- logger.trace(`generateGraphQLSchema called with ${services.length} services`);
117
- let typeDefs = `
118
- `;
119
- const scalarTypes: Set<string> = new Set();
120
- const resolvers: any = {};
121
- const queryFields: string[] = [];
122
- const mutationFields: string[] = [];
123
-
124
- // PRE-GENERATE ALL ARCHETYPE SCHEMAS
125
- // Scan all services for archetype instances and generate their schemas upfront
126
- logger.trace(`Pre-generating archetype schemas from service operations...`);
127
- services.forEach(service => {
128
- const operations = service.__graphqlOperations || service.constructor.prototype.__graphqlOperations;
129
- if (operations) {
130
- operations.forEach((op: any) => {
131
- const { output } = op;
132
- // Check if output is an archetype or array of archetypes
133
- if (Array.isArray(output) && output[0] instanceof BaseArcheType) {
134
- const archetypeInstance = output[0];
135
- getArchetypeTypeName(archetypeInstance); // This will cache the schema
136
- } else if (output instanceof BaseArcheType) {
137
- getArchetypeTypeName(output); // This will cache the schema
138
- }
139
- });
140
- }
141
- });
142
- logger.trace(`Completed pre-generation of archetype schemas`);
143
-
144
- // Generate archetype operations if enabled
145
- if (options?.enableArchetypeOperations !== false) {
146
- try {
147
- const archetypeOps = generateArchetypeOperations();
148
- typeDefs += archetypeOps.typeDefs;
149
- queryFields.push(...archetypeOps.queryFields);
150
- mutationFields.push(...archetypeOps.mutationFields);
151
- Object.assign(resolvers, archetypeOps.resolvers);
152
- logger.trace(`Added archetype operations: ${archetypeOps.queryFields.length} queries, ${archetypeOps.mutationFields.length} mutations`);
153
- } catch (error) {
154
- logger.error(`Failed to generate archetype operations: ${error}`);
155
- }
156
- } else {
157
- // Still generate type definitions even if operations are disabled
158
- try {
159
- const archetypeOps = generateArchetypeOperations();
160
- typeDefs += archetypeOps.typeDefs;
161
- logger.trace(`Added archetype type definitions (without operations)`);
162
- } catch (error) {
163
- logger.error(`Failed to generate archetype type definitions: ${error}`);
164
- }
165
- }
166
-
167
- services.forEach(service => {
168
- logger.trace(`Processing service: ${service.constructor.name}`);
169
- // Check if service has graphql operations (either on instance or prototype)
170
- const operations = service.__graphqlOperations || service.constructor.prototype.__graphqlOperations;
171
- if(service.constructor.__graphqlScalarTypes) {
172
- for (const scalarName of service.constructor.__graphqlScalarTypes) {
173
- if (!scalarTypes.has(scalarName)) {
174
- scalarTypes.add(scalarName);
175
- typeDefs += `scalar ${scalarName}\n`;
176
- }
177
- }
178
- }
179
- if (service.constructor.__graphqlObjectType) {
180
- for (const meta of service.constructor.__graphqlObjectType) {
181
- const { name, fields } = meta;
182
- typeDefs += `type ${name} {\n${Object.entries(fields).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
183
- }
184
- }
185
- if (operations) {
186
- logger.trace(`Processing ${operations.length} operations for ${service.constructor.name}`);
187
- operations.forEach((op: any) => {
188
- try {
189
- let { type, name, input, output, propertyKey } = op;
190
- if (!resolvers[type]) resolvers[type] = {};
191
- let fieldDef = `${name}`;
192
- if (input) {
193
- const inputName = `${name}Input`;
194
- // Check if input is a Zod schema
195
- if (input && typeof input === 'object' && '_def' in input) {
196
- // It's a Zod schema - use GQLoom's weave to generate GraphQL type
197
- try {
198
- // Add __typename to input zod object
199
- input = input.extend({ __typename: z.literal(inputName).nullish() });
200
- logger.trace(`Weaving Zod schema for ${name}`);
201
- const gqlInputSchema = weave(ZodWeaver, input as ZodType) as GraphQLSchema;
202
- const schemaString = printSchema(gqlInputSchema);
203
- logger.trace(`Schema string for ${name}: ${schemaString}`);
204
- // Extract the type definition and convert it to an input type
205
- // The schema will contain "type <TypeName> { ... }", we need to replace with "input <inputName> { ... }"
206
- const typeMatch = schemaString.match(/type\s+(\w+)\s*\{([^}]*)\}/s);
207
- if (typeMatch) {
208
- const fields = typeMatch[2];
209
- typeDefs += `input ${inputName} {${fields}}\n`;
210
- logger.trace(`Successfully generated input type ${inputName}`);
211
- } else {
212
- logger.warn(`Could not extract type from Zod schema for ${name}, schema: ${schemaString}`);
213
- typeDefs += `input ${inputName} { _placeholder: String }\n`;
214
- }
215
- } catch (error) {
216
- logger.error(`Failed to weave Zod schema for ${name}: ${error}`);
217
- logger.error(`Error stack: ${error instanceof Error ? error.stack : 'No stack'}`);
218
- // Fallback: generate basic input type
219
- typeDefs += `input ${inputName} { _placeholder: String }\n`;
220
- }
221
- } else {
222
- // Legacy Record<string, GraphQLType> format
223
- typeDefs += `input ${inputName} {\n${Object.entries(input).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
224
- }
225
- fieldDef += `(input: ${inputName}!)`;
226
-
227
- // Store the Zod schema for validation if it's a Zod type
228
- const zodSchema = (input && typeof input === 'object' && '_def' in input) ? input as ZodType : null;
229
-
230
- resolvers[type][name] = async (_: any, args: any, context: any, info: any) => {
231
- try {
232
- const inputArgs = args.input || args;
233
-
234
- // Automatically validate with Zod schema if provided
235
- if (zodSchema) {
236
- try {
237
- const validated = zodSchema.parse(inputArgs);
238
- return await service[propertyKey](validated, context, info);
239
- } catch (error) {
240
- if (error instanceof z.ZodError) {
241
- // Let handleGraphQLError convert Zod errors to user-friendly messages
242
- const { handleGraphQLError } = await import("../core/ErrorHandler");
243
- handleGraphQLError(error);
244
- }
245
- throw error;
246
- }
247
- } else {
248
- return await service[propertyKey](inputArgs, context, info);
249
- }
250
- } catch (error) {
251
- logger.error(`Error in ${type}.${name}:`);
252
- logger.error(error);
253
- if (error instanceof GraphQLError) {
254
- throw error;
255
- }
256
- throw new GraphQLError(`Internal error in ${name}`, {
257
- extensions: {
258
- code: "INTERNAL_ERROR",
259
- originalError: process.env.NODE_ENV === 'development' ? error : undefined
260
- }
261
- });
262
- }
263
- };
264
- } else {
265
- resolvers[type][name] = async (_: any, args: any, context: any, info: any) => {
266
- try {
267
- return await service[propertyKey]({}, context, info);
268
- } catch (error) {
269
- logger.error(`Error in ${type}.${name}:`);
270
- logger.error(error);
271
- if (error instanceof GraphQLError) {
272
- throw error;
273
- }
274
- throw new GraphQLError(`Internal error in ${name}`, {
275
- extensions: {
276
- code: "INTERNAL_ERROR",
277
- originalError: process.env.NODE_ENV === 'development' ? error : undefined
278
- }
279
- });
280
- }
281
- };
282
- }
283
- if (typeof output === 'string') {
284
- fieldDef += `: ${output}`;
285
- } else if (Array.isArray(output)) {
286
- // Handle array of archetypes: [serviceAreaArcheType]
287
- const archetypeInstance = output[0];
288
- const typeName = getArchetypeTypeName(archetypeInstance);
289
- if (typeName) {
290
- fieldDef += `: [${typeName}]`;
291
- } else {
292
- logger.warn(`Invalid array output type for ${name}, expected archetype instance`);
293
- fieldDef += `: [Any]`;
294
- }
295
- } else if (output instanceof BaseArcheType) {
296
- // Handle single archetype instance: serviceAreaArcheType
297
- const typeName = getArchetypeTypeName(output);
298
- if (typeName) {
299
- fieldDef += `: ${typeName}`;
300
- } else {
301
- logger.warn(`Could not determine type name for archetype in ${name}`);
302
- fieldDef += `: Any`;
303
- }
304
- } else if (typeof output === 'object') {
305
- const outputName = `${name}Output`;
306
- typeDefs += `type ${outputName} {\n${Object.entries(output).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n}\n`;
307
- fieldDef += `: ${outputName}`;
308
- }
309
- if (type === 'Query') {
310
- queryFields.push(fieldDef);
311
- logger.trace(`Added query field: ${fieldDef}`);
312
- } else if (type === 'Mutation') {
313
- mutationFields.push(fieldDef);
314
- logger.trace(`Added mutation field: ${fieldDef}`);
315
- }
316
- } catch (opError) {
317
- logger.error(`Failed to process operation ${op.name || 'unknown'} in ${service.constructor.name}: ${opError}`);
318
- logger.error(`Error stack: ${opError instanceof Error ? opError.stack : 'No stack'}`);
319
- }
320
- });
321
-
322
- logger.trace(`Completed processing operations for ${service.constructor.name}`);
323
- }
324
- });
325
73
 
326
- // Process field resolvers
327
- services.forEach(service => {
328
- const fields = service.__graphqlFields || service.constructor.prototype.__graphqlFields;
329
- if (fields) {
330
- fields.forEach((fieldMeta: any) => {
331
- const { type, field, propertyKey } = fieldMeta;
332
- if (!resolvers[type]) resolvers[type] = {};
333
- resolvers[type][field] = async (parent: any, args: any, context: any, info: any) => {
334
- try {
335
- return await service[propertyKey](parent, args, context, info);
336
- } catch (error) {
337
- logger.error(`Error in ${type}.${field}:`);
338
- logger.error(error);
339
- if (error instanceof GraphQLError) {
340
- throw error;
341
- }
342
- throw new GraphQLError(`Internal error in ${field}`, {
343
- extensions: {
344
- code: "INTERNAL_ERROR",
345
- originalError: process.env.NODE_ENV === 'development' ? error : undefined
346
- }
347
- });
348
- }
349
- };
350
- });
74
+ export function GraphQLSubscription<T extends BaseArcheType | BaseArcheType[] | string = string>(meta: GraphQLSubscriptionMeta<T>) {
75
+ return function (target: BaseService, propertyKey: string, descriptor: PropertyDescriptor) {
76
+ if (!target.__graphqlSubscriptions) target.__graphqlSubscriptions = [];
77
+ const subscriptionName = meta.name ?? propertyKey;
78
+ if (!subscriptionName) {
79
+ throw new Error("GraphQLSubscription: Subscription name is required (either meta.name or propertyKey must be defined)");
351
80
  }
352
- });
353
-
354
- if (queryFields.length > 0) {
355
- typeDefs += `type Query {\n${queryFields.map(f => ` ${f}`).join('\n')}\n}\n`;
356
- }
357
- if (mutationFields.length > 0) {
358
- typeDefs += `type Mutation {\n${mutationFields.map(f => ` ${f}`).join('\n')}\n}\n`;
359
- }
360
-
361
- logger.trace(`Query fields count: ${queryFields.length}, Mutation fields count: ${mutationFields.length}`);
362
- logger.trace(`System Type Defs: ${typeDefs}`);
363
- let schema : GraphQLSchema | null = null;
364
- // Check if typeDefs contains actual schema definitions, not just whitespace
365
- if(typeDefs.trim() !== "" && (queryFields.length > 0 || mutationFields.length > 0 || scalarTypes.size > 0)) {
366
- logger.trace(`Creating schema with resolvers: ${Object.keys(resolvers).join(', ')}`);
367
- schema = createSchema({ typeDefs, resolvers });
368
- } else {
369
- logger.warn(`No schema generated - queryFields: ${queryFields.length}, mutationFields: ${mutationFields.length}, scalarTypes: ${scalarTypes.size}`);
370
- }
371
- return { schema, resolvers };
372
- }
81
+ const subscriptionMeta = { ...meta, name: subscriptionName, propertyKey } as GraphQLSubscriptionMeta<any>;
82
+ target.__graphqlSubscriptions.push(subscriptionMeta);
83
+ };
84
+ }
@@ -0,0 +1,37 @@
1
+ import { GraphQLSchema } from "graphql";
2
+ import { GraphQLSchemaOrchestrator } from "./orchestration/GraphQLSchemaOrchestrator";
3
+ import { logger } from "../core/Logger";
4
+
5
+ /**
6
+ * New graph-based GraphQL schema generation function.
7
+ * This is the V2 implementation using the GraphQLSchemaOrchestrator.
8
+ *
9
+ * @param services Array of service instances to generate schema from
10
+ * @param options Configuration options
11
+ * @returns Object containing the generated GraphQL schema and resolvers
12
+ */
13
+ export function generateGraphQLSchemaV2(
14
+ services: any[],
15
+ options?: { enableArchetypeOperations?: boolean }
16
+ ): { schema: GraphQLSchema | null; resolvers: any } {
17
+ try {
18
+ logger.info("Starting GraphQL schema generation with V2 (graph-based) implementation");
19
+
20
+ // Create orchestrator instance
21
+ const orchestrator = new GraphQLSchemaOrchestrator();
22
+
23
+ // Generate schema using orchestrator
24
+ const schema = orchestrator.generateSchema(services);
25
+
26
+ // For now, return empty resolvers since the orchestrator handles everything internally
27
+ // TODO: Extract resolvers from orchestrator if needed for external access
28
+ const resolvers = {};
29
+
30
+ logger.info("GraphQL schema generation V2 completed successfully");
31
+ return { schema, resolvers };
32
+
33
+ } catch (error) {
34
+ logger.error(`Failed to generate GraphQL schema with V2 implementation: ${error instanceof Error ? error.message : String(error)}`);
35
+ throw error;
36
+ }
37
+ }
@@ -0,0 +1,99 @@
1
+ import { logger } from "../../core/Logger";
2
+
3
+ export interface InputTypeDefinition {
4
+ name: string;
5
+ fields: string[];
6
+ }
7
+
8
+ export class InputTypeBuilder {
9
+ private inputTypes: Map<string, string[]> = new Map();
10
+ private deduplicationMap: Map<string, string> = new Map();
11
+
12
+ /**
13
+ * Add an input type definition
14
+ */
15
+ addInputType(typeDef: InputTypeDefinition): void {
16
+ const existing = this.inputTypes.get(typeDef.name);
17
+ if (existing) {
18
+ // Merge fields if type already exists
19
+ const mergedFields = [...new Set([...existing, ...typeDef.fields])];
20
+ this.inputTypes.set(typeDef.name, mergedFields);
21
+ logger.trace(`Merged input type: ${typeDef.name} with ${mergedFields.length} fields`);
22
+ } else {
23
+ this.inputTypes.set(typeDef.name, [...typeDef.fields]);
24
+ logger.trace(`Added input type: ${typeDef.name} with ${typeDef.fields.length} fields`);
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Get or create a deduplicated input type name
30
+ */
31
+ getDeduplicatedName(originalName: string): string {
32
+ // Check if name conflicts with existing types
33
+ let deduplicatedName = originalName;
34
+ let counter = 1;
35
+
36
+ // Keep incrementing counter until we find a name that doesn't conflict
37
+ // with existing input types OR previously returned deduplicated names
38
+ while (this.inputTypes.has(deduplicatedName) || this.deduplicationMap.has(deduplicatedName)) {
39
+ deduplicatedName = `${originalName}${counter}`;
40
+ counter++;
41
+ }
42
+
43
+ // Mark this name as used (store original name for reference)
44
+ this.deduplicationMap.set(deduplicatedName, originalName);
45
+ return deduplicatedName;
46
+ }
47
+
48
+ /**
49
+ * Build all input type definitions
50
+ */
51
+ buildInputTypes(): string {
52
+ if (this.inputTypes.size === 0) {
53
+ return '';
54
+ }
55
+
56
+ const typeDefs: string[] = [];
57
+
58
+ for (const [typeName, fields] of this.inputTypes.entries()) {
59
+ const sortedFields = fields.sort();
60
+ const typeDef = `input ${typeName} {\n${sortedFields.map(f => ` ${f}`).join('\n')}\n}\n`;
61
+ typeDefs.push(typeDef);
62
+ }
63
+
64
+ return typeDefs.join('\n');
65
+ }
66
+
67
+ /**
68
+ * Check if an input type exists
69
+ */
70
+ hasInputType(name: string): boolean {
71
+ return this.inputTypes.has(name);
72
+ }
73
+
74
+ /**
75
+ * Get all input type names
76
+ */
77
+ getInputTypeNames(): string[] {
78
+ return Array.from(this.inputTypes.keys());
79
+ }
80
+
81
+ /**
82
+ * Clear all input types (for reuse)
83
+ */
84
+ clear(): void {
85
+ this.inputTypes.clear();
86
+ this.deduplicationMap.clear();
87
+ }
88
+
89
+ /**
90
+ * Get statistics
91
+ */
92
+ getStats(): { totalTypes: number; totalFields: number } {
93
+ const totalFields = Array.from(this.inputTypes.values()).reduce((sum, fields) => sum + fields.length, 0);
94
+ return {
95
+ totalTypes: this.inputTypes.size,
96
+ totalFields
97
+ };
98
+ }
99
+ }