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

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 (79) hide show
  1. package/Dockerfile +31 -0
  2. package/EXPRESS_MIGRATION.md +176 -0
  3. package/LICENSE +92 -0
  4. package/README.md +908 -0
  5. package/dist/config/subscription-config.d.ts +47 -0
  6. package/dist/config/subscription-config.d.ts.map +1 -0
  7. package/dist/config/subscription-config.js +133 -0
  8. package/dist/config/subscription-config.js.map +1 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +217 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/plugins/all-fields-filter-plugin.d.ts +4 -0
  14. package/dist/plugins/all-fields-filter-plugin.d.ts.map +1 -0
  15. package/dist/plugins/all-fields-filter-plugin.js +132 -0
  16. package/dist/plugins/all-fields-filter-plugin.js.map +1 -0
  17. package/dist/plugins/database-introspector.d.ts +23 -0
  18. package/dist/plugins/database-introspector.d.ts.map +1 -0
  19. package/dist/plugins/database-introspector.js +96 -0
  20. package/dist/plugins/database-introspector.js.map +1 -0
  21. package/dist/plugins/enhanced-playground.d.ts +9 -0
  22. package/dist/plugins/enhanced-playground.d.ts.map +1 -0
  23. package/dist/plugins/enhanced-playground.js +97 -0
  24. package/dist/plugins/enhanced-playground.js.map +1 -0
  25. package/dist/plugins/enhanced-server-manager.d.ts +28 -0
  26. package/dist/plugins/enhanced-server-manager.d.ts.map +1 -0
  27. package/dist/plugins/enhanced-server-manager.js +232 -0
  28. package/dist/plugins/enhanced-server-manager.js.map +1 -0
  29. package/dist/plugins/index.d.ts +9 -0
  30. package/dist/plugins/index.d.ts.map +1 -0
  31. package/dist/plugins/index.js +26 -0
  32. package/dist/plugins/index.js.map +1 -0
  33. package/dist/plugins/postgraphile-config.d.ts +94 -0
  34. package/dist/plugins/postgraphile-config.d.ts.map +1 -0
  35. package/dist/plugins/postgraphile-config.js +183 -0
  36. package/dist/plugins/postgraphile-config.js.map +1 -0
  37. package/dist/plugins/query-filter.d.ts +4 -0
  38. package/dist/plugins/query-filter.d.ts.map +1 -0
  39. package/dist/plugins/query-filter.js +42 -0
  40. package/dist/plugins/query-filter.js.map +1 -0
  41. package/dist/plugins/simple-naming.d.ts +4 -0
  42. package/dist/plugins/simple-naming.d.ts.map +1 -0
  43. package/dist/plugins/simple-naming.js +79 -0
  44. package/dist/plugins/simple-naming.js.map +1 -0
  45. package/dist/plugins/welcome-page.d.ts +11 -0
  46. package/dist/plugins/welcome-page.d.ts.map +1 -0
  47. package/dist/plugins/welcome-page.js +203 -0
  48. package/dist/plugins/welcome-page.js.map +1 -0
  49. package/dist/universal-subscriptions.d.ts +32 -0
  50. package/dist/universal-subscriptions.d.ts.map +1 -0
  51. package/dist/universal-subscriptions.js +318 -0
  52. package/dist/universal-subscriptions.js.map +1 -0
  53. package/dist/utils/logger/index.d.ts +80 -0
  54. package/dist/utils/logger/index.d.ts.map +1 -0
  55. package/dist/utils/logger/index.js +232 -0
  56. package/dist/utils/logger/index.js.map +1 -0
  57. package/docker-compose.yml +87 -0
  58. package/package.json +71 -0
  59. package/server.log +62 -0
  60. package/src/config/subscription-config.ts +186 -0
  61. package/src/index.ts +239 -0
  62. package/src/plugins/README.md +123 -0
  63. package/src/plugins/all-fields-filter-plugin.ts +158 -0
  64. package/src/plugins/database-introspector.ts +126 -0
  65. package/src/plugins/enhanced-playground.ts +105 -0
  66. package/src/plugins/enhanced-server-manager.ts +282 -0
  67. package/src/plugins/index.ts +9 -0
  68. package/src/plugins/postgraphile-config.ts +226 -0
  69. package/src/plugins/query-filter.ts +50 -0
  70. package/src/plugins/simple-naming.ts +105 -0
  71. package/src/plugins/welcome-page.ts +218 -0
  72. package/src/universal-subscriptions.ts +397 -0
  73. package/src/utils/logger/README.md +193 -0
  74. package/src/utils/logger/index.ts +315 -0
  75. package/sui-indexer-schema.graphql +1004 -0
  76. package/test-express.js +124 -0
  77. package/test_listen_subscription.js +121 -0
  78. package/test_notification.js +63 -0
  79. package/tsconfig.json +28 -0
