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,234 @@
1
+ import { GraphQLError } from "graphql";
2
+ import { logger } from "../../core/Logger";
3
+ import { type ZodType } from "zod";
4
+ import * as z from "zod";
5
+
6
+ /** Check if error is a GraphQLError (handles cross-package version mismatches) */
7
+ function isGraphQLError(error: unknown): error is GraphQLError {
8
+ return error instanceof GraphQLError ||
9
+ (error !== null && typeof error === 'object' && 'extensions' in error &&
10
+ 'message' in error && typeof (error as any).message === 'string');
11
+ }
12
+
13
+ export interface ResolverDefinition {
14
+ name: string;
15
+ type: "Query" | "Mutation" | "Subscription";
16
+ service: any;
17
+ propertyKey: string;
18
+ zodSchema?: ZodType;
19
+ hasInput?: boolean;
20
+ }
21
+
22
+ export class ResolverBuilder {
23
+ private resolvers: Record<string, Record<string, Function>> = {
24
+ Query: {},
25
+ Mutation: {},
26
+ Subscription: {}
27
+ };
28
+
29
+ /**
30
+ * Add a resolver definition
31
+ */
32
+ addResolver(definition: ResolverDefinition): void {
33
+ const { name, type, service, propertyKey, zodSchema, hasInput } = definition;
34
+
35
+ let resolver: any;
36
+
37
+ if (type === "Subscription") {
38
+ // Subscriptions need special handling with subscribe/resolve pattern
39
+ resolver = hasInput
40
+ ? this.createSubscriptionResolverWithInput(service, propertyKey, zodSchema)
41
+ : this.createSubscriptionResolverWithoutInput(service, propertyKey);
42
+ } else {
43
+ // Queries and Mutations use regular resolver pattern
44
+ resolver = hasInput
45
+ ? this.createResolverWithInput(service, propertyKey, zodSchema)
46
+ : this.createResolverWithoutInput(service, propertyKey);
47
+ }
48
+
49
+ // Ensure the resolver category exists (should always exist due to initialization)
50
+ if (!this.resolvers[type]) {
51
+ this.resolvers[type] = {};
52
+ }
53
+ this.resolvers[type][name] = resolver;
54
+ logger.trace(`Added ${type} resolver: ${name}`);
55
+ }
56
+
57
+ /**
58
+ * Create a resolver that expects input arguments
59
+ */
60
+ private createResolverWithInput(service: any, propertyKey: string, zodSchema?: ZodType): Function {
61
+ return async (_: any, args: any, context: any, info: any) => {
62
+ try {
63
+ const inputArgs = args.input || args;
64
+
65
+ // Automatically validate with Zod schema if provided
66
+ if (zodSchema) {
67
+ try {
68
+ const validated = zodSchema.parse(inputArgs);
69
+ return await service[propertyKey](validated, context, info);
70
+ } catch (error) {
71
+ if (error instanceof z.ZodError) {
72
+ // Let handleGraphQLError convert Zod errors to user-friendly messages
73
+ const { handleGraphQLError } = await import("../../core/ErrorHandler");
74
+ handleGraphQLError(error);
75
+ }
76
+ throw error;
77
+ }
78
+ } else {
79
+ return await service[propertyKey](inputArgs, context, info);
80
+ }
81
+ } catch (error) {
82
+ logger.error(`Error in resolver with input:`);
83
+ logger.error(error);
84
+ if (isGraphQLError(error)) {
85
+ throw error;
86
+ }
87
+ throw new GraphQLError(`Internal error`, {
88
+ extensions: {
89
+ code: "INTERNAL_ERROR",
90
+ originalError: process.env.NODE_ENV === 'development' ? error : undefined
91
+ }
92
+ });
93
+ }
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Create a resolver that doesn't expect input arguments
99
+ */
100
+ private createResolverWithoutInput(service: any, propertyKey: string): Function {
101
+ return async (_: any, args: any, context: any, info: any) => {
102
+ try {
103
+ const result = await service[propertyKey]({}, context, info);
104
+ return result;
105
+ } catch (error) {
106
+ logger.error(`Error in resolver without input:`);
107
+ logger.error(error);
108
+ if (isGraphQLError(error)) {
109
+ throw error;
110
+ }
111
+ throw new GraphQLError(`Internal error`, {
112
+ extensions: {
113
+ code: "INTERNAL_ERROR",
114
+ originalError: process.env.NODE_ENV === 'development' ? error : undefined
115
+ }
116
+ });
117
+ }
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Create a subscription resolver with input (returns { subscribe, resolve })
123
+ */
124
+ private createSubscriptionResolverWithInput(service: any, propertyKey: string, zodSchema?: ZodType): any {
125
+ return {
126
+ subscribe: async (_: any, args: any, context: any, info: any) => {
127
+ try {
128
+ const inputArgs = args.input || args;
129
+
130
+ // Automatically validate with Zod schema if provided
131
+ if (zodSchema) {
132
+ try {
133
+ const validated = zodSchema.parse(inputArgs);
134
+ return await service[propertyKey](validated, context, info);
135
+ } catch (error) {
136
+ if (error instanceof z.ZodError) {
137
+ const { handleGraphQLError } = await import("../../core/ErrorHandler");
138
+ handleGraphQLError(error);
139
+ }
140
+ throw error;
141
+ }
142
+ } else {
143
+ return await service[propertyKey](inputArgs, context, info);
144
+ }
145
+ } catch (error) {
146
+ logger.error(`Error in subscription with input:`);
147
+ logger.error(error);
148
+ if (isGraphQLError(error)) {
149
+ throw error;
150
+ }
151
+ throw new GraphQLError(`Internal error in subscription`, {
152
+ extensions: {
153
+ code: "INTERNAL_ERROR",
154
+ originalError: process.env.NODE_ENV === 'development' ? error : undefined
155
+ }
156
+ });
157
+ }
158
+ },
159
+ resolve: (payload: any) => payload
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Create a subscription resolver without input (returns { subscribe, resolve })
165
+ */
166
+ private createSubscriptionResolverWithoutInput(service: any, propertyKey: string): any {
167
+ return {
168
+ subscribe: async (_: any, args: any, context: any, info: any) => {
169
+ try {
170
+ return await service[propertyKey]({}, context, info);
171
+ } catch (error) {
172
+ logger.error(`Error in subscription without input:`);
173
+ logger.error(error);
174
+ if (isGraphQLError(error)) {
175
+ throw error;
176
+ }
177
+ throw new GraphQLError(`Internal error in subscription`, {
178
+ extensions: {
179
+ code: "INTERNAL_ERROR",
180
+ originalError: process.env.NODE_ENV === 'development' ? error : undefined
181
+ }
182
+ });
183
+ }
184
+ },
185
+ resolve: (payload: any) => payload
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Get all built resolvers
191
+ */
192
+ getResolvers(): Record<string, Record<string, Function>> {
193
+ return { ...this.resolvers };
194
+ }
195
+
196
+ /**
197
+ * Get resolvers for a specific type
198
+ */
199
+ getResolversForType(type: "Query" | "Mutation" | "Subscription"): Record<string, Function> {
200
+ return { ...this.resolvers[type] };
201
+ }
202
+
203
+ /**
204
+ * Clear all resolvers (for reuse)
205
+ */
206
+ clear(): void {
207
+ this.resolvers = {
208
+ Query: {},
209
+ Mutation: {},
210
+ Subscription: {}
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Add a scalar resolver
216
+ */
217
+ addScalarResolver(name: string, resolver: any): void {
218
+ if (!this.resolvers[name]) {
219
+ this.resolvers[name] = resolver;
220
+ logger.trace(`Added scalar resolver: ${name}`);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Get statistics
226
+ */
227
+ getStats(): { queries: number; mutations: number; subscriptions: number } {
228
+ return {
229
+ queries: Object.keys(this.resolvers.Query ?? {}).length,
230
+ mutations: Object.keys(this.resolvers.Mutation ?? {}).length,
231
+ subscriptions: Object.keys(this.resolvers.Subscription ?? {}).length
232
+ };
233
+ }
234
+ }
@@ -0,0 +1,105 @@
1
+ import { logger } from "../../core/Logger";
2
+
3
+ export interface OperationField {
4
+ name: string;
5
+ fieldDef: string;
6
+ }
7
+
8
+ export class TypeDefBuilder {
9
+ private queryFields: string[] = [];
10
+ private mutationFields: string[] = [];
11
+ private subscriptionFields: string[] = [];
12
+
13
+ /**
14
+ * Add a query field
15
+ */
16
+ addQueryField(field: OperationField): void {
17
+ this.queryFields.push(field.fieldDef);
18
+ logger.trace(`Added query field: ${field.fieldDef}`);
19
+ }
20
+
21
+ /**
22
+ * Add a mutation field
23
+ */
24
+ addMutationField(field: OperationField): void {
25
+ this.mutationFields.push(field.fieldDef);
26
+ logger.trace(`Added mutation field: ${field.fieldDef}`);
27
+ }
28
+
29
+ /**
30
+ * Add a subscription field
31
+ */
32
+ addSubscriptionField(field: OperationField): void {
33
+ this.subscriptionFields.push(field.fieldDef);
34
+ logger.trace(`Added subscription field: ${field.fieldDef}`);
35
+ }
36
+
37
+ /**
38
+ * Build the Query type definition
39
+ */
40
+ buildQueryType(): string {
41
+ if (this.queryFields.length === 0) {
42
+ return '';
43
+ }
44
+
45
+ const sortedFields = this.queryFields.sort();
46
+ return `type Query {\n${sortedFields.map(f => ` ${f}`).join('\n')}\n}\n`;
47
+ }
48
+
49
+ /**
50
+ * Build the Mutation type definition
51
+ */
52
+ buildMutationType(): string {
53
+ if (this.mutationFields.length === 0) {
54
+ return '';
55
+ }
56
+
57
+ const sortedFields = this.mutationFields.sort();
58
+ return `type Mutation {\n${sortedFields.map(f => ` ${f}`).join('\n')}\n}\n`;
59
+ }
60
+
61
+ /**
62
+ * Build the Subscription type definition
63
+ */
64
+ buildSubscriptionType(): string {
65
+ if (this.subscriptionFields.length === 0) {
66
+ return '';
67
+ }
68
+
69
+ const sortedFields = this.subscriptionFields.sort();
70
+ return `type Subscription {\n${sortedFields.map(f => ` ${f}`).join('\n')}\n}\n`;
71
+ }
72
+
73
+ /**
74
+ * Build all operation types
75
+ */
76
+ buildAllOperationTypes(): string {
77
+ let typeDefs = '';
78
+
79
+ typeDefs += this.buildQueryType();
80
+ typeDefs += this.buildMutationType();
81
+ typeDefs += this.buildSubscriptionType();
82
+
83
+ return typeDefs;
84
+ }
85
+
86
+ /**
87
+ * Clear all fields (for reuse)
88
+ */
89
+ clear(): void {
90
+ this.queryFields = [];
91
+ this.mutationFields = [];
92
+ this.subscriptionFields = [];
93
+ }
94
+
95
+ /**
96
+ * Get statistics
97
+ */
98
+ getStats(): { queries: number; mutations: number; subscriptions: number } {
99
+ return {
100
+ queries: this.queryFields.length,
101
+ mutations: this.mutationFields.length,
102
+ subscriptions: this.subscriptionFields.length
103
+ };
104
+ }
105
+ }
@@ -0,0 +1,3 @@
1
+ export { TypeDefBuilder, type OperationField } from "./TypeDefBuilder";
2
+ export { InputTypeBuilder, type InputTypeDefinition } from "./InputTypeBuilder";
3
+ export { ResolverBuilder, type ResolverDefinition } from "./ResolverBuilder";
@@ -1,6 +1,6 @@
1
1
  import "reflect-metadata";
2
2
  import type { UploadDecoratorConfig } from "../../types/upload.types";
3
- import { UploadManager } from "../../core/UploadManager";
3
+ import { UploadManager } from "../../upload";
4
4
  import { logger as MainLogger } from "../../core/Logger";
5
5
 
6
6
  const logger = MainLogger.child({ scope: "UploadDecorator" });
@@ -0,0 +1,85 @@
1
+ import {
2
+ type ASTVisitor,
3
+ type DocumentNode,
4
+ type FragmentDefinitionNode,
5
+ type ValidationContext,
6
+ GraphQLError,
7
+ Kind,
8
+ } from "graphql";
9
+
10
+ export function depthLimitRule(maxDepth: number) {
11
+ return function DepthLimitValidationRule(
12
+ context: ValidationContext,
13
+ ): ASTVisitor {
14
+ const document: DocumentNode = context.getDocument();
15
+
16
+ // Build fragment map
17
+ const fragments = new Map<string, FragmentDefinitionNode>();
18
+ for (const def of document.definitions) {
19
+ if (def.kind === Kind.FRAGMENT_DEFINITION) {
20
+ fragments.set(def.name.value, def);
21
+ }
22
+ }
23
+
24
+ function measureDepth(
25
+ node: { selectionSet?: { selections: readonly any[] } },
26
+ depth: number,
27
+ visited: Set<string>,
28
+ ): number {
29
+ if (!node.selectionSet) return depth;
30
+
31
+ let max = depth;
32
+ for (const selection of node.selectionSet.selections) {
33
+ if (selection.kind === Kind.FIELD) {
34
+ const fieldDepth = measureDepth(
35
+ selection,
36
+ depth + 1,
37
+ visited,
38
+ );
39
+ if (fieldDepth > max) max = fieldDepth;
40
+ } else if (selection.kind === Kind.INLINE_FRAGMENT) {
41
+ const fragDepth = measureDepth(selection, depth, visited);
42
+ if (fragDepth > max) max = fragDepth;
43
+ } else if (selection.kind === Kind.FRAGMENT_SPREAD) {
44
+ const name = selection.name.value;
45
+ if (visited.has(name)) continue;
46
+ visited.add(name);
47
+ const fragment = fragments.get(name);
48
+ if (fragment) {
49
+ const fragDepth = measureDepth(
50
+ fragment,
51
+ depth,
52
+ visited,
53
+ );
54
+ if (fragDepth > max) max = fragDepth;
55
+ }
56
+ }
57
+ }
58
+ return max;
59
+ }
60
+
61
+ return {
62
+ OperationDefinition(node) {
63
+ // Skip introspection queries — they are inherently deep (13+ levels)
64
+ // and must be allowed for tooling (codegen, IDE autocomplete, etc.)
65
+ if (node.selectionSet) {
66
+ const isIntrospection = node.selectionSet.selections.every(
67
+ (sel) =>
68
+ sel.kind === Kind.FIELD &&
69
+ sel.name.value.startsWith("__"),
70
+ );
71
+ if (isIntrospection) return;
72
+ }
73
+
74
+ const depth = measureDepth(node, 0, new Set());
75
+ if (depth > maxDepth) {
76
+ context.reportError(
77
+ new GraphQLError(
78
+ `Query depth ${depth} exceeds maximum allowed depth of ${maxDepth}`,
79
+ ),
80
+ );
81
+ }
82
+ },
83
+ };
84
+ };
85
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Core graph data structures for GraphQL schema generation.
3
+ * Following Gall's Law: Start with a simple system that works.
4
+ */
5
+
6
+ export enum NodeType {
7
+ TYPE = 'TYPE',
8
+ OPERATION = 'OPERATION',
9
+ FIELD = 'FIELD',
10
+ INPUT = 'INPUT',
11
+ SCALAR = 'SCALAR'
12
+ }
13
+
14
+ export enum GraphQLTypeKind {
15
+ SCALAR = 'SCALAR',
16
+ OBJECT = 'OBJECT',
17
+ INTERFACE = 'INTERFACE',
18
+ UNION = 'UNION',
19
+ ENUM = 'ENUM',
20
+ INPUT_OBJECT = 'INPUT_OBJECT'
21
+ }
22
+
23
+ export enum OperationType {
24
+ QUERY = 'QUERY',
25
+ MUTATION = 'MUTATION',
26
+ SUBSCRIPTION = 'SUBSCRIPTION'
27
+ }
28
+
29
+ /**
30
+ * Base class for all graph nodes in the GraphQL schema generation system.
31
+ * Each node represents a component of the GraphQL schema with its dependencies.
32
+ */
33
+ export abstract class GraphNode {
34
+ public readonly id: string;
35
+ public readonly nodeType: NodeType;
36
+ public readonly metadata: Record<string, any>;
37
+ public readonly dependencies: string[]; // Array of node IDs this node depends on
38
+
39
+ constructor(
40
+ id: string,
41
+ nodeType: NodeType,
42
+ metadata: Record<string, any> = {},
43
+ dependencies: string[] = []
44
+ ) {
45
+ this.id = id;
46
+ this.nodeType = nodeType;
47
+ this.metadata = { ...metadata };
48
+ this.dependencies = [...dependencies];
49
+ }
50
+
51
+ /**
52
+ * Get a human-readable description of this node
53
+ */
54
+ abstract getDescription(): string;
55
+
56
+ /**
57
+ * Check if this node depends on another node
58
+ */
59
+ dependsOn(nodeId: string): boolean {
60
+ return this.dependencies.includes(nodeId);
61
+ }
62
+
63
+ /**
64
+ * Add a dependency to this node
65
+ */
66
+ addDependency(nodeId: string): void {
67
+ if (!this.dependencies.includes(nodeId)) {
68
+ this.dependencies.push(nodeId);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Remove a dependency from this node
74
+ */
75
+ removeDependency(nodeId: string): void {
76
+ const index = this.dependencies.indexOf(nodeId);
77
+ if (index > -1) {
78
+ this.dependencies.splice(index, 1);
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Represents a GraphQL type definition (scalar, object, interface, union, enum)
85
+ */
86
+ export class TypeNode extends GraphNode {
87
+ public readonly kind: GraphQLTypeKind;
88
+ public readonly name: string;
89
+ public readonly typeDef: string; // GraphQL type definition string
90
+
91
+ constructor(
92
+ id: string,
93
+ name: string,
94
+ kind: GraphQLTypeKind,
95
+ typeDef: string,
96
+ metadata: Record<string, any> = {},
97
+ dependencies: string[] = []
98
+ ) {
99
+ super(id, NodeType.TYPE, metadata, dependencies);
100
+ this.name = name;
101
+ this.kind = kind;
102
+ this.typeDef = typeDef;
103
+ }
104
+
105
+ getDescription(): string {
106
+ return `Type ${this.name} (${this.kind})`;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Represents a GraphQL operation (Query, Mutation, Subscription)
112
+ */
113
+ export class OperationNode extends GraphNode {
114
+ public readonly operationType: OperationType;
115
+ public readonly name: string;
116
+ public readonly fieldDef: string; // GraphQL field definition string
117
+ public readonly inputNodeId?: string; // ID of input node if any
118
+ public readonly outputNodeId?: string; // ID of output node if any
119
+
120
+ constructor(
121
+ id: string,
122
+ name: string,
123
+ operationType: OperationType,
124
+ fieldDef: string,
125
+ inputNodeId?: string,
126
+ outputNodeId?: string,
127
+ metadata: Record<string, any> = {},
128
+ dependencies: string[] = []
129
+ ) {
130
+ super(id, NodeType.OPERATION, metadata, dependencies);
131
+ this.name = name;
132
+ this.operationType = operationType;
133
+ this.fieldDef = fieldDef;
134
+ this.inputNodeId = inputNodeId;
135
+ this.outputNodeId = outputNodeId;
136
+
137
+ // Auto-add dependencies based on input/output nodes
138
+ if (inputNodeId && !dependencies.includes(inputNodeId)) {
139
+ this.addDependency(inputNodeId);
140
+ }
141
+ if (outputNodeId && !dependencies.includes(outputNodeId)) {
142
+ this.addDependency(outputNodeId);
143
+ }
144
+ }
145
+
146
+ getDescription(): string {
147
+ return `${this.operationType} operation ${this.name}`;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Represents a GraphQL field resolver
153
+ */
154
+ export class FieldNode extends GraphNode {
155
+ public readonly typeName: string; // The type this field belongs to
156
+ public readonly fieldName: string;
157
+ public readonly fieldDef: string; // GraphQL field definition string
158
+
159
+ constructor(
160
+ id: string,
161
+ typeName: string,
162
+ fieldName: string,
163
+ fieldDef: string,
164
+ metadata: Record<string, any> = {},
165
+ dependencies: string[] = []
166
+ ) {
167
+ super(id, NodeType.FIELD, metadata, dependencies);
168
+ this.typeName = typeName;
169
+ this.fieldName = fieldName;
170
+ this.fieldDef = fieldDef;
171
+ }
172
+
173
+ getDescription(): string {
174
+ return `Field ${this.fieldName} on type ${this.typeName}`;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Represents a GraphQL input type definition
180
+ */
181
+ export class InputNode extends GraphNode {
182
+ public readonly name: string;
183
+ public readonly typeDef: string; // GraphQL input type definition string
184
+ public readonly isOptional: boolean; // Whether this input is optional
185
+
186
+ constructor(
187
+ id: string,
188
+ name: string,
189
+ typeDef: string,
190
+ isOptional: boolean = false,
191
+ metadata: Record<string, any> = {},
192
+ dependencies: string[] = []
193
+ ) {
194
+ super(id, NodeType.INPUT, metadata, dependencies);
195
+ this.name = name;
196
+ this.typeDef = typeDef;
197
+ this.isOptional = isOptional;
198
+ }
199
+
200
+ getDescription(): string {
201
+ return `Input type ${this.name}${this.isOptional ? ' (optional)' : ''}`;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Represents a GraphQL scalar type
207
+ */
208
+ export class ScalarNode extends GraphNode {
209
+ public readonly name: string;
210
+
211
+ constructor(
212
+ id: string,
213
+ name: string,
214
+ metadata: Record<string, any> = {},
215
+ dependencies: string[] = []
216
+ ) {
217
+ super(id, NodeType.SCALAR, metadata, dependencies);
218
+ this.name = name;
219
+ }
220
+
221
+ getDescription(): string {
222
+ return `Scalar type ${this.name}`;
223
+ }
224
+ }