@0xobelisk/graphql-server 1.2.0-pre.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -0
- package/DUAL_POOL_CONFIG.md +188 -0
- package/Dockerfile +35 -0
- package/LICENSE +92 -0
- package/README.md +487 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/subscription-config.d.ts +80 -0
- package/dist/config/subscription-config.d.ts.map +1 -0
- package/dist/config/subscription-config.js +158 -0
- package/dist/config/subscription-config.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/all-fields-filter-plugin.d.ts +4 -0
- package/dist/plugins/all-fields-filter-plugin.d.ts.map +1 -0
- package/dist/plugins/all-fields-filter-plugin.js +132 -0
- package/dist/plugins/all-fields-filter-plugin.js.map +1 -0
- package/dist/plugins/database-introspector.d.ts +23 -0
- package/dist/plugins/database-introspector.d.ts.map +1 -0
- package/dist/plugins/database-introspector.js +96 -0
- package/dist/plugins/database-introspector.js.map +1 -0
- package/dist/plugins/enhanced-playground.d.ts +9 -0
- package/dist/plugins/enhanced-playground.d.ts.map +1 -0
- package/dist/plugins/enhanced-playground.js +113 -0
- package/dist/plugins/enhanced-playground.js.map +1 -0
- package/dist/plugins/enhanced-server-manager.d.ts +29 -0
- package/dist/plugins/enhanced-server-manager.d.ts.map +1 -0
- package/dist/plugins/enhanced-server-manager.js +262 -0
- package/dist/plugins/enhanced-server-manager.js.map +1 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +26 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/postgraphile-config.d.ts +94 -0
- package/dist/plugins/postgraphile-config.d.ts.map +1 -0
- package/dist/plugins/postgraphile-config.js +138 -0
- package/dist/plugins/postgraphile-config.js.map +1 -0
- package/dist/plugins/query-filter.d.ts +4 -0
- package/dist/plugins/query-filter.d.ts.map +1 -0
- package/dist/plugins/query-filter.js +42 -0
- package/dist/plugins/query-filter.js.map +1 -0
- package/dist/plugins/simple-naming.d.ts +4 -0
- package/dist/plugins/simple-naming.d.ts.map +1 -0
- package/dist/plugins/simple-naming.js +79 -0
- package/dist/plugins/simple-naming.js.map +1 -0
- package/dist/plugins/welcome-page.d.ts +11 -0
- package/dist/plugins/welcome-page.d.ts.map +1 -0
- package/dist/plugins/welcome-page.js +203 -0
- package/dist/plugins/welcome-page.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +265 -0
- package/dist/server.js.map +1 -0
- package/dist/universal-subscriptions.d.ts +32 -0
- package/dist/universal-subscriptions.d.ts.map +1 -0
- package/dist/universal-subscriptions.js +318 -0
- package/dist/universal-subscriptions.js.map +1 -0
- package/dist/utils/logger/index.d.ts +80 -0
- package/dist/utils/logger/index.d.ts.map +1 -0
- package/dist/utils/logger/index.js +230 -0
- package/dist/utils/logger/index.js.map +1 -0
- package/docker-compose.yml +46 -0
- package/eslint.config.mjs +3 -0
- package/package.json +78 -0
- package/src/cli.ts +232 -0
- package/src/config/subscription-config.ts +243 -0
- package/src/index.ts +11 -0
- package/src/plugins/README.md +138 -0
- package/src/plugins/all-fields-filter-plugin.ts +158 -0
- package/src/plugins/database-introspector.ts +126 -0
- package/src/plugins/enhanced-playground.ts +121 -0
- package/src/plugins/enhanced-server-manager.ts +314 -0
- package/src/plugins/index.ts +9 -0
- package/src/plugins/postgraphile-config.ts +182 -0
- package/src/plugins/query-filter.ts +50 -0
- package/src/plugins/simple-naming.ts +105 -0
- package/src/plugins/welcome-page.ts +218 -0
- package/src/server.ts +324 -0
- package/src/universal-subscriptions.ts +397 -0
- package/src/utils/logger/README.md +209 -0
- package/src/utils/logger/index.ts +275 -0
- package/sui-indexer-schema.graphql +3691 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,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,121 @@
|
|
|
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
|
+
// This content is sourced from https://github.com/graphql/graphiql/blob/main/examples/graphiql-cdn/index.html
|
|
18
|
+
return `<!--
|
|
19
|
+
* Copyright (c) 2025 GraphQL Contributors
|
|
20
|
+
* All rights reserved.
|
|
21
|
+
*
|
|
22
|
+
* This source code is licensed under the license found in the
|
|
23
|
+
* LICENSE file in the root directory of this source tree.
|
|
24
|
+
-->
|
|
25
|
+
<!doctype html>
|
|
26
|
+
<html lang="en">
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="UTF-8" />
|
|
29
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
30
|
+
<title>GraphiQL 5 with React 19 and GraphiQL Explorer</title>
|
|
31
|
+
<style>
|
|
32
|
+
body {
|
|
33
|
+
margin: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#graphiql {
|
|
37
|
+
height: 100dvh;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.loading {
|
|
41
|
+
height: 100%;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
font-size: 4rem;
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
48
|
+
<link rel="stylesheet" href="https://esm.sh/graphiql/dist/style.css" />
|
|
49
|
+
<link
|
|
50
|
+
rel="stylesheet"
|
|
51
|
+
href="https://esm.sh/@graphiql/plugin-explorer/dist/style.css"
|
|
52
|
+
/>
|
|
53
|
+
<!-- Note: the ?standalone flag bundles the module along with all of its 'dependencies', excluding peerDependencies', into a single JavaScript file. -->
|
|
54
|
+
<script type="importmap">
|
|
55
|
+
{
|
|
56
|
+
"imports": {
|
|
57
|
+
"react": "https://esm.sh/react@19.1.0",
|
|
58
|
+
"react/jsx-runtime": "https://esm.sh/react@19.1.0/jsx-runtime",
|
|
59
|
+
|
|
60
|
+
"react-dom": "https://esm.sh/react-dom@19.1.0",
|
|
61
|
+
"react-dom/client": "https://esm.sh/react-dom@19.1.0/client",
|
|
62
|
+
|
|
63
|
+
"graphiql": "https://esm.sh/graphiql?standalone&external=react,react-dom,@graphiql/react,graphql",
|
|
64
|
+
"@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer?standalone&external=react,@graphiql/react,graphql",
|
|
65
|
+
"@graphiql/react": "https://esm.sh/@graphiql/react?standalone&external=react,react-dom,graphql",
|
|
66
|
+
|
|
67
|
+
"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit?standalone&external=graphql",
|
|
68
|
+
"graphql": "https://esm.sh/graphql@16.11.0"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
<script type="module">
|
|
73
|
+
// Import React and ReactDOM
|
|
74
|
+
import React from 'react';
|
|
75
|
+
import ReactDOM from 'react-dom/client';
|
|
76
|
+
// Import GraphiQL and the Explorer plugin
|
|
77
|
+
import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
|
|
78
|
+
import { createGraphiQLFetcher } from '@graphiql/toolkit';
|
|
79
|
+
import { explorerPlugin } from '@graphiql/plugin-explorer';
|
|
80
|
+
|
|
81
|
+
import createJSONWorker from 'https://esm.sh/monaco-editor/esm/vs/language/json/json.worker.js?worker';
|
|
82
|
+
import createGraphQLWorker from 'https://esm.sh/monaco-graphql/esm/graphql.worker.js?worker';
|
|
83
|
+
import createEditorWorker from 'https://esm.sh/monaco-editor/esm/vs/editor/editor.worker.js?worker';
|
|
84
|
+
|
|
85
|
+
globalThis.MonacoEnvironment = {
|
|
86
|
+
getWorker(_workerId, label) {
|
|
87
|
+
console.info('MonacoEnvironment.getWorker', { label });
|
|
88
|
+
switch (label) {
|
|
89
|
+
case 'json':
|
|
90
|
+
return createJSONWorker();
|
|
91
|
+
case 'graphql':
|
|
92
|
+
return createGraphQLWorker();
|
|
93
|
+
}
|
|
94
|
+
return createEditorWorker();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const fetcher = createGraphiQLFetcher(${JSON.stringify(options)});
|
|
99
|
+
const plugins = [HISTORY_PLUGIN, explorerPlugin()];
|
|
100
|
+
|
|
101
|
+
function App() {
|
|
102
|
+
return React.createElement(GraphiQL, {
|
|
103
|
+
fetcher,
|
|
104
|
+
plugins,
|
|
105
|
+
defaultEditorToolsVisibility: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const container = document.getElementById('graphiql');
|
|
110
|
+
const root = ReactDOM.createRoot(container);
|
|
111
|
+
root.render(React.createElement(App));
|
|
112
|
+
</script>
|
|
113
|
+
</head>
|
|
114
|
+
<body>
|
|
115
|
+
<div id="graphiql">
|
|
116
|
+
<div class="loading">Loading…</div>
|
|
117
|
+
</div>
|
|
118
|
+
</body>
|
|
119
|
+
</html>`;
|
|
120
|
+
};
|
|
121
|
+
}
|