@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,314 @@
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
+ private pgPool: Pool | null = null;
29
+
30
+ constructor() {
31
+ this.config = subscriptionConfig.getConfig();
32
+ }
33
+
34
+ // Create Express application
35
+ private createExpressApp(serverConfig: EnhancedServerConfig): Express {
36
+ const { postgraphileMiddleware, allTables, welcomeConfig, postgraphileConfigOptions } =
37
+ serverConfig;
38
+
39
+ const app = express();
40
+
41
+ // Middleware configuration
42
+ app.use(
43
+ cors({
44
+ origin: '*',
45
+ methods: ['GET', 'POST', 'OPTIONS'],
46
+ allowedHeaders: ['Content-Type', 'Authorization']
47
+ })
48
+ );
49
+
50
+ // Request logging middleware
51
+ app.use((req: Request, res: Response, next) => {
52
+ const startTime = Date.now();
53
+
54
+ res.on('finish', () => {
55
+ logExpress(req.method, req.path, res.statusCode, startTime, {
56
+ userAgent: req.get('user-agent')?.substring(0, 50)
57
+ });
58
+ });
59
+
60
+ next();
61
+ });
62
+
63
+ // Route configuration
64
+
65
+ // Root path - welcome page
66
+ app.get('/', (req: Request, res: Response) => {
67
+ res.set('Content-Type', 'text/html; charset=utf-8');
68
+ res.send(createWelcomePage(allTables, welcomeConfig));
69
+ });
70
+
71
+ // GraphQL Playground
72
+ app.get('/playground', (req: Request, res: Response) => {
73
+ res.set('Content-Type', 'text/html; charset=utf-8');
74
+ res.send(createPlaygroundHtml(postgraphileConfigOptions));
75
+ });
76
+
77
+ // Redirect old GraphiQL paths
78
+ app.get('/graphiql*', (req: Request, res: Response) => {
79
+ serverLogger.info('Redirecting old GraphiQL path', {
80
+ from: req.path,
81
+ to: '/playground'
82
+ });
83
+ res.redirect(301, '/playground');
84
+ });
85
+
86
+ // Health check endpoint
87
+ app.get('/health', (req: Request, res: Response) => {
88
+ res.json({
89
+ status: 'healthy',
90
+ subscriptions: this.getSubscriptionStatus(),
91
+ timestamp: new Date().toISOString()
92
+ });
93
+ });
94
+
95
+ // Subscription configuration endpoint
96
+ app.get('/subscription-config', (req: Request, res: Response) => {
97
+ res.json(subscriptionConfig.generateClientConfig());
98
+ });
99
+
100
+ // Configuration documentation endpoint
101
+ app.get('/subscription-docs', (req: Request, res: Response) => {
102
+ res.set('Content-Type', 'text/plain');
103
+ res.send(subscriptionConfig.generateDocumentation());
104
+ });
105
+
106
+ // Add connection pool status endpoint
107
+ app.get('/pool-status', (req, res) => {
108
+ if (this.pgPool) {
109
+ const poolStatus = {
110
+ totalCount: this.pgPool.totalCount,
111
+ idleCount: this.pgPool.idleCount,
112
+ waitingCount: this.pgPool.waitingCount,
113
+ maxConnections: this.pgPool.options.max || 'Not set',
114
+ minConnections: this.pgPool.options.min || 'Not set'
115
+ };
116
+
117
+ res.json({
118
+ status: 'ok',
119
+ connectionPool: poolStatus,
120
+ strategy: 'single-pool-unified',
121
+ operations: ['query', 'mutation', 'subscription'],
122
+ timestamp: new Date().toISOString(),
123
+ uptime: process.uptime(),
124
+ memory: process.memoryUsage()
125
+ });
126
+ } else {
127
+ res.status(503).json({
128
+ status: 'error',
129
+ message: 'Connection pool not available'
130
+ });
131
+ }
132
+ });
133
+
134
+ // PostGraphile middleware - mount at root path, let PostGraphile handle routing itself
135
+ app.use((req: Request, res: Response, next) => {
136
+ // Check if PostGraphile middleware exists
137
+ if (!postgraphileMiddleware) {
138
+ console.error('❌ PostGraphile middleware is null!');
139
+ if (req.path.startsWith('/graphql')) {
140
+ res.status(500).json({
141
+ error: 'PostGraphile middleware not properly initialized'
142
+ });
143
+ return;
144
+ }
145
+ next();
146
+ return;
147
+ }
148
+
149
+ try {
150
+ postgraphileMiddleware(req, res, next);
151
+ } catch (error) {
152
+ console.error('❌ PostGraphile middleware execution error:', error);
153
+ if (req.path.startsWith('/graphql')) {
154
+ res.status(500).json({
155
+ error: 'PostGraphile execution error',
156
+ details: error instanceof Error ? error.message : String(error)
157
+ });
158
+ return;
159
+ }
160
+ next();
161
+ }
162
+ });
163
+
164
+ // Error handling middleware
165
+ app.use((err: Error, req: Request, res: Response, _next: express.NextFunction) => {
166
+ serverLogger.error('Express error handling', err, {
167
+ url: req.originalUrl,
168
+ method: req.method,
169
+ userAgent: req.get('user-agent')?.substring(0, 50)
170
+ });
171
+ res.status(500).send('Internal Server Error');
172
+ });
173
+
174
+ return app;
175
+ }
176
+
177
+ // Create and configure HTTP server
178
+ async createEnhancedServer(serverConfig: EnhancedServerConfig): Promise<HttpServer> {
179
+ const { postgraphileMiddleware, pgPool } = serverConfig;
180
+
181
+ // Store pool references for monitoring
182
+ this.pgPool = pgPool;
183
+
184
+ // Create Express application
185
+ this.app = this.createExpressApp(serverConfig);
186
+
187
+ // Create HTTP server
188
+ this.httpServer = createServer(this.app);
189
+
190
+ // Enable PostgreSQL subscriptions and WebSocket support
191
+ if (this.config.capabilities.pgSubscriptions) {
192
+ enhanceHttpServerWithSubscriptions(this.httpServer, postgraphileMiddleware, {
193
+ // Enable WebSocket transport
194
+ graphqlRoute: '/graphql'
195
+ });
196
+ systemLogger.info('✅ PostgreSQL subscriptions and WebSocket enabled', {
197
+ pgSubscriptions: this.config.capabilities.pgSubscriptions,
198
+ webSocket: true
199
+ });
200
+ }
201
+
202
+ serverLogger.info('🚀 Express server creation completed', {
203
+ framework: 'Express',
204
+ graphqlPort: this.config.graphqlPort,
205
+ capabilities: {
206
+ pgSubscriptions: this.config.capabilities.pgSubscriptions
207
+ },
208
+ recommendedMethod: 'pg-subscriptions'
209
+ });
210
+
211
+ return this.httpServer;
212
+ }
213
+
214
+ // Start server
215
+ async startServer(): Promise<void> {
216
+ if (!this.httpServer) {
217
+ throw new Error('Server not created, please call createEnhancedServer() first');
218
+ }
219
+
220
+ return new Promise((resolve, reject) => {
221
+ this.httpServer!.listen(this.config.graphqlPort, (err?: Error) => {
222
+ if (err) {
223
+ reject(err);
224
+ return;
225
+ }
226
+
227
+ this.logServerStatus();
228
+ resolve();
229
+ });
230
+ });
231
+ }
232
+
233
+ // Log server status
234
+ private logServerStatus() {
235
+ const clientConfig = subscriptionConfig.generateClientConfig();
236
+
237
+ serverLogger.info('🎉 Express GraphQL server started successfully!', {
238
+ port: this.config.graphqlPort,
239
+ framework: 'Express',
240
+ endpoints: {
241
+ home: `http://localhost:${this.config.graphqlPort}/`,
242
+ playground: `http://localhost:${this.config.graphqlPort}/playground`,
243
+ graphql: clientConfig.graphqlEndpoint,
244
+ subscription: clientConfig.subscriptionEndpoint,
245
+ health: `http://localhost:${this.config.graphqlPort}/health`,
246
+ config: `http://localhost:${this.config.graphqlPort}/subscription-config`,
247
+ docs: `http://localhost:${this.config.graphqlPort}/subscription-docs`
248
+ }
249
+ });
250
+
251
+ // Display main access links
252
+ console.log('\n' + '🌟'.repeat(30));
253
+ console.log('🏠 Homepage: ' + `http://localhost:${this.config.graphqlPort}/`);
254
+ console.log('🎮 Playground: ' + `http://localhost:${this.config.graphqlPort}/playground`);
255
+ console.log('🔗 GraphQL: ' + clientConfig.graphqlEndpoint);
256
+ console.log('📡 WebSocket: ' + clientConfig.subscriptionEndpoint);
257
+ console.log('🌟'.repeat(30) + '\n');
258
+ }
259
+
260
+ // Get subscription status
261
+ private getSubscriptionStatus() {
262
+ return {
263
+ enabled: this.config.capabilities.pgSubscriptions,
264
+ method: 'pg-subscriptions',
265
+ config: subscriptionConfig.generateClientConfig()
266
+ };
267
+ }
268
+
269
+ // Quick shutdown
270
+ async quickShutdown(): Promise<void> {
271
+ systemLogger.info('🛑 Starting quick shutdown of Express server...');
272
+
273
+ if (this.httpServer) {
274
+ this.httpServer.close();
275
+ systemLogger.info('✅ HTTP server closed');
276
+ }
277
+
278
+ systemLogger.info('🎯 Express server quick shutdown completed');
279
+ }
280
+
281
+ // Graceful shutdown
282
+ async gracefulShutdown(pgPool: Pool): Promise<void> {
283
+ systemLogger.info('🛑 Starting graceful shutdown of Express server...');
284
+
285
+ const shutdownPromises: Promise<void>[] = [];
286
+
287
+ // Close HTTP server
288
+ if (this.httpServer) {
289
+ shutdownPromises.push(
290
+ new Promise((resolve) => {
291
+ this.httpServer!.close(() => {
292
+ systemLogger.info('✅ HTTP server closed');
293
+ resolve();
294
+ });
295
+ })
296
+ );
297
+ }
298
+
299
+ // Close database connection pool
300
+ shutdownPromises.push(
301
+ pgPool.end().then(() => {
302
+ systemLogger.info('✅ Database connection pool closed');
303
+ })
304
+ );
305
+
306
+ try {
307
+ await Promise.all(shutdownPromises);
308
+ systemLogger.info('🎯 Express server graceful shutdown completed');
309
+ } catch (error) {
310
+ systemLogger.error('❌ Error occurred during shutdown process', error);
311
+ throw error;
312
+ }
313
+ }
314
+ }
@@ -0,0 +1,9 @@
1
+ // Unified export entry point for plugins
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,182 @@
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
+ // Additional configuration from CLI
19
+ disableQueryLog: boolean;
20
+ enableQueryLog: boolean;
21
+ queryTimeout: number;
22
+ }
23
+
24
+ // Create PostGraphile configuration
25
+ export function createPostGraphileConfig(options: PostGraphileConfigOptions) {
26
+ const { port, nodeEnv, graphqlEndpoint, enableSubscriptions, enableCors, availableTables } =
27
+ options;
28
+
29
+ // Build GraphQL and WebSocket endpoint URLs
30
+ const baseUrl = `http://localhost:${port}`;
31
+ const _graphqlUrl = `${baseUrl}${graphqlEndpoint}`;
32
+ const _subscriptionUrl =
33
+ enableSubscriptions === 'true' ? `ws://localhost:${port}${graphqlEndpoint}` : undefined;
34
+
35
+ // Create plugin hook to support WebSocket and subscriptions
36
+ const pluginHook = makePluginHook([PgPubSub]);
37
+
38
+ const config = {
39
+ // Basic configuration - disable default GraphiQL
40
+ graphiql: false,
41
+ enhanceGraphiql: false,
42
+ showErrorStack: nodeEnv === 'development',
43
+ extendedErrors: nodeEnv === 'development' ? ['hint', 'detail', 'errcode'] : [],
44
+
45
+ // Feature configuration - enable subscriptions
46
+ subscriptions: enableSubscriptions === 'true',
47
+ live: enableSubscriptions === 'true', // Enable live functionality to support subscriptions
48
+ enableQueryBatching: true,
49
+ enableCors: enableCors === 'true',
50
+
51
+ // Add plugin hook to support WebSocket
52
+ pluginHook,
53
+
54
+ // Disable all mutation functionality - only keep queries and subscriptions
55
+ disableDefaultMutations: true,
56
+
57
+ // Schema configuration
58
+ dynamicJson: true,
59
+ setofFunctionsContainNulls: false,
60
+ ignoreRBAC: false,
61
+ ignoreIndexes: true,
62
+
63
+ // Log control configuration
64
+ // Control SQL query logs through CLI parameters
65
+ disableQueryLog:
66
+ options.disableQueryLog || (nodeEnv === 'production' && !options.enableQueryLog),
67
+
68
+ // Enable query execution plan explanation (development environment only)
69
+ allowExplain: nodeEnv === 'development',
70
+
71
+ // Monitor PostgreSQL changes (development environment only)
72
+ watchPg: nodeEnv === 'development',
73
+
74
+ // GraphQL query timeout setting
75
+ queryTimeout: options.queryTimeout,
76
+
77
+ // GraphQL endpoint - explicitly specify route
78
+ graphqlRoute: graphqlEndpoint,
79
+ graphiqlRoute: '/graphiql', // GraphiQL interface route
80
+
81
+ // Add custom plugins
82
+ appendPlugins: [
83
+ QueryFilterPlugin, // Must execute before SimpleNamingPlugin
84
+ PgSimplifyInflectorPlugin, // Simplify field names, remove ByXxxAndYyy suffixes
85
+ SimpleNamingPlugin, // Fixed field loss issue
86
+ ConnectionFilterPlugin,
87
+ AllFieldsFilterPlugin
88
+ ],
89
+
90
+ // Advanced configuration options for Connection Filter plugin
91
+ graphileBuildOptions: {
92
+ // Enable logical operators (and, or, not)
93
+ connectionFilterLogicalOperators: true,
94
+
95
+ // Enable relationship filtering
96
+ connectionFilterRelations: true,
97
+
98
+ // Enable computed column filtering
99
+ connectionFilterComputedColumns: true,
100
+
101
+ // Enable array filtering
102
+ connectionFilterArrays: true,
103
+
104
+ // Enable function filtering
105
+ connectionFilterSetofFunctions: true,
106
+
107
+ // Allow null input and empty object input
108
+ connectionFilterAllowNullInput: true,
109
+ connectionFilterAllowEmptyObjectInput: true
110
+ },
111
+
112
+ // Only include detected tables
113
+ includeExtensionResources: false,
114
+
115
+ // Exclude unnecessary tables
116
+ ignoreTable: (tableName: string) => {
117
+ // If no tables detected, allow all tables
118
+ if (availableTables.length === 0) {
119
+ return false;
120
+ }
121
+ // Otherwise only include detected tables
122
+ return !availableTables.includes(tableName);
123
+ },
124
+
125
+ // Export schema (development environment)
126
+ exportGqlSchemaPath: nodeEnv === 'development' ? 'sui-indexer-schema.graphql' : undefined
127
+ };
128
+
129
+ // If subscriptions are enabled, add additional PostgreSQL subscription configuration
130
+ if (enableSubscriptions === 'true') {
131
+ return {
132
+ ...config,
133
+ // Use dedicated subscription connection pool
134
+ ownerConnectionString: options.databaseUrl,
135
+
136
+ // WebSocket configuration
137
+ websocketMiddlewares: [],
138
+
139
+ // PostgreSQL settings - optimized for long-running subscriptions
140
+ pgSettings: {
141
+ statement_timeout: '0', // No timeout for subscription queries
142
+ idle_in_transaction_session_timeout: '0', // Allow long transactions
143
+ default_transaction_isolation: 'read committed'
144
+ },
145
+
146
+ // Retry on connection failure
147
+ retryOnInitFail: true,
148
+
149
+ // Performance optimization for subscriptions
150
+ pgDefaultRole: undefined,
151
+ jwtSecret: undefined,
152
+
153
+ // Additional configuration for development environment
154
+ ...(nodeEnv === 'development' && {
155
+ queryCache: false, // Disable cache for real-time data
156
+ allowExplain: true
157
+ })
158
+ };
159
+ }
160
+
161
+ return config;
162
+ }
163
+
164
+ // Export enhanced playground HTML generator
165
+ export function createPlaygroundHtml(options: PostGraphileConfigOptions): string {
166
+ const { graphqlEndpoint, enableSubscriptions, availableTables } = options;
167
+
168
+ // Use relative URLs so playground connects to the same domain as the server
169
+ const graphqlUrl = graphqlEndpoint;
170
+ const subscriptionUrl = enableSubscriptions === 'true' ? graphqlEndpoint : undefined;
171
+
172
+ return createEnhancedPlayground({
173
+ url: graphqlUrl,
174
+ subscriptionUrl,
175
+ title: 'Sui Indexer GraphQL Playground',
176
+ subtitle: `Powerful GraphQL API | ${availableTables.length} tables discovered | ${
177
+ enableSubscriptions === 'true'
178
+ ? 'Real-time subscriptions supported'
179
+ : 'Real-time subscriptions disabled'
180
+ }`
181
+ })(null as any, null as any, {});
182
+ }
@@ -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;