@@ -0,0 +1,282 @@
1
+ // Express server manager - using Express framework and PostgreSQL subscriptions
2
+
3
+ import express, { Express, Request, Response } from 'express';
4
+ import { createServer, Server as HttpServer } from 'http';
5
+ import cors from 'cors';
6
+ import { Pool } from 'pg';
7
+ import { enhanceHttpServerWithSubscriptions } from 'postgraphile';
8
+ import { subscriptionConfig, SubscriptionConfig } from '../config/subscription-config';
9
+ import { systemLogger, serverLogger, logExpress } from '../utils/logger';
10
+ import { createWelcomePage, WelcomePageConfig } from './welcome-page';
11
+ import { createPlaygroundHtml, PostGraphileConfigOptions } from './postgraphile-config';
12
+ import type { DynamicTable } from './database-introspector';
13
+
14
+ export interface EnhancedServerConfig {
15
+ postgraphileMiddleware: any;
16
+ pgPool: Pool;
17
+ tableNames: string[];
18
+ databaseUrl: string;
19
+ allTables: DynamicTable[];
20
+ welcomeConfig: WelcomePageConfig;
21
+ postgraphileConfigOptions: PostGraphileConfigOptions;
22
+ }
23
+
24
+ export class EnhancedServerManager {
25
+ private config: SubscriptionConfig;
26
+ private app: Express | null = null;
27
+ private httpServer: HttpServer | null = null;
28
+
29
+ constructor() {
30
+ this.config = subscriptionConfig.getConfig();
31
+ }
32
+
33
+ // Create Express application
34
+ private createExpressApp(serverConfig: EnhancedServerConfig): Express {
35
+ const { postgraphileMiddleware, allTables, welcomeConfig, postgraphileConfigOptions } =
36
+ serverConfig;
37
+
38
+ const app = express();
39
+
40
+ // Middleware configuration
41
+ app.use(
42
+ cors({
43
+ origin: '*',
44
+ methods: ['GET', 'POST', 'OPTIONS'],
45
+ allowedHeaders: ['Content-Type', 'Authorization']
46
+ })
47
+ );
48
+
49
+ // Request logging middleware
50
+ app.use((req: Request, res: Response, next) => {
51
+ const startTime = Date.now();
52
+
53
+ res.on('finish', () => {
54
+ logExpress(req.method, req.path, res.statusCode, startTime, {
55
+ userAgent: req.get('user-agent')?.substring(0, 50)
56
+ });
57
+ });
58
+
59
+ next();
60
+ });
61
+
62
+ // Route configuration
63
+
64
+ // Root path - welcome page
65
+ app.get('/', (req: Request, res: Response) => {
66
+ res.set('Content-Type', 'text/html; charset=utf-8');
67
+ res.send(createWelcomePage(allTables, welcomeConfig));
68
+ });
69
+
70
+ // GraphQL Playground
71
+ app.get('/playground', (req: Request, res: Response) => {
72
+ res.set('Content-Type', 'text/html; charset=utf-8');
73
+ res.send(createPlaygroundHtml(postgraphileConfigOptions));
74
+ });
75
+
76
+ // Redirect old GraphiQL paths
77
+ app.get('/graphiql*', (req: Request, res: Response) => {
78
+ serverLogger.info('Redirecting old GraphiQL path', {
79
+ from: req.path,
80
+ to: '/playground'
81
+ });
82
+ res.redirect(301, '/playground');
83
+ });
84
+
85
+ // Health check endpoint
86
+ app.get('/health', (req: Request, res: Response) => {
87
+ res.json({
88
+ status: 'healthy',
89
+ subscriptions: this.getSubscriptionStatus(),
90
+ timestamp: new Date().toISOString()
91
+ });
92
+ });
93
+
94
+ // Subscription configuration endpoint
95
+ app.get('/subscription-config', (req: Request, res: Response) => {
96
+ res.json(subscriptionConfig.generateClientConfig());
97
+ });
98
+
99
+ // Configuration documentation endpoint
100
+ app.get('/subscription-docs', (req: Request, res: Response) => {
101
+ res.set('Content-Type', 'text/plain');
102
+ res.send(subscriptionConfig.generateDocumentation());
103
+ });
104
+
105
+ // PostGraphile middleware - mount at root path, let PostGraphile handle routing itself
106
+ app.use((req: Request, res: Response, next) => {
107
+ // Check if PostGraphile middleware exists
108
+ if (!postgraphileMiddleware) {
109
+ console.error('❌ PostGraphile middleware is null!');
110
+ if (req.path.startsWith('/graphql')) {
111
+ res.status(500).json({
112
+ error: 'PostGraphile middleware not properly initialized'
113
+ });
114
+ return;
115
+ }
116
+ next();
117
+ return;
118
+ }
119
+
120
+ try {
121
+ postgraphileMiddleware(req, res, next);
122
+ } catch (error) {
123
+ console.error('❌ PostGraphile middleware execution error:', error);
124
+ if (req.path.startsWith('/graphql')) {
125
+ res.status(500).json({
126
+ error: 'PostGraphile execution error',
127
+ details: error instanceof Error ? error.message : String(error)
128
+ });
129
+ return;
130
+ }
131
+ next();
132
+ }
133
+ });
134
+
135
+ // Error handling middleware
136
+ app.use((err: Error, req: Request, res: Response, next: express.NextFunction) => {
137
+ serverLogger.error('Express error handling', err, {
138
+ url: req.originalUrl,
139
+ method: req.method,
140
+ userAgent: req.get('user-agent')?.substring(0, 50)
141
+ });
142
+ res.status(500).send('Internal Server Error');
143
+ });
144
+
145
+ return app;
146
+ }
147
+
148
+ // Create and configure HTTP server
149
+ async createEnhancedServer(serverConfig: EnhancedServerConfig): Promise<HttpServer> {
150
+ const { postgraphileMiddleware } = serverConfig;
151
+
152
+ // Create Express application
153
+ this.app = this.createExpressApp(serverConfig);
154
+
155
+ // Create HTTP server
156
+ this.httpServer = createServer(this.app);
157
+
158
+ // Enable PostgreSQL subscriptions and WebSocket support
159
+ if (this.config.capabilities.pgSubscriptions) {
160
+ enhanceHttpServerWithSubscriptions(this.httpServer, postgraphileMiddleware, {
161
+ // Enable WebSocket transport
162
+ graphqlRoute: '/graphql'
163
+ });
164
+ systemLogger.info('✅ PostgreSQL subscriptions and WebSocket enabled', {
165
+ pgSubscriptions: this.config.capabilities.pgSubscriptions,
166
+ webSocket: true
167
+ });
168
+ }
169
+
170
+ serverLogger.info('🚀 Express server creation completed', {
171
+ framework: 'Express',
172
+ graphqlPort: this.config.graphqlPort,
173
+ capabilities: {
174
+ pgSubscriptions: this.config.capabilities.pgSubscriptions
175
+ },
176
+ recommendedMethod: 'pg-subscriptions'
177
+ });
178
+
179
+ return this.httpServer;
180
+ }
181
+
182
+ // Start server
183
+ async startServer(): Promise<void> {
184
+ if (!this.httpServer) {
185
+ throw new Error('Server not created, please call createEnhancedServer() first');
186
+ }
187
+
188
+ return new Promise((resolve, reject) => {
189
+ this.httpServer!.listen(this.config.graphqlPort, (err?: Error) => {
190
+ if (err) {
191
+ reject(err);
192
+ return;
193
+ }
194
+
195
+ this.logServerStatus();
196
+ resolve();
197
+ });
198
+ });
199
+ }
200
+
201
+ // Log server status
202
+ private logServerStatus() {
203
+ const clientConfig = subscriptionConfig.generateClientConfig();
204
+
205
+ serverLogger.info('🎉 Express GraphQL server started successfully!', {
206
+ port: this.config.graphqlPort,
207
+ framework: 'Express',
208
+ endpoints: {
209
+ home: `http://localhost:${this.config.graphqlPort}/`,
210
+ playground: `http://localhost:${this.config.graphqlPort}/playground`,
211
+ graphql: clientConfig.graphqlEndpoint,
212
+ subscription: clientConfig.subscriptionEndpoint,
213
+ health: `http://localhost:${this.config.graphqlPort}/health`,
214
+ config: `http://localhost:${this.config.graphqlPort}/subscription-config`,
215
+ docs: `http://localhost:${this.config.graphqlPort}/subscription-docs`
216
+ }
217
+ });
218
+
219
+ // Display main access links
220
+ console.log('\n' + '🌟'.repeat(30));
221
+ console.log('🏠 Homepage: ' + `http://localhost:${this.config.graphqlPort}/`);
222
+ console.log('🎮 Playground: ' + `http://localhost:${this.config.graphqlPort}/playground`);
223
+ console.log('🔗 GraphQL: ' + clientConfig.graphqlEndpoint);
224
+ console.log('📡 WebSocket: ' + clientConfig.subscriptionEndpoint);
225
+ console.log('🌟'.repeat(30) + '\n');
226
+ }
227
+
228
+ // Get subscription status
229
+ private getSubscriptionStatus() {
230
+ return {
231
+ enabled: this.config.capabilities.pgSubscriptions,
232
+ method: 'pg-subscriptions',
233
+ config: subscriptionConfig.generateClientConfig()
234
+ };
235
+ }
236
+
237
+ // Quick shutdown
238
+ async quickShutdown(): Promise<void> {
239
+ systemLogger.info('🛑 Starting quick shutdown of Express server...');
240
+
241
+ if (this.httpServer) {
242
+ this.httpServer.close();
243
+ systemLogger.info('✅ HTTP server closed');
244
+ }
245
+
246
+ systemLogger.info('🎯 Express server quick shutdown completed');
247
+ }
248
+
249
+ // Graceful shutdown
250
+ async gracefulShutdown(pgPool: Pool): Promise<void> {
251
+ systemLogger.info('🛑 Starting graceful shutdown of Express server...');
252
+
253
+ const shutdownPromises: Promise<void>[] = [];
254
+
255
+ // Close HTTP server
256
+ if (this.httpServer) {
257
+ shutdownPromises.push(
258
+ new Promise((resolve) => {
259
+ this.httpServer!.close(() => {
260
+ systemLogger.info('✅ HTTP server closed');
261
+ resolve();
262
+ });
263
+ })
264
+ );
265
+ }
266
+
267
+ // Close database connection pool
268
+ shutdownPromises.push(
269
+ pgPool.end().then(() => {
270
+ systemLogger.info('✅ Database connection pool closed');
271
+ })
272
+ );
273
+
274
+ try {
275
+ await Promise.all(shutdownPromises);
276
+ systemLogger.info('🎯 Express server graceful shutdown completed');
277
+ } catch (error) {
278
+ systemLogger.error('❌ Error occurred during shutdown process', error);
279
+ throw error;
280
+ }
281
+ }
282
+ }
@@ -0,0 +1,9 @@
1
+ // 插件统一导出入口
2
+ export * from './database-introspector';
3
+ export * from './welcome-page';
4
+ export * from './postgraphile-config';
5
+ export * from './query-filter';
6
+ export * from './simple-naming';
7
+ export * from './all-fields-filter-plugin';
8
+ export * from './enhanced-server-manager';
9
+ export * from './enhanced-playground';
@@ -0,0 +1,226 @@
1
+ import { QueryFilterPlugin } from './query-filter';
2
+ import { SimpleNamingPlugin } from './simple-naming';
3
+ import { AllFieldsFilterPlugin } from './all-fields-filter-plugin';
4
+ import { createEnhancedPlayground } from './enhanced-playground';
5
+ import ConnectionFilterPlugin from 'postgraphile-plugin-connection-filter';
6
+ import PgSimplifyInflectorPlugin from '@graphile-contrib/pg-simplify-inflector';
7
+ import { makePluginHook } from 'postgraphile';
8
+ import PgPubSub from '@graphile/pg-pubsub';
9
+
10
+ export interface PostGraphileConfigOptions {
11
+ port: string | number;
12
+ nodeEnv: string;
13
+ graphqlEndpoint: string;
14
+ enableSubscriptions: string;
15
+ enableCors: string;
16
+ databaseUrl: string;
17
+ availableTables: string[];
18
+ }
19
+
20
+ // 创建 PostGraphile 配置
21
+ export function createPostGraphileConfig(options: PostGraphileConfigOptions) {
22
+ const { port, nodeEnv, graphqlEndpoint, enableSubscriptions, enableCors, availableTables } =
23
+ options;
24
+
25
+ // 构建GraphQL和WebSocket端点URL
26
+ const baseUrl = `http://localhost:${port}`;
27
+ const graphqlUrl = `${baseUrl}${graphqlEndpoint}`;
28
+ const subscriptionUrl =
29
+ enableSubscriptions === 'true' ? `ws://localhost:${port}${graphqlEndpoint}` : undefined;
30
+
31
+ // 创建插件钩子以支持WebSocket和订阅
32
+ const pluginHook = makePluginHook([PgPubSub]);
33
+
34
+ const config = {
35
+ // 基础配置 - 关闭默认GraphiQL
36
+ graphiql: false,
37
+ enhanceGraphiql: false,
38
+ showErrorStack: nodeEnv === 'development',
39
+ extendedErrors: nodeEnv === 'development' ? ['hint', 'detail', 'errcode'] : [],
40
+
41
+ // 功能配置 - 启用订阅
42
+ subscriptions: enableSubscriptions === 'true',
43
+ live: enableSubscriptions === 'true', // 启用live功能以支持订阅
44
+ enableQueryBatching: true,
45
+ enableCors: enableCors === 'true',
46
+
47
+ // 添加插件钩子以支持WebSocket
48
+ pluginHook,
49
+
50
+ // 禁用所有mutation功能 - 只保留查询和订阅
51
+ disableDefaultMutations: true,
52
+
53
+ // Schema 配置
54
+ dynamicJson: true,
55
+ setofFunctionsContainNulls: false,
56
+ ignoreRBAC: false,
57
+ ignoreIndexes: true,
58
+
59
+ // 日志控制配置
60
+ // 通过环境变量控制SQL查询日志: DISABLE_QUERY_LOG=true 禁用查询日志
61
+ disableQueryLog:
62
+ process.env.DISABLE_QUERY_LOG === 'true' ||
63
+ (nodeEnv === 'production' && process.env.ENABLE_QUERY_LOG !== 'true'),
64
+
65
+ // 启用查询执行计划解释(仅开发环境)
66
+ allowExplain: nodeEnv === 'development',
67
+
68
+ // 监控PostgreSQL变化(仅开发环境)
69
+ watchPg: nodeEnv === 'development',
70
+
71
+ // GraphQL查询超时设置
72
+ queryTimeout: parseInt(process.env.QUERY_TIMEOUT || '30000'),
73
+
74
+ // GraphQL 端点 - 明确指定路由
75
+ graphqlRoute: graphqlEndpoint,
76
+ graphiqlRoute: '/graphiql', // GraphiQL界面路由
77
+
78
+ // 添加自定义插件
79
+ appendPlugins: [
80
+ QueryFilterPlugin, // 必须在SimpleNamingPlugin之前执行
81
+ PgSimplifyInflectorPlugin, // 简化字段名,去掉ByXxxAndYyy后缀
82
+ SimpleNamingPlugin, // 已修复字段丢失问题
83
+ ConnectionFilterPlugin,
84
+ AllFieldsFilterPlugin
85
+ ],
86
+
87
+ // Connection Filter 插件的高级配置选项
88
+ graphileBuildOptions: {
89
+ // 启用所有支持的操作符
90
+ connectionFilterAllowedOperators: [
91
+ 'isNull',
92
+ 'equalTo',
93
+ 'notEqualTo',
94
+ 'distinctFrom',
95
+ 'notDistinctFrom',
96
+ 'lessThan',
97
+ 'lessThanOrEqualTo',
98
+ 'greaterThan',
99
+ 'greaterThanOrEqualTo',
100
+ 'in',
101
+ 'notIn',
102
+ 'like',
103
+ 'notLike',
104
+ 'ilike',
105
+ 'notIlike',
106
+ 'similarTo',
107
+ 'notSimilarTo',
108
+ 'includes',
109
+ 'notIncludes',
110
+ 'includesInsensitive',
111
+ 'notIncludesInsensitive',
112
+ 'startsWith',
113
+ 'notStartsWith',
114
+ 'startsWithInsensitive',
115
+ 'notStartsWithInsensitive',
116
+ 'endsWith',
117
+ 'notEndsWith',
118
+ 'endsWithInsensitive',
119
+ 'notEndsWithInsensitive'
120
+ ],
121
+
122
+ // 支持所有字段类型的过滤 - 明确允许所有类型
123
+ connectionFilterAllowedFieldTypes: [
124
+ 'String',
125
+ 'Int',
126
+ 'Float',
127
+ 'Boolean',
128
+ 'ID',
129
+ 'Date',
130
+ 'Time',
131
+ 'Datetime',
132
+ 'JSON',
133
+ 'BigInt'
134
+ ],
135
+
136
+ // 启用逻辑操作符 (and, or, not)
137
+ connectionFilterLogicalOperators: true,
138
+
139
+ // 启用关系过滤
140
+ connectionFilterRelations: true,
141
+
142
+ // 启用计算列过滤
143
+ connectionFilterComputedColumns: true,
144
+
145
+ // 启用数组过滤
146
+ connectionFilterArrays: true,
147
+
148
+ // 启用函数过滤
149
+ connectionFilterSetofFunctions: true,
150
+
151
+ // 允许空输入和空对象输入
152
+ connectionFilterAllowNullInput: true,
153
+ connectionFilterAllowEmptyObjectInput: true
154
+ },
155
+
156
+ // 只包含检测到的表
157
+ includeExtensionResources: false,
158
+
159
+ // 排除不需要的表
160
+ ignoreTable: (tableName: string) => {
161
+ // 如果没有检测到任何表,允许所有表
162
+ if (availableTables.length === 0) {
163
+ return false;
164
+ }
165
+ // 否则只包含检测到的表
166
+ return !availableTables.includes(tableName);
167
+ },
168
+
169
+ // 导出 schema(开发环境)
170
+ exportGqlSchemaPath: nodeEnv === 'development' ? 'sui-indexer-schema.graphql' : undefined
171
+ };
172
+
173
+ // 如果启用订阅,添加额外的PostgreSQL订阅配置
174
+ if (enableSubscriptions === 'true') {
175
+ return {
176
+ ...config,
177
+ // 使用专用数据库连接用于订阅
178
+ ownerConnectionString: options.databaseUrl,
179
+
180
+ // WebSocket配置
181
+ websocketMiddlewares: [],
182
+
183
+ // PostgreSQL设置 - 为订阅优化
184
+ pgSettings: {
185
+ statement_timeout: '30s',
186
+ // 为订阅设置适当的事务隔离级别
187
+ default_transaction_isolation: 'read committed'
188
+ },
189
+
190
+ // 连接失败时重试
191
+ retryOnInitFail: true,
192
+
193
+ // 性能优化
194
+ pgDefaultRole: undefined,
195
+ jwtSecret: undefined,
196
+
197
+ // 开发环境的额外配置
198
+ ...(nodeEnv === 'development' && {
199
+ queryCache: true,
200
+ allowExplain: true
201
+ })
202
+ };
203
+ }
204
+
205
+ return config;
206
+ }
207
+
208
+ // 导出增强版playground的HTML生成器
209
+ export function createPlaygroundHtml(options: PostGraphileConfigOptions): string {
210
+ const { port, graphqlEndpoint, enableSubscriptions, availableTables } = options;
211
+
212
+ // 构建GraphQL和WebSocket端点URL
213
+ const baseUrl = `http://localhost:${port}`;
214
+ const graphqlUrl = `${baseUrl}${graphqlEndpoint}`;
215
+ const subscriptionUrl =
216
+ enableSubscriptions === 'true' ? `ws://localhost:${port}${graphqlEndpoint}` : undefined;
217
+
218
+ return createEnhancedPlayground({
219
+ url: graphqlUrl,
220
+ subscriptionUrl,
221
+ title: 'Sui Indexer GraphQL Playground',
222
+ subtitle: `强大的GraphQL API | 已发现 ${availableTables.length} 个表 | ${
223
+ enableSubscriptions === 'true' ? '支持实时订阅' : '实时订阅已禁用'
224
+ }`
225
+ })(null as any, null as any, {});
226
+ }
@@ -0,0 +1,50 @@
1
+ // PostGraphile plugin for filtering queries
2
+ import { Plugin } from 'postgraphile';
3
+
4
+ // Query filter plugin - only keep useful table-related queries
5
+ export const QueryFilterPlugin: Plugin = (builder) => {
6
+ // Filter query fields
7
+ builder.hook('GraphQLObjectType:fields', (fields, build, context) => {
8
+ const {
9
+ scope: { isRootQuery }
10
+ } = context;
11
+
12
+ if (!isRootQuery) {
13
+ return fields;
14
+ }
15
+
16
+ // Define query types to keep
17
+ const allowedQueries = new Set<string>();
18
+
19
+ // Get all table-related queries
20
+ Object.keys(fields).forEach((fieldName) => {
21
+ // Keep PostGraphile required system fields
22
+ if (['query', 'nodeId', 'node'].includes(fieldName)) {
23
+ allowedQueries.add(fieldName);
24
+ }
25
+
26
+ // Keep store table-related queries
27
+ if (fieldName.match(/^(allStore|store)/i)) {
28
+ allowedQueries.add(fieldName);
29
+ }
30
+
31
+ // Keep table_fields table queries
32
+ if (fieldName.match(/^(allTable|table)/i)) {
33
+ allowedQueries.add(fieldName);
34
+ }
35
+ });
36
+
37
+ // Filter fields, only keep allowed queries
38
+ const filteredFields: typeof fields = {};
39
+ Object.keys(fields).forEach((fieldName) => {
40
+ if (allowedQueries.has(fieldName)) {
41
+ filteredFields[fieldName] = fields[fieldName];
42
+ }
43
+ });
44
+
45
+ // console.log('🔍 Filtered query fields:', Object.keys(filteredFields));
46
+ return filteredFields;
47
+ });
48
+ };
49
+
50
+ export default QueryFilterPlugin;
@@ -0,0 +1,105 @@
1
+ import { Plugin } from 'postgraphile';
2
+
3
+ export const SimpleNamingPlugin: Plugin = (builder) => {
4
+ // Rename query fields
5
+ builder.hook('GraphQLObjectType:fields', (fields, build, context) => {
6
+ const {
7
+ scope: { isRootQuery }
8
+ } = context;
9
+
10
+ if (!isRootQuery) {
11
+ return fields;
12
+ }
13
+
14
+ // Create renamed field mapping
15
+ const renamedFields: typeof fields = {};
16
+ const originalFieldNames = Object.keys(fields);
17
+
18
+ console.log('🔍 Original field list:', originalFieldNames);
19
+
20
+ // For tracking rename mapping
21
+ const renameMap: Record<string, string> = {};
22
+
23
+ originalFieldNames.forEach((fieldName) => {
24
+ let newFieldName = fieldName;
25
+
26
+ // Remove "all" prefix, but keep system fields
27
+ if (
28
+ fieldName.startsWith('all') &&
29
+ !['allRows', 'allTableFields'].includes(fieldName) // Extend reserved list
30
+ ) {
31
+ // allStoreAccounts -> storeAccounts
32
+ // allStoreEncounters -> storeEncounters
33
+ newFieldName = fieldName.replace(/^all/, '');
34
+ // First letter to lowercase, maintain camelCase
35
+ if (newFieldName.length > 0) {
36
+ newFieldName = newFieldName.charAt(0).toLowerCase() + newFieldName.slice(1);
37
+ }
38
+ }
39
+
40
+ // Remove "store" prefix (note lowercase s, because it's already processed above)
41
+ if (newFieldName.startsWith('store') && newFieldName !== 'store') {
42
+ // storeAccounts -> accounts
43
+ // storeAccount -> account
44
+ // storeEncounters -> encounters
45
+ // storeEncounter -> encounter
46
+ const withoutStore = newFieldName.replace(/^store/, '');
47
+ // First letter to lowercase, maintain camelCase
48
+ if (withoutStore.length > 0) {
49
+ const finalName = withoutStore.charAt(0).toLowerCase() + withoutStore.slice(1);
50
+
51
+ // Check if field name conflict will occur
52
+ if (!renamedFields[finalName] && !originalFieldNames.includes(finalName)) {
53
+ newFieldName = finalName;
54
+ }
55
+ // If conflict, keep original name (remove all but keep store)
56
+ }
57
+ }
58
+
59
+ // Check if final field name will conflict
60
+ if (renamedFields[newFieldName]) {
61
+ console.warn(`⚠️ Field name conflict: ${newFieldName}, keeping original name ${fieldName}`);
62
+ newFieldName = fieldName; // Keep original name to avoid conflict
63
+ }
64
+
65
+ renameMap[fieldName] = newFieldName;
66
+ renamedFields[newFieldName] = fields[fieldName];
67
+ });
68
+
69
+ const renamedCount = Object.entries(renameMap).filter(
70
+ ([old, newName]) => old !== newName
71
+ ).length;
72
+ const finalFieldNames = Object.keys(renamedFields);
73
+
74
+ console.log('🔄 Field rename statistics:', {
75
+ 'Original field count': originalFieldNames.length,
76
+ 'Final field count': finalFieldNames.length,
77
+ 'Renamed field count': renamedCount
78
+ });
79
+
80
+ if (renamedCount > 0) {
81
+ console.log(
82
+ '📝 Rename mapping:',
83
+ Object.entries(renameMap)
84
+ .filter(([old, newName]) => old !== newName)
85
+ .reduce((acc, [old, newName]) => ({ ...acc, [old]: newName }), {})
86
+ );
87
+ }
88
+
89
+ // Ensure field count is not lost
90
+ if (finalFieldNames.length !== originalFieldNames.length) {
91
+ console.error(
92
+ '❌ Fields lost! Original:',
93
+ originalFieldNames.length,
94
+ 'Final:',
95
+ finalFieldNames.length
96
+ );
97
+ // If fields are lost, return original fields to avoid breakage
98
+ return fields;
99
+ }
100
+
101
+ return renamedFields;
102
+ });
103
+ };
104
+
105
+ export default SimpleNamingPlugin;