@0xobelisk/graphql-server 1.2.0-pre.100

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 (87) hide show
  1. package/.turbo/turbo-build.log +8 -0
  2. package/DUAL_POOL_CONFIG.md +188 -0
  3. package/Dockerfile +35 -0
  4. package/LICENSE +92 -0
  5. package/README.md +487 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +206 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config/subscription-config.d.ts +80 -0
  11. package/dist/config/subscription-config.d.ts.map +1 -0
  12. package/dist/config/subscription-config.js +158 -0
  13. package/dist/config/subscription-config.js.map +1 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +11 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/plugins/all-fields-filter-plugin.d.ts +4 -0
  19. package/dist/plugins/all-fields-filter-plugin.d.ts.map +1 -0
  20. package/dist/plugins/all-fields-filter-plugin.js +132 -0
  21. package/dist/plugins/all-fields-filter-plugin.js.map +1 -0
  22. package/dist/plugins/database-introspector.d.ts +23 -0
  23. package/dist/plugins/database-introspector.d.ts.map +1 -0
  24. package/dist/plugins/database-introspector.js +96 -0
  25. package/dist/plugins/database-introspector.js.map +1 -0
  26. package/dist/plugins/enhanced-playground.d.ts +9 -0
  27. package/dist/plugins/enhanced-playground.d.ts.map +1 -0
  28. package/dist/plugins/enhanced-playground.js +113 -0
  29. package/dist/plugins/enhanced-playground.js.map +1 -0
  30. package/dist/plugins/enhanced-server-manager.d.ts +29 -0
  31. package/dist/plugins/enhanced-server-manager.d.ts.map +1 -0
  32. package/dist/plugins/enhanced-server-manager.js +262 -0
  33. package/dist/plugins/enhanced-server-manager.js.map +1 -0
  34. package/dist/plugins/index.d.ts +9 -0
  35. package/dist/plugins/index.d.ts.map +1 -0
  36. package/dist/plugins/index.js +26 -0
  37. package/dist/plugins/index.js.map +1 -0
  38. package/dist/plugins/postgraphile-config.d.ts +94 -0
  39. package/dist/plugins/postgraphile-config.d.ts.map +1 -0
  40. package/dist/plugins/postgraphile-config.js +138 -0
  41. package/dist/plugins/postgraphile-config.js.map +1 -0
  42. package/dist/plugins/query-filter.d.ts +4 -0
  43. package/dist/plugins/query-filter.d.ts.map +1 -0
  44. package/dist/plugins/query-filter.js +42 -0
  45. package/dist/plugins/query-filter.js.map +1 -0
  46. package/dist/plugins/simple-naming.d.ts +4 -0
  47. package/dist/plugins/simple-naming.d.ts.map +1 -0
  48. package/dist/plugins/simple-naming.js +79 -0
  49. package/dist/plugins/simple-naming.js.map +1 -0
  50. package/dist/plugins/welcome-page.d.ts +11 -0
  51. package/dist/plugins/welcome-page.d.ts.map +1 -0
  52. package/dist/plugins/welcome-page.js +203 -0
  53. package/dist/plugins/welcome-page.js.map +1 -0
  54. package/dist/server.d.ts +21 -0
  55. package/dist/server.d.ts.map +1 -0
  56. package/dist/server.js +265 -0
  57. package/dist/server.js.map +1 -0
  58. package/dist/universal-subscriptions.d.ts +32 -0
  59. package/dist/universal-subscriptions.d.ts.map +1 -0
  60. package/dist/universal-subscriptions.js +318 -0
  61. package/dist/universal-subscriptions.js.map +1 -0
  62. package/dist/utils/logger/index.d.ts +80 -0
  63. package/dist/utils/logger/index.d.ts.map +1 -0
  64. package/dist/utils/logger/index.js +230 -0
  65. package/dist/utils/logger/index.js.map +1 -0
  66. package/docker-compose.yml +46 -0
  67. package/eslint.config.mjs +3 -0
  68. package/package.json +78 -0
  69. package/src/cli.ts +232 -0
  70. package/src/config/subscription-config.ts +243 -0
  71. package/src/index.ts +11 -0
  72. package/src/plugins/README.md +138 -0
  73. package/src/plugins/all-fields-filter-plugin.ts +158 -0
  74. package/src/plugins/database-introspector.ts +126 -0
  75. package/src/plugins/enhanced-playground.ts +121 -0
  76. package/src/plugins/enhanced-server-manager.ts +314 -0
  77. package/src/plugins/index.ts +9 -0
  78. package/src/plugins/postgraphile-config.ts +182 -0
  79. package/src/plugins/query-filter.ts +50 -0
  80. package/src/plugins/simple-naming.ts +105 -0
  81. package/src/plugins/welcome-page.ts +218 -0
  82. package/src/server.ts +324 -0
  83. package/src/universal-subscriptions.ts +397 -0
  84. package/src/utils/logger/README.md +209 -0
  85. package/src/utils/logger/index.ts +275 -0
  86. package/sui-indexer-schema.graphql +3691 -0
  87. package/tsconfig.json +28 -0
