@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.
- package/Dockerfile +31 -0
- package/EXPRESS_MIGRATION.md +176 -0
- package/LICENSE +92 -0
- package/README.md +908 -0
- package/dist/config/subscription-config.d.ts +47 -0
- package/dist/config/subscription-config.d.ts.map +1 -0
- package/dist/config/subscription-config.js +133 -0
- package/dist/config/subscription-config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +217 -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 +97 -0
- package/dist/plugins/enhanced-playground.js.map +1 -0
- package/dist/plugins/enhanced-server-manager.d.ts +28 -0
- package/dist/plugins/enhanced-server-manager.d.ts.map +1 -0
- package/dist/plugins/enhanced-server-manager.js +232 -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 +183 -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/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 +232 -0
- package/dist/utils/logger/index.js.map +1 -0
- package/docker-compose.yml +87 -0
- package/package.json +71 -0
- package/server.log +62 -0
- package/src/config/subscription-config.ts +186 -0
- package/src/index.ts +239 -0
- package/src/plugins/README.md +123 -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 +105 -0
- package/src/plugins/enhanced-server-manager.ts +282 -0
- package/src/plugins/index.ts +9 -0
- package/src/plugins/postgraphile-config.ts +226 -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/universal-subscriptions.ts +397 -0
- package/src/utils/logger/README.md +193 -0
- package/src/utils/logger/index.ts +315 -0
- package/sui-indexer-schema.graphql +1004 -0
- package/test-express.js +124 -0
- package/test_listen_subscription.js +121 -0
- package/test_notification.js +63 -0
- 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
|
+
}
|