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.
- package/.claude/settings.local.json +47 -0
- package/.claude/skills/update-memory.md +74 -0
- package/.prettierrc +4 -0
- package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
- package/.serena/memories/architecture.md +154 -0
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
- package/.serena/memories/code_style_and_conventions.md +76 -0
- package/.serena/memories/project_overview.md +43 -0
- package/.serena/memories/schema-dsl-plan.md +107 -0
- package/.serena/memories/suggested_commands.md +80 -0
- package/.serena/memories/typescript-compilation-status.md +54 -0
- package/.serena/project.yml +114 -0
- package/TODO.md +1 -7
- package/bun.lock +150 -4
- package/bunfig.toml +10 -0
- package/config/cache.config.ts +77 -0
- package/config/upload.config.ts +4 -5
- package/core/App.ts +870 -123
- package/core/ArcheType.ts +2268 -377
- package/core/BatchLoader.ts +181 -71
- package/core/Config.ts +153 -0
- package/core/Decorators.ts +4 -1
- package/core/Entity.ts +621 -92
- package/core/EntityHookManager.ts +1 -1
- package/core/EntityInterface.ts +3 -1
- package/core/EntityManager.ts +1 -13
- package/core/ErrorHandler.ts +8 -2
- package/core/Logger.ts +9 -0
- package/core/Middleware.ts +34 -0
- package/core/RequestContext.ts +5 -1
- package/core/RequestLoaders.ts +227 -93
- package/core/SchedulerManager.ts +193 -52
- package/core/cache/CacheAnalytics.ts +399 -0
- package/core/cache/CacheFactory.ts +145 -0
- package/core/cache/CacheManager.ts +520 -0
- package/core/cache/CacheProvider.ts +34 -0
- package/core/cache/CacheWarmer.ts +157 -0
- package/core/cache/CompressionUtils.ts +110 -0
- package/core/cache/MemoryCache.ts +251 -0
- package/core/cache/MultiLevelCache.ts +180 -0
- package/core/cache/NoOpCache.ts +53 -0
- package/core/cache/RedisCache.ts +464 -0
- package/core/cache/TTLStrategy.ts +254 -0
- package/core/cache/index.ts +6 -0
- package/core/components/BaseComponent.ts +120 -0
- package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
- package/core/components/Decorators.ts +88 -0
- package/core/components/Interfaces.ts +7 -0
- package/core/components/index.ts +5 -0
- package/core/decorators/EntityHooks.ts +0 -3
- package/core/decorators/IndexedField.ts +26 -0
- package/core/decorators/ScheduledTask.ts +0 -47
- package/core/events/EntityLifecycleEvents.ts +1 -1
- package/core/health.ts +112 -0
- package/core/metadata/definitions/ArcheType.ts +14 -0
- package/core/metadata/definitions/Component.ts +9 -0
- package/core/metadata/definitions/gqlObject.ts +1 -1
- package/core/metadata/index.ts +42 -1
- package/core/metadata/metadata-storage.ts +28 -2
- package/core/middleware/AccessLog.ts +59 -0
- package/core/middleware/RequestId.ts +38 -0
- package/core/middleware/SecurityHeaders.ts +62 -0
- package/core/middleware/index.ts +3 -0
- package/core/scheduler/DistributedLock.ts +266 -0
- package/core/scheduler/index.ts +15 -0
- package/core/validateEnv.ts +92 -0
- package/database/DatabaseHelper.ts +416 -40
- package/database/IndexingStrategy.ts +342 -0
- package/database/PreparedStatementCache.ts +226 -0
- package/database/index.ts +32 -7
- package/database/sqlHelpers.ts +14 -2
- package/endpoints/archetypes.ts +362 -0
- package/endpoints/components.ts +58 -0
- package/endpoints/entity.ts +80 -0
- package/endpoints/index.ts +27 -0
- package/endpoints/query.ts +93 -0
- package/endpoints/stats.ts +76 -0
- package/endpoints/tables.ts +212 -0
- package/endpoints/types.ts +155 -0
- package/gql/ArchetypeOperations.ts +32 -86
- package/gql/Generator.ts +27 -315
- package/gql/GeneratorV2.ts +37 -0
- package/gql/builders/InputTypeBuilder.ts +99 -0
- package/gql/builders/ResolverBuilder.ts +234 -0
- package/gql/builders/TypeDefBuilder.ts +105 -0
- package/gql/builders/index.ts +3 -0
- package/gql/decorators/Upload.ts +1 -1
- package/gql/depthLimit.ts +85 -0
- package/gql/graph/GraphNode.ts +224 -0
- package/gql/graph/SchemaGraph.ts +278 -0
- package/gql/helpers.ts +8 -2
- package/gql/index.ts +56 -4
- package/gql/middleware.ts +79 -0
- package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
- package/gql/orchestration/index.ts +1 -0
- package/gql/scanner/ServiceScanner.ts +347 -0
- package/gql/schema/index.ts +458 -0
- package/gql/strategies/TypeGenerationStrategy.ts +329 -0
- package/gql/types.ts +1 -0
- package/gql/utils/TypeSignature.ts +220 -0
- package/gql/utils/index.ts +1 -0
- package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
- package/gql/visitors/DeduplicationVisitor.ts +82 -0
- package/gql/visitors/GraphVisitor.ts +78 -0
- package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
- package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
- package/gql/visitors/TypeCollectorVisitor.ts +79 -0
- package/gql/visitors/VisitorComposer.ts +96 -0
- package/gql/visitors/index.ts +7 -0
- package/package.json +59 -37
- package/plugins/index.ts +2 -2
- package/query/CTENode.ts +97 -0
- package/query/ComponentInclusionNode.ts +689 -0
- package/query/FilterBuilder.ts +127 -0
- package/query/FilterBuilderRegistry.ts +202 -0
- package/query/OrNode.ts +517 -0
- package/query/OrQuery.ts +42 -0
- package/query/Query.ts +1022 -0
- package/query/QueryContext.ts +170 -0
- package/query/QueryDAG.ts +122 -0
- package/query/QueryNode.ts +65 -0
- package/query/SourceNode.ts +53 -0
- package/query/builders/FullTextSearchBuilder.ts +236 -0
- package/query/index.ts +21 -0
- package/scheduler/index.ts +40 -8
- package/service/Service.ts +2 -1
- package/service/ServiceRegistry.ts +6 -5
- package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
- package/storage/S3StorageProvider.ts +316 -0
- package/{core/storage → storage}/StorageProvider.ts +7 -3
- package/studio/bun.lock +482 -0
- package/studio/index.html +13 -0
- package/studio/package.json +39 -0
- package/studio/postcss.config.js +6 -0
- package/studio/src/components/DataTable.tsx +211 -0
- package/studio/src/components/Layout.tsx +13 -0
- package/studio/src/components/PageContainer.tsx +9 -0
- package/studio/src/components/PageHeader.tsx +13 -0
- package/studio/src/components/SearchBar.tsx +57 -0
- package/studio/src/components/Sidebar.tsx +294 -0
- package/studio/src/components/ui/button.tsx +56 -0
- package/studio/src/components/ui/checkbox.tsx +26 -0
- package/studio/src/components/ui/input.tsx +25 -0
- package/studio/src/hooks/useDataTable.ts +131 -0
- package/studio/src/index.css +36 -0
- package/studio/src/lib/api.ts +186 -0
- package/studio/src/lib/utils.ts +13 -0
- package/studio/src/main.tsx +17 -0
- package/studio/src/pages/ArcheType.tsx +239 -0
- package/studio/src/pages/Components.tsx +124 -0
- package/studio/src/pages/EntityInspector.tsx +302 -0
- package/studio/src/pages/QueryRunner.tsx +246 -0
- package/studio/src/pages/Table.tsx +94 -0
- package/studio/src/pages/Welcome.tsx +241 -0
- package/studio/src/routes.tsx +45 -0
- package/studio/src/store/archeTypeSettings.ts +30 -0
- package/studio/src/store/studio.ts +65 -0
- package/studio/src/utils/columnHelpers.tsx +114 -0
- package/studio/studio-instructions.md +81 -0
- package/studio/tailwind.config.js +77 -0
- package/studio/tsconfig.json +24 -0
- package/studio/utils.ts +54 -0
- package/studio/vite.config.js +19 -0
- package/swagger/generator.ts +1 -1
- package/tests/e2e/http.test.ts +126 -0
- package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
- package/tests/fixtures/components/TestOrder.ts +23 -0
- package/tests/fixtures/components/TestProduct.ts +23 -0
- package/tests/fixtures/components/TestUser.ts +20 -0
- package/tests/fixtures/components/index.ts +6 -0
- package/tests/graphql/SchemaGeneration.test.ts +90 -0
- package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
- package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
- package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
- package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
- package/tests/integration/entity/Entity.persistence.test.ts +333 -0
- package/tests/integration/query/Query.exec.test.ts +523 -0
- package/tests/pglite-setup.ts +61 -0
- package/tests/setup.ts +164 -0
- package/tests/stress/BenchmarkRunner.ts +203 -0
- package/tests/stress/DataSeeder.ts +190 -0
- package/tests/stress/StressTestReporter.ts +229 -0
- package/tests/stress/cursor-perf-test.ts +171 -0
- package/tests/stress/fixtures/StressTestComponents.ts +58 -0
- package/tests/stress/index.ts +7 -0
- package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
- package/tests/unit/BatchLoader.test.ts +82 -0
- package/tests/unit/archetype/ArcheType.test.ts +107 -0
- package/tests/unit/cache/CacheManager.test.ts +347 -0
- package/tests/unit/cache/MemoryCache.test.ts +260 -0
- package/tests/unit/cache/RedisCache.test.ts +411 -0
- package/tests/unit/entity/Entity.components.test.ts +244 -0
- package/tests/unit/entity/Entity.test.ts +345 -0
- package/tests/unit/gql/depthLimit.test.ts +203 -0
- package/tests/unit/gql/operationMiddleware.test.ts +293 -0
- package/tests/unit/health/Health.test.ts +129 -0
- package/tests/unit/middleware/AccessLog.test.ts +37 -0
- package/tests/unit/middleware/Middleware.test.ts +98 -0
- package/tests/unit/middleware/RequestId.test.ts +54 -0
- package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
- package/tests/unit/query/FilterBuilder.test.ts +111 -0
- package/tests/unit/query/Query.test.ts +308 -0
- package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
- package/tests/unit/schema/schema-integration.test.ts +426 -0
- package/tests/unit/schema/schema.test.ts +580 -0
- package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
- package/tests/unit/upload/RestUpload.test.ts +267 -0
- package/tests/unit/validateEnv.test.ts +82 -0
- package/tests/utils/entity-tracker.ts +57 -0
- package/tests/utils/index.ts +13 -0
- package/tests/utils/test-context.ts +149 -0
- package/tsconfig.json +5 -1
- package/types/archetype.types.ts +6 -0
- package/types/hooks.types.ts +1 -1
- package/types/query.types.ts +110 -0
- package/types/scheduler.types.ts +68 -7
- package/types/upload.types.ts +1 -0
- package/{core → upload}/FileValidator.ts +10 -1
- package/upload/RestUpload.ts +130 -0
- package/{core/components → upload}/UploadComponent.ts +11 -11
- package/{core → upload}/UploadManager.ts +3 -3
- package/upload/index.ts +23 -7
- package/utils/UploadHelper.ts +27 -6
- package/utils/cronParser.ts +16 -6
- package/.github/workflows/deploy-docs.yml +0 -57
- package/core/Components.ts +0 -202
- package/core/EntityCache.ts +0 -15
- package/core/Query.ts +0 -880
- package/docs/README.md +0 -149
- package/docs/_coverpage.md +0 -36
- package/docs/_sidebar.md +0 -23
- package/docs/api/core.md +0 -568
- package/docs/api/hooks.md +0 -554
- package/docs/api/index.md +0 -222
- package/docs/api/query.md +0 -678
- package/docs/api/service.md +0 -744
- package/docs/core-concepts/archetypes.md +0 -512
- package/docs/core-concepts/components.md +0 -498
- package/docs/core-concepts/entity.md +0 -314
- package/docs/core-concepts/hooks.md +0 -683
- package/docs/core-concepts/query.md +0 -588
- package/docs/core-concepts/services.md +0 -647
- package/docs/examples/code-examples.md +0 -425
- package/docs/getting-started.md +0 -337
- package/docs/index.html +0 -97
- package/tests/bench/insert.bench.ts +0 -60
- package/tests/bench/relations.bench.ts +0 -270
- package/tests/bench/sorting.bench.ts +0 -416
- package/tests/component-hooks-simple.test.ts +0 -117
- package/tests/component-hooks.test.ts +0 -1461
- package/tests/component.test.ts +0 -339
- package/tests/errorHandling.test.ts +0 -155
- package/tests/hooks.test.ts +0 -667
- package/tests/query-sorting.test.ts +0 -101
- package/tests/query.test.ts +0 -81
- package/tests/relations.test.ts +0 -170
- package/tests/scheduler.test.ts +0 -724
package/gql/Generator.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
6
|
-
import { getArchetypeSchema } from "../core/ArcheType";
|
|
3
|
+
import type { SchemaType } from "./schema";
|
|
7
4
|
import BaseArcheType from "../core/ArcheType";
|
|
8
|
-
import {
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
+
}
|