@@ -0,0 +1,218 @@
1
+ import type { DynamicTable } from './database-introspector';
2
+
3
+ export interface WelcomePageConfig {
4
+ port: string | number;
5
+ graphqlEndpoint: string;
6
+ nodeEnv: string;
7
+ schema: string;
8
+ enableCors: string;
9
+ enableSubscriptions: string;
10
+ }
11
+
12
+ // Create custom welcome page
13
+ export function createWelcomePage(tables: DynamicTable[], config: WelcomePageConfig): string {
14
+ const { port, graphqlEndpoint, nodeEnv, schema, enableCors, enableSubscriptions } = config;
15
+
16
+ const tableList = tables
17
+ .map((table) => {
18
+ const keyFields = table.fields.filter((f) => f.is_key).map((f) => f.field_name);
19
+ const valueFields = table.fields.filter((f) => !f.is_key).map((f) => f.field_name);
20
+ return `
21
+ <div class="table-info">
22
+ <h3>📊 ${table.table_name}</h3>
23
+ <div class="fields">
24
+ <div><strong>Key Fields:</strong> ${keyFields.join(', ') || 'None'}</div>
25
+ <div><strong>Value Fields:</strong> ${valueFields.join(', ')}</div>
26
+ </div>
27
+ </div>
28
+ `;
29
+ })
30
+ .join('');
31
+
32
+ return `
33
+ <!DOCTYPE html>
34
+ <html>
35
+ <head>
36
+ <title>🚀 Sui Indexer GraphQL API</title>
37
+ <meta charset="utf-8">
38
+ <meta name="viewport" content="width=device-width, initial-scale=1">
39
+ <style>
40
+ body {
41
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
42
+ margin: 0;
43
+ padding: 20px;
44
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
45
+ color: #333;
46
+ min-height: 100vh;
47
+ }
48
+ .container {
49
+ max-width: 1200px;
50
+ margin: 0 auto;
51
+ background: white;
52
+ padding: 40px;
53
+ border-radius: 16px;
54
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
55
+ }
56
+ h1 {
57
+ color: #2c3e50;
58
+ text-align: center;
59
+ margin-bottom: 10px;
60
+ font-size: 2.5em;
61
+ }
62
+ .subtitle {
63
+ text-align: center;
64
+ color: #7f8c8d;
65
+ margin-bottom: 40px;
66
+ font-size: 1.2em;
67
+ }
68
+ .link {
69
+ display: inline-block;
70
+ margin: 10px;
71
+ padding: 15px 25px;
72
+ background: linear-gradient(135deg, #74b9ff, #0984e3);
73
+ color: white;
74
+ text-decoration: none;
75
+ border-radius: 8px;
76
+ text-align: center;
77
+ font-weight: 500;
78
+ transition: transform 0.2s ease;
79
+ }
80
+ .link:hover {
81
+ transform: translateY(-2px);
82
+ box-shadow: 0 8px 15px rgba(116, 185, 255, 0.4);
83
+ }
84
+ .status {
85
+ color: #00b894;
86
+ font-weight: bold;
87
+ text-align: center;
88
+ font-size: 1.1em;
89
+ margin: 20px 0;
90
+ }
91
+ .warning {
92
+ background: #ffeaa7;
93
+ border-left: 4px solid #fdcb6e;
94
+ padding: 15px;
95
+ margin: 20px 0;
96
+ border-radius: 4px;
97
+ }
98
+ .warning h4 {
99
+ margin-top: 0;
100
+ color: #e17055;
101
+ }
102
+ .table-info {
103
+ background: #f8f9fa;
104
+ padding: 20px;
105
+ margin: 15px 0;
106
+ border-radius: 8px;
107
+ border-left: 4px solid #74b9ff;
108
+ }
109
+ .table-info h3 {
110
+ margin: 0 0 10px 0;
111
+ color: #2c3e50;
112
+ }
113
+ .fields div {
114
+ margin: 5px 0;
115
+ color: #555;
116
+ }
117
+ .info-grid {
118
+ display: grid;
119
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
120
+ gap: 20px;
121
+ margin: 30px 0;
122
+ }
123
+ .info-card {
124
+ background: #f8f9fa;
125
+ padding: 20px;
126
+ border-radius: 8px;
127
+ border: 1px solid #e9ecef;
128
+ }
129
+ .info-card h3 {
130
+ color: #495057;
131
+ margin-top: 0;
132
+ }
133
+ .center {
134
+ text-align: center;
135
+ }
136
+ .highlight {
137
+ background: linear-gradient(135deg, #fdcb6e, #e17055);
138
+ color: white;
139
+ padding: 2px 8px;
140
+ border-radius: 4px;
141
+ font-weight: 500;
142
+ }
143
+ </style>
144
+ </head>
145
+ <body>
146
+ <div class="container">
147
+ <h1>🚀 Sui Indexer GraphQL API</h1>
148
+ <p class="subtitle">Dynamically scan database, automatically generate GraphQL API</p>
149
+ <p class="status">● Server Status: Running Normally | Scanned <span class="highlight">${
150
+ tables.length
151
+ }</span> tables</p>
152
+
153
+ ${
154
+ enableSubscriptions === 'false'
155
+ ? `
156
+ <div class="warning">
157
+ <h4>⚠️ WebSocket subscription feature is temporarily disabled</h4>
158
+ <p>Currently fixing subscription configuration issues. Basic GraphQL query and mutation functions work perfectly.</p>
159
+ </div>
160
+ `
161
+ : `
162
+ <div class="status">
163
+ <p>📡 Real-time subscription feature: ${enableSubscriptions === 'true' ? 'Enabled' : 'Disabled'}</p>
164
+ </div>
165
+ `
166
+ }
167
+
168
+ <div class="center">
169
+ <a href="${graphqlEndpoint}" class="link">📊 GraphQL API</a>
170
+ <a href="/playground" class="link">🎮 Enhanced GraphQL Playground</a>
171
+ </div>
172
+
173
+ <div class="info-grid">
174
+ <div class="info-card">
175
+ <h3>🎯 Core Features</h3>
176
+ <ul>
177
+ <li>✨ Auto-scan dubhe-indexer database</li>
178
+ <li>🔄 Dynamically generate GraphQL schema</li>
179
+ <li>📡 Support real-time subscription features ${enableSubscriptions === 'true' ? '✅' : '⚠️'}</li>
180
+ <li>🚀 Complete CRUD operations</li>
181
+ <li>🛡️ PostGraphile powerful features</li>
182
+ </ul>
183
+ </div>
184
+
185
+ <div class="info-card">
186
+ <h3>📊 Server Information</h3>
187
+ <ul>
188
+ <li>Environment: ${nodeEnv}</li>
189
+ <li>Port: ${port}</li>
190
+ <li>Database Schema: ${schema}</li>
191
+ <li>CORS: ${enableCors === 'true' ? 'Enabled' : 'Disabled'}</li>
192
+ <li>Subscriptions: ${enableSubscriptions === 'true' ? 'Enabled' : 'Disabled'}</li>
193
+ </ul>
194
+ </div>
195
+ </div>
196
+
197
+ <h2>📋 Detected Data Tables</h2>
198
+ ${tableList}
199
+
200
+ <div style="margin-top: 40px; padding: 20px; background: #e3f2fd; border-radius: 8px;">
201
+ <h3>💡 Usage Tips</h3>
202
+ <p>1. Visit <strong>Enhanced GraphQL Playground</strong> for better query experience</p>
203
+ <p> • 📊 Visual Schema Explorer - Click-to-build queries</p>
204
+ <p> • 🎨 Modern UI interface and enhanced code highlighting</p>
205
+ <p> • 📝 Code export feature - Generate client code in multiple languages</p>
206
+ <p> • ⌨️ Keyboard shortcuts support - Ctrl/Cmd+Enter to execute queries</p>
207
+ <p>2. All tables support standard GraphQL query, mutation${
208
+ enableSubscriptions === 'true' ? ' and subscription' : ''
209
+ } operations</p>
210
+ <p>3. Dynamic tables (store_*) automatically generate fields based on table_fields metadata</p>
211
+ <p>4. System tables provide core data access for dubhe-indexer</p>
212
+ ${enableSubscriptions === 'true' ? '<p>5. Use WebSocket for real-time data subscriptions</p>' : ''}
213
+ </div>
214
+ </div>
215
+ </body>
216
+ </html>
217
+ `;
218
+ }
package/src/server.ts ADDED
@@ -0,0 +1,324 @@
1
+ import { postgraphile } from 'postgraphile';
2
+ import { Pool } from 'pg';
3
+ import * as dotenv from 'dotenv';
4
+ import {
5
+ dbLogger,
6
+ serverLogger,
7
+ systemLogger,
8
+ subscriptionLogger,
9
+ logPerformance
10
+ } from './utils/logger';
11
+ import {
12
+ DatabaseIntrospector,
13
+ createPostGraphileConfig,
14
+ PostGraphileConfigOptions,
15
+ WelcomePageConfig
16
+ } from './plugins';
17
+ import { EnhancedServerManager } from './plugins/enhanced-server-manager';
18
+ import { subscriptionConfig, SubscriptionConfigInput } from './config/subscription-config';
19
+ import {
20
+ generateStoreTablesInfo,
21
+ createUniversalSubscriptionsPlugin
22
+ } from './universal-subscriptions';
23
+
24
+ // Load environment variables
25
+ dotenv.config();
26
+
27
+ // Server configuration interface
28
+ export interface ServerConfig {
29
+ // Basic server configuration
30
+ port: string;
31
+ databaseUrl: string;
32
+ schema: string;
33
+ endpoint: string;
34
+ cors: boolean;
35
+ subscriptions: boolean;
36
+ env: string;
37
+
38
+ // Debug configuration
39
+ debug: boolean;
40
+
41
+ // Performance configuration
42
+ queryTimeout: number;
43
+ maxConnections: number;
44
+ heartbeatInterval: number;
45
+ enableMetrics: boolean;
46
+
47
+ // Subscription capabilities
48
+ enableLiveQueries: boolean;
49
+ enablePgSubscriptions: boolean;
50
+ enableNativeWebSocket: boolean;
51
+ realtimePort?: number;
52
+
53
+ // Internal debug flags
54
+ debugNotifications: boolean;
55
+ }
56
+
57
+ // Start server
58
+ export const startServer = async (config: ServerConfig): Promise<void> => {
59
+ // Set log level from config to environment (for logger compatibility)
60
+ process.env.LOG_LEVEL = config.debug ? 'debug' : 'info';
61
+
62
+ // Extract variables from config
63
+ const {
64
+ port: PORT,
65
+ databaseUrl: DATABASE_URL,
66
+ schema: PG_SCHEMA,
67
+ endpoint: GRAPHQL_ENDPOINT,
68
+ cors: ENABLE_CORS,
69
+ subscriptions: ENABLE_SUBSCRIPTIONS_BOOL,
70
+ env: NODE_ENV
71
+ } = config;
72
+
73
+ // Convert boolean values to string format
74
+ const ENABLE_SUBSCRIPTIONS = ENABLE_SUBSCRIPTIONS_BOOL ? 'true' : 'false';
75
+
76
+ // Build subscription configuration and refresh
77
+ systemLogger.info('Refreshing subscription configuration with new settings...');
78
+ const subscriptionConfigInput: SubscriptionConfigInput = {
79
+ enableSubscriptions: ENABLE_SUBSCRIPTIONS_BOOL,
80
+ databaseUrl: DATABASE_URL,
81
+ port: PORT,
82
+ // Use configuration from ServerConfig instead of defaults
83
+ enableLiveQueries: config.enableLiveQueries,
84
+ enablePgSubscriptions: config.enablePgSubscriptions,
85
+ enableNativeWebSocket: config.enableNativeWebSocket,
86
+ realtimePort: config.realtimePort?.toString(),
87
+ maxConnections: config.maxConnections.toString(),
88
+ heartbeatInterval: config.heartbeatInterval.toString(),
89
+ debugNotifications: config.debugNotifications,
90
+ enableMetrics: config.enableMetrics
91
+ };
92
+
93
+ subscriptionConfig.refresh(subscriptionConfigInput);
94
+
95
+ // === Unified Connection Pool Architecture: Single pool handles all operations ===
96
+
97
+ systemLogger.info('🔄 Connection pool configuration', {
98
+ maxConnections: config.maxConnections,
99
+ strategy: 'single-pool-unified',
100
+ operations: ['query', 'mutation', 'subscription']
101
+ });
102
+
103
+ // Unified connection pool - handles all GraphQL operations
104
+ const pgPool = new Pool({
105
+ connectionString: DATABASE_URL,
106
+
107
+ // === Connection Pool Configuration ===
108
+ max: config.maxConnections, // Use configured maximum connections
109
+ min: Math.min(5, Math.floor(config.maxConnections * 0.1)), // Keep minimum connections
110
+
111
+ // === Balanced Configuration: Support both short-term queries and long-term subscriptions ===
112
+ connectionTimeoutMillis: 10000, // 10 second timeout (balanced value)
113
+ idleTimeoutMillis: 600000, // 10 minute idle cleanup (support subscriptions but not too long)
114
+ maxLifetimeSeconds: 3600, // 1 hour rotation (prevent connection leaks)
115
+
116
+ allowExitOnIdle: config.env === 'development'
117
+ });
118
+
119
+ // Add connection pool event listeners
120
+ pgPool.on('connect', (_client) => {
121
+ dbLogger.debug('📤 New connection established', {
122
+ totalCount: pgPool.totalCount,
123
+ idleCount: pgPool.idleCount,
124
+ waitingCount: pgPool.waitingCount
125
+ });
126
+ });
127
+
128
+ pgPool.on('error', (err, _client) => {
129
+ dbLogger.error('❌ Connection pool error', err, {
130
+ totalCount: pgPool.totalCount
131
+ });
132
+ });
133
+
134
+ const startTime = Date.now();
135
+
136
+ try {
137
+ // 1. Test database connection and scan table structure
138
+ systemLogger.info('Initializing database connection and scanning table structure...', {
139
+ schema: PG_SCHEMA,
140
+ databaseUrl: DATABASE_URL.replace(/:[^:]*@/, ':****@') // Hide password
141
+ });
142
+
143
+ const introspector = new DatabaseIntrospector(pgPool, PG_SCHEMA);
144
+
145
+ const isConnected = await introspector.testConnection();
146
+ if (!isConnected) {
147
+ throw new Error('Database connection failed');
148
+ }
149
+ dbLogger.info('Database connection successful', { schema: PG_SCHEMA });
150
+
151
+ const allTables = await introspector.getAllTables();
152
+ const tableNames = allTables.map((t) => t.table_name);
153
+
154
+ dbLogger.info('Table structure scan completed', {
155
+ tableCount: allTables.length,
156
+ storeTableCount: tableNames.filter((name) => name.startsWith('store_')).length,
157
+ tableNames: tableNames.slice(0, 10) // Only show first 10 table names
158
+ });
159
+
160
+ // 2. Display subscription configuration status
161
+ const subscriptionConfigData = subscriptionConfig.getConfig();
162
+ subscriptionLogger.info('📡 Subscription system configuration status', {
163
+ enableSubscriptions: subscriptionConfigData.enableSubscriptions,
164
+ capabilities: {
165
+ pgSubscriptions: subscriptionConfigData.capabilities.pgSubscriptions
166
+ },
167
+ recommendedMethod: 'pg-subscriptions',
168
+ walLevel: subscriptionConfigData.walLevel
169
+ });
170
+
171
+ // 3. Pre-generate store table information for dynamic queries
172
+ subscriptionLogger.info('Pre-generating store table information for tool queries...');
173
+ const storeTablesInfo = await generateStoreTablesInfo(pgPool);
174
+ const storeTableNames = Object.keys(storeTablesInfo);
175
+
176
+ subscriptionLogger.info(`Discovered store tables: ${storeTableNames.join(', ')}`);
177
+
178
+ // 4. Create PostGraphile configuration
179
+ const postgraphileConfigOptions: PostGraphileConfigOptions = {
180
+ port: PORT,
181
+ nodeEnv: NODE_ENV,
182
+ graphqlEndpoint: GRAPHQL_ENDPOINT,
183
+ enableSubscriptions: ENABLE_SUBSCRIPTIONS,
184
+ enableCors: ENABLE_CORS ? 'true' : 'false',
185
+ databaseUrl: DATABASE_URL,
186
+ availableTables: tableNames,
187
+ // Pass additional configuration from CLI
188
+ disableQueryLog: !config.debug, // Disable query log unless debug mode
189
+ enableQueryLog: config.debug, // Enable query log in debug mode
190
+ queryTimeout: config.queryTimeout
191
+ };
192
+
193
+ serverLogger.info('Creating PostGraphile configuration', {
194
+ endpoint: GRAPHQL_ENDPOINT,
195
+ enableCors: ENABLE_CORS,
196
+ enableSubscriptions: ENABLE_SUBSCRIPTIONS,
197
+ debug: config.debug,
198
+ disableQueryLog: !config.debug,
199
+ enableQueryLog: config.debug
200
+ });
201
+
202
+ // Use simplified configuration
203
+ const postgraphileConfig = {
204
+ ...createPostGraphileConfig(postgraphileConfigOptions),
205
+ ...subscriptionConfig.generatePostGraphileConfig()
206
+ };
207
+
208
+ // Add tools query plugin
209
+ const toolsPlugin = createUniversalSubscriptionsPlugin(storeTablesInfo);
210
+ postgraphileConfig.appendPlugins = [...(postgraphileConfig.appendPlugins || []), toolsPlugin];
211
+
212
+ // 5. Create PostGraphile middleware
213
+ console.log('🔧 Creating PostGraphile middleware...');
214
+ const postgraphileMiddleware = postgraphile(pgPool, PG_SCHEMA, {
215
+ ...postgraphileConfig
216
+ });
217
+ console.log('✅ PostGraphile middleware creation completed:', typeof postgraphileMiddleware);
218
+
219
+ // 6. Configure welcome page
220
+ const welcomeConfig: WelcomePageConfig = {
221
+ port: PORT,
222
+ graphqlEndpoint: GRAPHQL_ENDPOINT,
223
+ nodeEnv: NODE_ENV,
224
+ schema: PG_SCHEMA,
225
+ enableCors: ENABLE_CORS ? 'true' : 'false',
226
+ enableSubscriptions: ENABLE_SUBSCRIPTIONS
227
+ };
228
+
229
+ // 7. Create Express server manager
230
+ const serverManager = new EnhancedServerManager();
231
+
232
+ // 8. Create Express server
233
+ await serverManager.createEnhancedServer({
234
+ postgraphileMiddleware,
235
+ pgPool,
236
+ tableNames,
237
+ databaseUrl: DATABASE_URL,
238
+ allTables,
239
+ welcomeConfig,
240
+ postgraphileConfigOptions
241
+ });
242
+
243
+ // 9. Start Express server
244
+ await serverManager.startServer();
245
+
246
+ logPerformance('Express server startup', startTime, {
247
+ port: PORT,
248
+ tableCount: allTables.length,
249
+ storeTableCount: storeTableNames.length,
250
+ nodeEnv: NODE_ENV,
251
+ framework: 'Express',
252
+ capabilities: {
253
+ pgSubscriptions: subscriptionConfigData.capabilities.pgSubscriptions
254
+ }
255
+ });
256
+
257
+ // 10. Display usage instructions
258
+ if (NODE_ENV === 'development') {
259
+ console.log('\n' + '='.repeat(80));
260
+ console.log('📖 Quick Access (Express Architecture):');
261
+ console.log(`Visit http://localhost:${PORT}/ to view homepage`);
262
+ console.log(`Visit http://localhost:${PORT}/playground to use GraphQL Playground`);
263
+ console.log(`Visit http://localhost:${PORT}/health to check server status`);
264
+ console.log(`Visit http://localhost:${PORT}/subscription-config to get client configuration`);
265
+ console.log(`Visit http://localhost:${PORT}/subscription-docs to view configuration guide`);
266
+ console.log('='.repeat(80) + '\n');
267
+ }
268
+
269
+ // 11. Set up simple and direct shutdown handling
270
+ let isShuttingDown = false;
271
+ const quickShutdown = (signal: string) => {
272
+ if (isShuttingDown) {
273
+ systemLogger.info('⚡ Force exiting process...');
274
+ process.exit(0);
275
+ }
276
+
277
+ isShuttingDown = true;
278
+ systemLogger.info(`🛑 Received ${signal} signal, shutting down Express server...`);
279
+
280
+ // Set 1 second force exit timeout
281
+ setTimeout(() => {
282
+ systemLogger.info('⚡ Quick exit');
283
+ process.exit(0);
284
+ }, 1000);
285
+
286
+ // Try to shutdown Express server quickly
287
+ serverManager.quickShutdown().finally(() => {
288
+ process.exit(0);
289
+ });
290
+ };
291
+
292
+ process.on('SIGINT', () => quickShutdown('SIGINT'));
293
+ process.on('SIGTERM', () => quickShutdown('SIGTERM'));
294
+
295
+ // Simplified exception handling
296
+ process.on('unhandledRejection', (reason) => {
297
+ console.error('❌ Unhandled Promise rejection:', reason);
298
+ });
299
+
300
+ process.on('uncaughtException', (error) => {
301
+ console.error('❌ Uncaught exception:', error.message);
302
+ process.exit(1);
303
+ });
304
+ } catch (error) {
305
+ systemLogger.error('Failed to start Express server', error, {
306
+ databaseUrl: DATABASE_URL.replace(/:[^:]*@/, ':****@'),
307
+ schema: PG_SCHEMA,
308
+ port: PORT
309
+ });
310
+
311
+ systemLogger.info('💡 Possible causes:');
312
+ systemLogger.info('1. Database connection failed - check DATABASE_URL');
313
+ systemLogger.info(
314
+ '2. Expected table structure not found in database - ensure dubhe-indexer is running'
315
+ );
316
+ systemLogger.info('3. Permission issues - ensure database user has sufficient permissions');
317
+ systemLogger.info('4. Missing dependencies - run pnpm install');
318
+
319
+ // Display subscription configuration help
320
+ console.log('\n' + subscriptionConfig.generateDocumentation());
321
+
322
+ process.exit(1);
323
+ }
324
+ };