Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@types/passport-jwt": "^4.0.1",
"apollo-server-express": "^3.13.0",
"bcrypt": "^6.0.0",
"chokidar": "^4.0.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"dotenv": "^16.5.0",
Expand Down
23 changes: 18 additions & 5 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ import { MongooseModule } from '@nestjs/mongoose';
import { PubSub } from 'graphql-subscriptions';
import { join } from 'path';
import * as dotenv from 'dotenv';
import * as fs from 'fs';

import { UsersModule } from './modules/users/users.module';
import { ModuleSchemaFactory } from './graphql/module-schema.factory';

const pubSub = new PubSub();
dotenv.config();

// Ensure schemas directory exists
const schemasDir = join(process.cwd(), 'src/graphql/schemas');
if (!fs.existsSync(schemasDir)) {
fs.mkdirSync(schemasDir, { recursive: true });
}

@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
Expand All @@ -21,15 +29,20 @@ dotenv.config();
connection,
pubSub,
}),
autoSchemaFile: join(process.cwd(), 'src/graphql/schema.gql'),
autoSchemaFile: join(process.cwd(), 'src/graphql/schemas/schema.gql'),
buildSchemaOptions: {
// Set orphanedTypes to ensure types are included properly
orphanedTypes: [],
},
playground: true,
formatError: (error) => {
return error
}
return error;
},
}),

MongooseModule.forRoot(process.env.MONGO_URI),
UsersModule
UsersModule,
],
providers: [ModuleSchemaFactory],
})
export class AppModule { }
export class AppModule {}
155 changes: 155 additions & 0 deletions src/graphql/module-schema.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { INestApplication, Injectable } from '@nestjs/common';
import { GraphQLSchemaHost } from '@nestjs/graphql';
import { join } from 'path';
import {
printSchema,
GraphQLSchema,
buildSchema,
GraphQLObjectType,
GraphQLList,
GraphQLNonNull,
} from 'graphql';
import * as fs from 'fs';

@Injectable()
export class ModuleSchemaFactory {
constructor(private readonly gqlSchemaHost: GraphQLSchemaHost) {}

async exportSchemaForModule(
moduleName: string,
typeNames: string[],
): Promise<void> {
// Get the full GraphQL schema
const { schema } = this.gqlSchemaHost;

// Ensure schemas directory exists
const schemasDir = join(process.cwd(), 'src/graphql/schemas');
if (!fs.existsSync(schemasDir)) {
fs.mkdirSync(schemasDir, { recursive: true });
}

try {
// Create separate SDL content for this module
let moduleSDL = '';

// Add the specified types to the module schema
for (const typeName of typeNames) {
const type = schema.getType(typeName);
if (type) {
// Extract the SDL for this type
const typeSDL = extractTypeSDL(schema, typeName);
if (typeSDL) {
moduleSDL += typeSDL + '\n\n';
}
}
}

// Add Query fields related to this module
const queryType = schema.getQueryType();
if (queryType) {
const queryFields = Object.values(queryType.getFields()).filter(
(field) => {
// Filter query fields related to this module
// We're checking if the return type is related to any of our module types
const returnTypeName = getBaseTypeName(field.type);
return typeNames.includes(returnTypeName);
},
);

if (queryFields.length > 0) {
moduleSDL += 'type Query {\n';

for (const field of queryFields) {
const args =
field.args.length > 0
? `(${field.args
.map((arg) => `${arg.name}: ${arg.type}`)
.join(', ')})`
: '';

moduleSDL += ` ${field.name}${args}: ${field.type}\n`;
}

moduleSDL += '}\n';
}
}

// Write to file
const outputPath = join(schemasDir, `${moduleName}.schema.gql`);
fs.writeFileSync(
outputPath,
`# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

# Schema for module: ${moduleName}
# Types included: ${typeNames.join(', ')}

${moduleSDL}`,
);

console.log(
`Schema for module '${moduleName}' generated at ${outputPath}`,
);
} catch (error) {
console.error(
`Error generating schema for module '${moduleName}':`,
error,
);
}
}
}

// Helper function to extract type definition in SDL format
function extractTypeSDL(
schema: GraphQLSchema,
typeName: string,
): string | null {
const type = schema.getType(typeName);
if (!type) return null;

// Generate SDL just for this type
const typeDefinition = printSchema(
new GraphQLSchema({
types: [type],
}),
);

// Remove schema definition and just keep the type definition
return typeDefinition.replace(/schema \{[\s\S]*?\}\s+/g, '').trim();
}

// Helper function to get the base type name from a GraphQL type
function getBaseTypeName(type: any): string {
if (type instanceof GraphQLNonNull || type instanceof GraphQLList) {
return getBaseTypeName(type.ofType);
}
return type.name;
}

/**
* Helper function to export schemas after the application is fully initialized
*/
export async function exportSchemasAfterInit(
app: INestApplication,
): Promise<void> {
const moduleSchemaFactory = app.get(ModuleSchemaFactory);

// Define modules and their types
const moduleConfigs = [
{
name: 'users',
types: ['User', 'DateTime'],
},
{
name: 'products',
types: ['Product', 'DateTime'],
},
// Add more modules as needed
];

// Export schema for each module
for (const config of moduleConfigs) {
await moduleSchemaFactory.exportSchemaForModule(config.name, config.types);
}
}
7 changes: 7 additions & 0 deletions src/graphql/schema.gql → src/graphql/schemas/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date
"""
scalar DateTime

type Product {
_id: ID!
name: String!
createdAt: DateTime!
}

type Query {
users: [User!]!
products: [Product!]!
}
22 changes: 22 additions & 0 deletions src/graphql/schemas/users.schema.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

# Schema for module: users
# Types included: User, DateTime

type User {
_id: ID!
name: String!
email: String!
createdAt: DateTime!
}

"""
A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.
"""
scalar DateTime

type Query {
users: [User!]!
}
13 changes: 7 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { graphqlUploadExpress } from 'graphql-upload-ts';

import { AppModule } from './app.module';
import { AppConfig } from './config/app.config';
import { exportSchemasAfterInit } from './graphql/module-schema.factory';

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(
Expand All @@ -15,17 +16,17 @@ async function bootstrap() {
{
cors: AppConfig.cors,
bodyParser: AppConfig.bodyParser,
}
},
);

app.use(
'/graphql',
graphqlUploadExpress(AppConfig.graphqlUpload),
);
app.use('/graphql', graphqlUploadExpress(AppConfig.graphqlUpload));

app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.use('/uploads', express.static(AppConfig.staticFiles.uploadsPath));

await app.listen(AppConfig.port);

await exportSchemasAfterInit(app);
}
bootstrap();

bootstrap();
Loading