@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.
- package/.turbo/turbo-build.log +8 -0
- package/DUAL_POOL_CONFIG.md +188 -0
- package/Dockerfile +35 -0
- package/LICENSE +92 -0
- package/README.md +487 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/subscription-config.d.ts +80 -0
- package/dist/config/subscription-config.d.ts.map +1 -0
- package/dist/config/subscription-config.js +158 -0
- package/dist/config/subscription-config.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/all-fields-filter-plugin.d.ts +4 -0
- package/dist/plugins/all-fields-filter-plugin.d.ts.map +1 -0
- package/dist/plugins/all-fields-filter-plugin.js +132 -0
- package/dist/plugins/all-fields-filter-plugin.js.map +1 -0
- package/dist/plugins/database-introspector.d.ts +23 -0
- package/dist/plugins/database-introspector.d.ts.map +1 -0
- package/dist/plugins/database-introspector.js +96 -0
- package/dist/plugins/database-introspector.js.map +1 -0
- package/dist/plugins/enhanced-playground.d.ts +9 -0
- package/dist/plugins/enhanced-playground.d.ts.map +1 -0
- package/dist/plugins/enhanced-playground.js +113 -0
- package/dist/plugins/enhanced-playground.js.map +1 -0
- package/dist/plugins/enhanced-server-manager.d.ts +29 -0
- package/dist/plugins/enhanced-server-manager.d.ts.map +1 -0
- package/dist/plugins/enhanced-server-manager.js +262 -0
- package/dist/plugins/enhanced-server-manager.js.map +1 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +26 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/postgraphile-config.d.ts +94 -0
- package/dist/plugins/postgraphile-config.d.ts.map +1 -0
- package/dist/plugins/postgraphile-config.js +138 -0
- package/dist/plugins/postgraphile-config.js.map +1 -0
- package/dist/plugins/query-filter.d.ts +4 -0
- package/dist/plugins/query-filter.d.ts.map +1 -0
- package/dist/plugins/query-filter.js +42 -0
- package/dist/plugins/query-filter.js.map +1 -0
- package/dist/plugins/simple-naming.d.ts +4 -0
- package/dist/plugins/simple-naming.d.ts.map +1 -0
- package/dist/plugins/simple-naming.js +79 -0
- package/dist/plugins/simple-naming.js.map +1 -0
- package/dist/plugins/welcome-page.d.ts +11 -0
- package/dist/plugins/welcome-page.d.ts.map +1 -0
- package/dist/plugins/welcome-page.js +203 -0
- package/dist/plugins/welcome-page.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +265 -0
- package/dist/server.js.map +1 -0
- package/dist/universal-subscriptions.d.ts +32 -0
- package/dist/universal-subscriptions.d.ts.map +1 -0
- package/dist/universal-subscriptions.js +318 -0
- package/dist/universal-subscriptions.js.map +1 -0
- package/dist/utils/logger/index.d.ts +80 -0
- package/dist/utils/logger/index.d.ts.map +1 -0
- package/dist/utils/logger/index.js +230 -0
- package/dist/utils/logger/index.js.map +1 -0
- package/docker-compose.yml +46 -0
- package/eslint.config.mjs +3 -0
- package/package.json +78 -0
- package/src/cli.ts +232 -0
- package/src/config/subscription-config.ts +243 -0
- package/src/index.ts +11 -0
- package/src/plugins/README.md +138 -0
- package/src/plugins/all-fields-filter-plugin.ts +158 -0
- package/src/plugins/database-introspector.ts +126 -0
- package/src/plugins/enhanced-playground.ts +121 -0
- package/src/plugins/enhanced-server-manager.ts +314 -0
- package/src/plugins/index.ts +9 -0
- package/src/plugins/postgraphile-config.ts +182 -0
- package/src/plugins/query-filter.ts +50 -0
- package/src/plugins/simple-naming.ts +105 -0
- package/src/plugins/welcome-page.ts +218 -0
- package/src/server.ts +324 -0
- package/src/universal-subscriptions.ts +397 -0
- package/src/utils/logger/README.md +209 -0
- package/src/utils/logger/index.ts +275 -0
- package/sui-indexer-schema.graphql +3691 -0
- 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
|
+
};
|