@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,123 @@
1
+ # GraphQL Server Plugin Architecture
2
+
3
+ This directory contains various functional module plugins for the Sui Indexer GraphQL server, using modular design for easy management and extension.
4
+
5
+ ## 📁 Plugin Structure
6
+
7
+ ### Core Plugins
8
+
9
+ #### `database-introspector.ts` - Database Introspector
10
+ - **Function**: Scan and analyze database table structure
11
+ - **Main Class**: `DatabaseIntrospector`
12
+ - **Responsibilities**:
13
+ - Get store_* dynamic tables
14
+ - Get system tables (dubhe related)
15
+ - Get field information from table_fields
16
+ - Test database connection
17
+ - Output table structure logs
18
+
19
+ #### `welcome-page.ts` - Welcome Page Generator
20
+ - **Function**: Generate server homepage
21
+ - **Main Function**: `createWelcomePage()`
22
+ - **Responsibilities**:
23
+ - Display server status and configuration information
24
+ - Show detected data tables
25
+ - Provide navigation links and usage guides
26
+ - Responsive design and beautiful interface
27
+
28
+ #### `postgraphile-config.ts` - PostGraphile Configuration Generator
29
+ - **Function**: Create PostGraphile configuration
30
+ - **Main Function**: `createPostGraphileConfig()`
31
+ - **Responsibilities**:
32
+ - Configure GraphQL endpoints and features
33
+ - Integrate enhanced Playground
34
+ - Set up subscriptions and real-time queries
35
+ - Optimize performance parameters
36
+
37
+ #### `subscription-manager.ts` - Subscription Manager
38
+ - **Function**: Manage GraphQL subscription features
39
+ - **Main Class**: `SubscriptionManager`
40
+ - **Responsibilities**:
41
+ - Load @graphile/pg-pubsub plugin
42
+ - Configure custom subscription plugins
43
+ - Error handling and fallback solutions
44
+ - Output subscription status information
45
+
46
+ #### `server-manager.ts` - Server Manager
47
+ - **Function**: Manage HTTP and WebSocket servers
48
+ - **Main Class**: `ServerManager`
49
+ - **Responsibilities**:
50
+ - Create and configure HTTP server
51
+ - Start real-time subscription server
52
+ - Database change monitoring
53
+ - Graceful shutdown handling
54
+
55
+ #### `enhanced-playground.ts` - Enhanced GraphQL Playground
56
+ - **Function**: Provide modern GraphQL IDE
57
+ - **Main Function**: `createEnhancedPlayground()`
58
+ - **Responsibilities**:
59
+ - Visual Schema Explorer
60
+ - Code export functionality
61
+ - Modern UI interface
62
+ - Keyboard shortcuts support
63
+
64
+ ## 🔧 Usage
65
+
66
+ ### Unified Import
67
+ ```typescript
68
+ import {
69
+ DatabaseIntrospector,
70
+ createPostGraphileConfig,
71
+ SubscriptionManager,
72
+ ServerManager,
73
+ WelcomePageConfig,
74
+ } from './plugins';
75
+ ```
76
+
77
+ ### Typical Usage Flow
78
+ 1. **Database Scanning**: Use `DatabaseIntrospector` to get table structure
79
+ 2. **Subscription Configuration**: Load plugins through `SubscriptionManager`
80
+ 3. **Configuration Generation**: Create configuration using `createPostGraphileConfig`
81
+ 4. **Server Startup**: Manage server lifecycle through `ServerManager`
82
+
83
+ ## 🎯 Design Advantages
84
+
85
+ ### Modular Design
86
+ - Each plugin has a single clear responsibility
87
+ - Easy to test and maintain individually
88
+ - Supports independent upgrades and replacements
89
+
90
+ ### Type Safety
91
+ - Complete TypeScript support
92
+ - Clear interface definitions
93
+ - Compile-time error checking
94
+
95
+ ### Extensibility
96
+ - Plugin architecture easy to extend
97
+ - Supports custom plugin development
98
+ - Flexible and adjustable configuration
99
+
100
+ ### Error Handling
101
+ - Graceful error degradation
102
+ - Detailed log output
103
+ - Fault isolation protection
104
+
105
+ ## 📈 Extension Guide
106
+
107
+ ### Adding New Plugins
108
+ 1. Create new file in `plugins/` directory
109
+ 2. Export main interfaces and classes
110
+ 3. Add export in `index.ts`
111
+ 4. Update main entry file to use
112
+
113
+ ### Custom Configuration
114
+ - Pass configuration through environment variables
115
+ - Use interfaces to define configuration structure
116
+ - Support runtime dynamic configuration
117
+
118
+ ### Plugin Integration
119
+ - Follow unified error handling patterns
120
+ - Use consistent log formats
121
+ - Maintain interface compatibility
122
+
123
+ This architecture makes the GraphQL server more modular, maintainable, and provides a solid foundation for future feature extensions.
@@ -0,0 +1,158 @@
1
+ import { Plugin } from 'postgraphile';
2
+
3
+ // All fields filter plugin - ensure all fields support filtering
4
+ export const AllFieldsFilterPlugin: Plugin = (builder) => {
5
+ // Extend filter input type, add filter support for all fields
6
+ builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
7
+ const {
8
+ scope: { isPgConnectionFilter, pgIntrospection: table }
9
+ } = context;
10
+
11
+ // Only handle connection filters
12
+ if (!isPgConnectionFilter || !table || table.kind !== 'class') {
13
+ return fields;
14
+ }
15
+
16
+ const enhancedFields = { ...fields };
17
+
18
+ // Add filters for each field of the table
19
+ table.attributes.forEach((attr: any) => {
20
+ const fieldName = build.inflection.column(attr);
21
+
22
+ // Skip fields that already exist
23
+ if (enhancedFields[fieldName]) {
24
+ return;
25
+ }
26
+
27
+ // Determine filter type based on field type
28
+ let filterType;
29
+ const pgType = attr.type;
30
+
31
+ // Special handling for BigInt type
32
+ if (pgType.name === 'int8' || pgType.name === 'bigint') {
33
+ // For BigInt type, try to use StringFilter (because BigInt is represented as string in GraphQL)
34
+ filterType = build.getTypeByName('StringFilter');
35
+ } else {
36
+ // Map PostgreSQL types to GraphQL filter types
37
+ switch (pgType.category) {
38
+ case 'S': // String type
39
+ filterType = build.getTypeByName('StringFilter');
40
+ break;
41
+ case 'N': // Numeric type
42
+ if (pgType.name.includes('int')) {
43
+ filterType = build.getTypeByName('IntFilter');
44
+ } else {
45
+ filterType = build.getTypeByName('FloatFilter');
46
+ }
47
+ break;
48
+ case 'B': // Boolean type
49
+ filterType = build.getTypeByName('BooleanFilter');
50
+ break;
51
+ case 'D': // Date/time type
52
+ filterType = build.getTypeByName('DatetimeFilter');
53
+ break;
54
+ default:
55
+ // For other types, use string filter as default
56
+ filterType = build.getTypeByName('StringFilter');
57
+ }
58
+ }
59
+
60
+ // If specific filter type not found, use string filter
61
+ if (!filterType) {
62
+ filterType = build.getTypeByName('StringFilter');
63
+ }
64
+
65
+ // Add field filter
66
+ if (filterType) {
67
+ enhancedFields[fieldName] = {
68
+ type: filterType,
69
+ description: `Filter by the object's \`${attr.name}\` field.`
70
+ };
71
+ }
72
+ });
73
+
74
+ return enhancedFields;
75
+ });
76
+
77
+ // Ensure sorting options are generated for all fields
78
+ builder.hook('GraphQLEnumType:values', (values, build, context) => {
79
+ const {
80
+ scope: { isPgRowSortEnum, pgIntrospection: table }
81
+ } = context;
82
+
83
+ if (!isPgRowSortEnum || !table || table.kind !== 'class') {
84
+ return values;
85
+ }
86
+
87
+ const enhancedValues = { ...values };
88
+
89
+ // Add ASC and DESC sorting options for each field
90
+ table.attributes.forEach((attr: any) => {
91
+ const columnName = build.inflection.column(attr);
92
+ const enumName = build.inflection.constantCase(columnName);
93
+
94
+ // Add ascending sort
95
+ const ascKey = `${enumName}_ASC`;
96
+ if (!enhancedValues[ascKey]) {
97
+ enhancedValues[ascKey] = {
98
+ value: {
99
+ alias: `${attr.name.toLowerCase()}_ASC`,
100
+ specs: [[attr.name, true]]
101
+ },
102
+ description: `Sorts by ${attr.name} in ascending order.`
103
+ };
104
+ }
105
+
106
+ // Add descending sort
107
+ const descKey = `${enumName}_DESC`;
108
+ if (!enhancedValues[descKey]) {
109
+ enhancedValues[descKey] = {
110
+ value: {
111
+ alias: `${attr.name.toLowerCase()}_DESC`,
112
+ specs: [[attr.name, false]]
113
+ },
114
+ description: `Sorts by ${attr.name} in descending order.`
115
+ };
116
+ }
117
+ });
118
+
119
+ return enhancedValues;
120
+ });
121
+
122
+ // Extend condition filters to support all fields
123
+ builder.hook('GraphQLInputObjectType:fields', (fields, build, context) => {
124
+ const {
125
+ scope: { isPgCondition, pgIntrospection: table }
126
+ } = context;
127
+
128
+ if (!isPgCondition || !table || table.kind !== 'class') {
129
+ return fields;
130
+ }
131
+
132
+ const enhancedFields = { ...fields };
133
+
134
+ // Add condition filters for each field
135
+ table.attributes.forEach((attr: any) => {
136
+ const fieldName = build.inflection.column(attr);
137
+
138
+ // Skip fields that already exist
139
+ if (enhancedFields[fieldName]) {
140
+ return;
141
+ }
142
+
143
+ // Get GraphQL type
144
+ const gqlType = build.pgGetGqlTypeByTypeIdAndModifier(attr.typeId, attr.typeModifier);
145
+
146
+ if (gqlType) {
147
+ enhancedFields[fieldName] = {
148
+ type: gqlType,
149
+ description: `Checks for equality with the object's \`${attr.name}\` field.`
150
+ };
151
+ }
152
+ });
153
+
154
+ return enhancedFields;
155
+ });
156
+ };
157
+
158
+ export default AllFieldsFilterPlugin;
@@ -0,0 +1,126 @@
1
+ import { Pool } from 'pg';
2
+
3
+ // Database table structure interface
4
+ export interface TableField {
5
+ field_name: string;
6
+ field_type: string;
7
+ field_index: number | null;
8
+ is_key: boolean;
9
+ }
10
+
11
+ export interface DynamicTable {
12
+ table_name: string;
13
+ fields: TableField[];
14
+ }
15
+
16
+ // Scan database table structure
17
+ export class DatabaseIntrospector {
18
+ constructor(private pool: Pool, private schema: string = 'public') {}
19
+
20
+ // Get all dynamically created store_* tables
21
+ async getStoreTables(): Promise<string[]> {
22
+ const result = await this.pool.query(
23
+ `
24
+ SELECT table_name
25
+ FROM information_schema.tables
26
+ WHERE table_schema = $1
27
+ AND table_name LIKE 'store_%'
28
+ ORDER BY table_name
29
+ `,
30
+ [this.schema]
31
+ );
32
+
33
+ return result.rows.map((row) => row.table_name);
34
+ }
35
+
36
+ // Get system tables (dubhe related tables)
37
+ async getSystemTables(): Promise<string[]> {
38
+ const result = await this.pool.query(
39
+ `
40
+ SELECT table_name
41
+ FROM information_schema.tables
42
+ WHERE table_schema = $1
43
+ AND (table_name = 'table_fields')
44
+ ORDER BY table_name
45
+ `,
46
+ [this.schema]
47
+ );
48
+
49
+ return result.rows.map((row) => row.table_name);
50
+ }
51
+
52
+ // Get dynamic table field information from table_fields table
53
+ async getDynamicTableFields(tableName: string): Promise<TableField[]> {
54
+ // Extract table name (remove store_ prefix)
55
+ const baseTableName = tableName.replace('store_', '');
56
+
57
+ const result = await this.pool.query(
58
+ `
59
+ SELECT field_name, field_type, field_index, is_key
60
+ FROM table_fields
61
+ WHERE table_name = $1
62
+ ORDER BY is_key DESC, field_index ASC
63
+ `,
64
+ [baseTableName]
65
+ );
66
+
67
+ return result.rows;
68
+ }
69
+
70
+ // Get field information from system tables
71
+ async getSystemTableFields(tableName: string): Promise<TableField[]> {
72
+ const result = await this.pool.query(
73
+ `
74
+ SELECT
75
+ column_name as field_name,
76
+ data_type as field_type,
77
+ ordinal_position as field_index,
78
+ CASE WHEN column_name = 'entity_id' THEN true ELSE false END as is_key
79
+ FROM information_schema.columns
80
+ WHERE table_schema = $1 AND table_name = $2
81
+ ORDER BY ordinal_position
82
+ `,
83
+ [this.schema, tableName]
84
+ );
85
+
86
+ return result.rows;
87
+ }
88
+
89
+ // Get complete information for all tables
90
+ async getAllTables(): Promise<DynamicTable[]> {
91
+ const storeTables = await this.getStoreTables();
92
+ const systemTables = await this.getSystemTables();
93
+ const allTables: DynamicTable[] = [];
94
+
95
+ // Process dynamic tables
96
+ for (const tableName of storeTables) {
97
+ const fields = await this.getDynamicTableFields(tableName);
98
+ allTables.push({
99
+ table_name: tableName,
100
+ fields
101
+ });
102
+ }
103
+
104
+ // Process system tables
105
+ for (const tableName of systemTables) {
106
+ const fields = await this.getSystemTableFields(tableName);
107
+ allTables.push({
108
+ table_name: tableName,
109
+ fields
110
+ });
111
+ }
112
+
113
+ return allTables;
114
+ }
115
+
116
+ // Test database connection
117
+ async testConnection(): Promise<boolean> {
118
+ try {
119
+ await this.pool.query('SELECT NOW() as current_time');
120
+ return true;
121
+ } catch (error) {
122
+ console.error('Database connection test failed:', error);
123
+ return false;
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,105 @@
1
+ // Enhanced GraphQL Playground plugin
2
+ // Provides better visual experience based on GraphiQL and Explorer plugin
3
+
4
+ import type { IncomingMessage, ServerResponse } from 'http';
5
+
6
+ export interface PlaygroundOptions {
7
+ url: string;
8
+ subscriptionUrl?: string;
9
+ title?: string;
10
+ subtitle?: string;
11
+ }
12
+
13
+ export function createEnhancedPlayground(
14
+ options: PlaygroundOptions
15
+ ): (req: IncomingMessage, res: ServerResponse, config?: any) => string {
16
+ return (_req: IncomingMessage, _res: ServerResponse, _config?: any) => {
17
+ return `
18
+
19
+ <!--
20
+ * Copyright (c) 2021 GraphQL Contributors
21
+ * All rights reserved.
22
+ *
23
+ * This source code is licensed under the license found in the
24
+ * LICENSE file in the root directory of this source tree.
25
+ -->
26
+ <!doctype html>
27
+ <html lang="en">
28
+ <head>
29
+ <title>Dubhe Playground</title>
30
+ <style>
31
+ body {
32
+ height: 100%;
33
+ margin: 0;
34
+ width: 100%;
35
+ overflow: hidden;
36
+ }
37
+
38
+ #graphiql {
39
+ height: 100vh;
40
+ }
41
+
42
+ :global(.graphiql-explorer-root > div:first-child) {
43
+ /* Remove the 2nd horizontal scroll bar */
44
+ overflow: hidden !important;
45
+ }
46
+ </style>
47
+ <!--
48
+ This GraphiQL example depends on Promise and fetch, which are available in
49
+ modern browsers, but can be "polyfilled" for older browsers.
50
+ GraphiQL itself depends on React DOM.
51
+ If you do not want to rely on a CDN, you can host these files locally or
52
+ include them directly in your favored resource bundler.
53
+ -->
54
+ <script
55
+ crossorigin
56
+ src="https://unpkg.com/react@18/umd/react.production.min.js"
57
+ ></script>
58
+ <script
59
+ crossorigin
60
+ src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
61
+ ></script>
62
+ <!--
63
+ These two files can be found in the npm module, however you may wish to
64
+ copy them directly into your environment, or perhaps include them in your
65
+ favored resource bundler.
66
+ -->
67
+ <script
68
+ src="https://unpkg.com/graphiql/graphiql.min.js"
69
+ type="application/javascript"
70
+ ></script>
71
+ <link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
72
+ <!--
73
+ These are imports for the GraphIQL Explorer plugin.
74
+ -->
75
+ <script
76
+ src="https://unpkg.com/@graphiql/plugin-explorer/dist/index.umd.js"
77
+ crossorigin
78
+ ></script>
79
+
80
+ <link
81
+ rel="stylesheet"
82
+ href="https://unpkg.com/@graphiql/plugin-explorer/dist/style.css"
83
+ />
84
+ </head>
85
+
86
+ <body>
87
+ <div id="graphiql">Loading...</div>
88
+ <script>
89
+ const root = ReactDOM.createRoot(document.getElementById('graphiql'));
90
+ const fetcher = GraphiQL.createFetcher(${JSON.stringify(options)});
91
+ const explorerPlugin = GraphiQLPluginExplorer.explorerPlugin({
92
+ showAttribution: true,
93
+ });
94
+ root.render(
95
+ React.createElement(GraphiQL, {
96
+ fetcher,
97
+ defaultEditorToolsVisibility: true,
98
+ plugins: [explorerPlugin],
99
+ }),
100
+ );
101
+ </script>
102
+ </body>
103
+ </html>`;
104
+ };
105
+ }