@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,87 @@
|
|
|
1
|
+
version: "3.8"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
postgres:
|
|
5
|
+
image: postgres:15-alpine
|
|
6
|
+
environment:
|
|
7
|
+
POSTGRES_DB: dubhe_graphql
|
|
8
|
+
POSTGRES_USER: postgres
|
|
9
|
+
POSTGRES_PASSWORD: postgres
|
|
10
|
+
ports:
|
|
11
|
+
- "5432:5432"
|
|
12
|
+
volumes:
|
|
13
|
+
- postgres_data:/var/lib/postgresql/data
|
|
14
|
+
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
17
|
+
interval: 5s
|
|
18
|
+
timeout: 5s
|
|
19
|
+
retries: 5
|
|
20
|
+
|
|
21
|
+
redis:
|
|
22
|
+
image: redis:7-alpine
|
|
23
|
+
ports:
|
|
24
|
+
- "6379:6379"
|
|
25
|
+
command: redis-server --appendonly yes
|
|
26
|
+
volumes:
|
|
27
|
+
- redis_data:/data
|
|
28
|
+
healthcheck:
|
|
29
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
30
|
+
interval: 5s
|
|
31
|
+
timeout: 3s
|
|
32
|
+
retries: 5
|
|
33
|
+
|
|
34
|
+
graphql-server:
|
|
35
|
+
build:
|
|
36
|
+
context: .
|
|
37
|
+
dockerfile: Dockerfile
|
|
38
|
+
ports:
|
|
39
|
+
- "4000:4000"
|
|
40
|
+
environment:
|
|
41
|
+
DATABASE_URL: postgres://postgres:postgres@postgres:5432/dubhe_graphql
|
|
42
|
+
NODE_ENV: production
|
|
43
|
+
PORT: 4000
|
|
44
|
+
GRAPHQL_ENDPOINT: /graphql
|
|
45
|
+
PG_SCHEMA: public
|
|
46
|
+
ENABLE_CORS: "true"
|
|
47
|
+
ENABLE_SUBSCRIPTIONS: "true"
|
|
48
|
+
WATCH_PG: "false"
|
|
49
|
+
depends_on:
|
|
50
|
+
postgres:
|
|
51
|
+
condition: service_healthy
|
|
52
|
+
redis:
|
|
53
|
+
condition: service_healthy
|
|
54
|
+
restart: unless-stopped
|
|
55
|
+
healthcheck:
|
|
56
|
+
test:
|
|
57
|
+
[
|
|
58
|
+
"CMD",
|
|
59
|
+
"wget",
|
|
60
|
+
"--no-verbose",
|
|
61
|
+
"--tries=1",
|
|
62
|
+
"--spider",
|
|
63
|
+
"http://localhost:4000/graphql",
|
|
64
|
+
]
|
|
65
|
+
interval: 30s
|
|
66
|
+
timeout: 10s
|
|
67
|
+
retries: 3
|
|
68
|
+
|
|
69
|
+
# 可选:添加 pgAdmin 用于数据库管理
|
|
70
|
+
pgadmin:
|
|
71
|
+
image: dpage/pgadmin4:latest
|
|
72
|
+
environment:
|
|
73
|
+
PGADMIN_DEFAULT_EMAIL: admin@dubhe.com
|
|
74
|
+
PGADMIN_DEFAULT_PASSWORD: admin
|
|
75
|
+
ports:
|
|
76
|
+
- "5050:80"
|
|
77
|
+
volumes:
|
|
78
|
+
- pgadmin_data:/var/lib/pgadmin
|
|
79
|
+
depends_on:
|
|
80
|
+
- postgres
|
|
81
|
+
profiles:
|
|
82
|
+
- tools
|
|
83
|
+
|
|
84
|
+
volumes:
|
|
85
|
+
postgres_data:
|
|
86
|
+
redis_data:
|
|
87
|
+
pgadmin_data:
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@0xobelisk/graphql-server",
|
|
3
|
+
"version": "1.2.0-pre.24",
|
|
4
|
+
"description": "Tookit for interacting with dubhe graphql server",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"graphql",
|
|
9
|
+
"postgraphile",
|
|
10
|
+
"sui-indexer",
|
|
11
|
+
"database-adapter",
|
|
12
|
+
"websocket",
|
|
13
|
+
"subscription",
|
|
14
|
+
"postgresql",
|
|
15
|
+
"dynamic"
|
|
16
|
+
],
|
|
17
|
+
"author": "Dubhe Team",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@graphile-contrib/pg-simplify-inflector": "^6.1.0",
|
|
21
|
+
"@graphile/pg-pubsub": "^4.14.1",
|
|
22
|
+
"@graphile/subscriptions-lds": "^4.14.1",
|
|
23
|
+
"apollo-server-express": "^3.13.0",
|
|
24
|
+
"cors": "^2.8.5",
|
|
25
|
+
"dotenv": "^16.3.1",
|
|
26
|
+
"express": "^4.21.2",
|
|
27
|
+
"graphile-utils": "^4.14.1",
|
|
28
|
+
"graphql": "^15.8.0",
|
|
29
|
+
"graphql-subscriptions": "^2.0.0",
|
|
30
|
+
"graphql-ws": "^5.16.2",
|
|
31
|
+
"node-fetch": "^3.3.2",
|
|
32
|
+
"pg": "^8.11.3",
|
|
33
|
+
"pino": "^8.16.2",
|
|
34
|
+
"pino-pretty": "^10.2.3",
|
|
35
|
+
"postgraphile": "^4.13.0",
|
|
36
|
+
"postgraphile-plugin-connection-filter": "^2.3.0",
|
|
37
|
+
"ws": "^8.14.2"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/cors": "^2.8.18",
|
|
41
|
+
"@types/express": "^4.17.23",
|
|
42
|
+
"@types/node": "^20.8.10",
|
|
43
|
+
"@types/pg": "^8.10.7",
|
|
44
|
+
"@types/ws": "^8.5.8",
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
|
46
|
+
"@typescript-eslint/parser": "^6.9.1",
|
|
47
|
+
"eslint": "^8.52.0",
|
|
48
|
+
"jest": "^29.7.0",
|
|
49
|
+
"nodemon": "^3.1.10",
|
|
50
|
+
"ts-node": "^10.9.1",
|
|
51
|
+
"tsx": "^3.12.10",
|
|
52
|
+
"typescript": "^5.2.2"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18.0.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"dev": "nodemon --exec ts-node src/index.ts",
|
|
59
|
+
"dev:node": "node --loader tsx/esm src/index.ts",
|
|
60
|
+
"dev:ts-node": "ts-node-esm src/index.ts",
|
|
61
|
+
"build": "tsc",
|
|
62
|
+
"start": "node dist/index.js",
|
|
63
|
+
"test": "jest",
|
|
64
|
+
"test:subscription": "node test-subscription.js",
|
|
65
|
+
"test:enhanced": "node test_enhanced_subscriptions.js",
|
|
66
|
+
"test:all": "node test_all_subscriptions.js",
|
|
67
|
+
"lint": "eslint src/**/*.ts",
|
|
68
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
69
|
+
"type-check": "tsc --noEmit"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/server.log
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [system]: 🚀 启动 Sui Indexer GraphQL 服务器...[39m [90m{"component":"system","nodeVersion":"v18.20.4","platform":"darwin"}[39m
|
|
2
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [system]: 正在初始化数据库连接和扫描表结构...[39m [90m{"component":"system","schema":"public","databaseUrl":"postgres://postgres:****@127.0.0.1:5432/postgres"}[39m
|
|
3
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: 数据库连接成功[39m [90m{"component":"database","schema":"public"}[39m
|
|
4
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: 扫描表结构完成[39m [90m{"component":"database","tableCount":5,"storeTableCount":4,"tableNames":["store_accounts","store_encounter","store_map_config","store_position","table_fields"]}[39m
|
|
5
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [subscription]: 启动统一实时引擎 (Live Queries + Native WebSocket)[39m [90m{"component":"subscription","enableSubscriptions":"true","availableTableCount":5,"storeTablesCount":4}[39m
|
|
6
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [system]: 🚀 统一实时引擎启动[39m [90m{"component":"system","port":4001,"enableLiveQueries":true,"enableNativeWebSocket":true,"tablesCount":5}[39m
|
|
7
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: 创建 PostGraphile 配置[39m [90m{"component":"server","endpoint":"/graphql","enableCors":"true","enableSubscriptions":"true"}[39m
|
|
8
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [subscription]: 创建简化订阅插件[39m [90m{"component":"subscription","totalTables":5,"storeTables":4,"storeTableNames":["store_accounts","store_encounter","store_map_config","store_position"]}[39m
|
|
9
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: 初始化服务器管理器[39m [90m{"component":"server","port":4000,"endpoint":"/graphql","subscriptions":"true","realtimePort":"4001"}[39m
|
|
10
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [subscription]: PostGraphile Live Queries已启用,无需额外配置[39m [90m{"component":"subscription"}[39m
|
|
11
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: 🚀 Sui Indexer GraphQL 服务器启动成功![39m [90m{"component":"server"}[39m
|
|
12
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: 服务器地址[39m [90m{"component":"server","url":"http://localhost:4000"}[39m
|
|
13
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: GraphQL API[39m [90m{"component":"server","url":"http://localhost:4000/graphql"}[39m
|
|
14
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: 增强版 GraphQL Playground[39m [90m{"component":"server","url":"http://localhost:4000/playground","features":["现代化界面","Schema Explorer","代码导出"],"note":"旧路径 /graphiql 会自动重定向到 /playground"}[39m
|
|
15
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: WebSocket 订阅[39m [90m{"component":"server","url":"ws://localhost:4000/graphql"}[39m
|
|
16
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [server]: 服务器配置[39m [90m{"component":"server","environment":"development","schema":"public","totalTables":5,"storeTables":4,"systemTables":1,"cors":"启用","subscriptions":"启用"}[39m
|
|
17
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [system]: 💡 访问根路径查看详细信息和使用指南[39m [90m{"component":"system"}[39m
|
|
18
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [system]: 按 Ctrl+C 停止服务器[39m [90m{"component":"system"}[39m
|
|
19
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [performance]: 服务器启动 completed[39m [90m{"component":"performance","duration":"81ms","port":4000,"tableCount":5,"nodeEnv":"development"}[39m
|
|
20
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: 开始监听数据库通道[39m [90m{"component":"database","channel":"table_change_store_accounts"}[39m
|
|
21
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: 开始监听数据库通道[39m [90m{"component":"database","channel":"table_change_store_encounter"}[39m
|
|
22
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: 开始监听数据库通道[39m [90m{"component":"database","channel":"table_change_store_map_config"}[39m
|
|
23
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: 开始监听数据库通道[39m [90m{"component":"database","channel":"table_change_store_position"}[39m
|
|
24
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: 开始监听数据库通道[39m [90m{"component":"database","channel":"table_change_table_fields"}[39m
|
|
25
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [database]: ✅ 数据库变更监听器设置完成[39m [90m{"component":"database"}[39m
|
|
26
|
+
🔍 原始字段列表: [
|
|
27
|
+
'query',
|
|
28
|
+
'nodeId',
|
|
29
|
+
'node',
|
|
30
|
+
'allStoreAccounts',
|
|
31
|
+
'allStoreEncounters',
|
|
32
|
+
'allStoreMapConfigs',
|
|
33
|
+
'allStorePositions',
|
|
34
|
+
'allTableFields',
|
|
35
|
+
'storeAccountByAssetIdAndAccount',
|
|
36
|
+
'storeEncounterByPlayer',
|
|
37
|
+
'storePositionByPlayer',
|
|
38
|
+
'tableFieldByTableNameAndFieldName',
|
|
39
|
+
'storeAccount',
|
|
40
|
+
'storeEncounter',
|
|
41
|
+
'storePosition',
|
|
42
|
+
'tableField'
|
|
43
|
+
]
|
|
44
|
+
🔄 字段重命名统计: { '原始字段数': 16, '最终字段数': 16, '重命名字段数': 10 }
|
|
45
|
+
📝 重命名映射: {
|
|
46
|
+
allStoreAccounts: 'accounts',
|
|
47
|
+
allStoreEncounters: 'encounters',
|
|
48
|
+
allStoreMapConfigs: 'mapConfigs',
|
|
49
|
+
allStorePositions: 'positions',
|
|
50
|
+
storeAccountByAssetIdAndAccount: 'accountByAssetIdAndAccount',
|
|
51
|
+
storeEncounterByPlayer: 'encounterByPlayer',
|
|
52
|
+
storePositionByPlayer: 'positionByPlayer',
|
|
53
|
+
storeAccount: 'account',
|
|
54
|
+
storeEncounter: 'encounter',
|
|
55
|
+
storePosition: 'position'
|
|
56
|
+
}
|
|
57
|
+
[2025-06-06 07:48:10] [32mINFO[39m: [36mdubhe-graphql-server [subscription]: 启用PostGraphile原生Live Queries功能[39m [90m{"component":"subscription"}[39m
|
|
58
|
+
[2025-06-06 07:48:31] [32mINFO[39m: [36mdubhe-graphql-server [performance]: GraphQL GET completed[39m [90m{"component":"performance","duration":"1ms","endpoint":"/graphql"}[39m
|
|
59
|
+
[2025-06-06 07:51:36] [32mINFO[39m: [36mdubhe-graphql-server [system]: 收到 SIGTERM 信号,开始优雅关闭服务器...[39m [90m{"component":"system"}[39m
|
|
60
|
+
[2025-06-06 07:51:36] [32mINFO[39m: [36mdubhe-graphql-server [system]: 🛑 开始关闭统一实时引擎...[39m [90m{"component":"system"}[39m
|
|
61
|
+
[2025-06-06 07:51:36] [32mINFO[39m: [36mdubhe-graphql-server [system]: ✅ 统一实时引擎已关闭[39m [90m{"component":"system"}[39m
|
|
62
|
+
[2025-06-06 07:51:36] [32mINFO[39m: [36mdubhe-graphql-server [system]: ⏹️ 正在关闭服务器...[39m [90m{"component":"system"}[39m
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Subscription configuration manager - supports dynamic configuration for three subscription modes
|
|
2
|
+
|
|
3
|
+
export interface SubscriptionCapabilities {
|
|
4
|
+
liveQueries: boolean;
|
|
5
|
+
pgSubscriptions: boolean;
|
|
6
|
+
nativeWebSocket: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SubscriptionConfig {
|
|
10
|
+
// Basic configuration
|
|
11
|
+
enableSubscriptions: boolean;
|
|
12
|
+
|
|
13
|
+
// Capability configuration
|
|
14
|
+
capabilities: SubscriptionCapabilities;
|
|
15
|
+
|
|
16
|
+
// Database configuration detection
|
|
17
|
+
walLevel: 'minimal' | 'replica' | 'logical';
|
|
18
|
+
pgVersion: string;
|
|
19
|
+
|
|
20
|
+
// Port configuration
|
|
21
|
+
graphqlPort: number;
|
|
22
|
+
websocketPort?: number;
|
|
23
|
+
|
|
24
|
+
// Performance configuration
|
|
25
|
+
maxConnections: number;
|
|
26
|
+
heartbeatInterval: number;
|
|
27
|
+
|
|
28
|
+
// Debug configuration
|
|
29
|
+
enableNotificationLogging: boolean;
|
|
30
|
+
enablePerformanceMetrics: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class SubscriptionConfigManager {
|
|
34
|
+
private config: SubscriptionConfig;
|
|
35
|
+
|
|
36
|
+
constructor(envVars: Record<string, string>) {
|
|
37
|
+
this.config = this.parseEnvironmentVariables(envVars);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Parse configuration from environment variables
|
|
41
|
+
private parseEnvironmentVariables(env: Record<string, string>): SubscriptionConfig {
|
|
42
|
+
const enableSubscriptions = env.ENABLE_SUBSCRIPTIONS !== 'false'; // Default enabled unless explicitly set to false
|
|
43
|
+
|
|
44
|
+
// Auto-detect WAL level (in actual applications, query through database)
|
|
45
|
+
const walLevel = this.detectWalLevel(env.DATABASE_URL);
|
|
46
|
+
|
|
47
|
+
// Determine capabilities based on WAL level and environment variables - default enable all features
|
|
48
|
+
const capabilities: SubscriptionCapabilities = {
|
|
49
|
+
liveQueries:
|
|
50
|
+
enableSubscriptions && env.ENABLE_LIVE_QUERIES !== 'false' && walLevel === 'logical',
|
|
51
|
+
|
|
52
|
+
pgSubscriptions: enableSubscriptions && env.ENABLE_PG_SUBSCRIPTIONS !== 'false',
|
|
53
|
+
|
|
54
|
+
nativeWebSocket: enableSubscriptions && env.ENABLE_NATIVE_WEBSOCKET !== 'false'
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
enableSubscriptions,
|
|
59
|
+
capabilities,
|
|
60
|
+
walLevel,
|
|
61
|
+
pgVersion: '13+', // In actual applications, get through query
|
|
62
|
+
|
|
63
|
+
graphqlPort: parseInt(env.PORT || '4000'),
|
|
64
|
+
websocketPort: env.REALTIME_PORT ? parseInt(env.REALTIME_PORT) : undefined,
|
|
65
|
+
|
|
66
|
+
maxConnections: parseInt(env.MAX_SUBSCRIPTION_CONNECTIONS || '1000'),
|
|
67
|
+
heartbeatInterval: parseInt(env.SUBSCRIPTION_HEARTBEAT_INTERVAL || '30000'),
|
|
68
|
+
|
|
69
|
+
enableNotificationLogging: env.DEBUG_NOTIFICATIONS === 'true',
|
|
70
|
+
enablePerformanceMetrics: env.ENABLE_SUBSCRIPTION_METRICS === 'true'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Detect database WAL level
|
|
75
|
+
private detectWalLevel(databaseUrl?: string): 'minimal' | 'replica' | 'logical' {
|
|
76
|
+
// In actual applications should query database
|
|
77
|
+
// SELECT setting FROM pg_settings WHERE name = 'wal_level';
|
|
78
|
+
|
|
79
|
+
// Currently return default value
|
|
80
|
+
return 'replica';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Get current configuration
|
|
84
|
+
getConfig(): SubscriptionConfig {
|
|
85
|
+
return { ...this.config };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check if specific capability is available
|
|
89
|
+
isCapabilityEnabled(capability: keyof SubscriptionCapabilities): boolean {
|
|
90
|
+
return this.config.capabilities[capability];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Get recommended subscription method
|
|
94
|
+
getRecommendedSubscriptionMethod(): string {
|
|
95
|
+
if (this.config.capabilities.liveQueries) {
|
|
96
|
+
return 'live-queries';
|
|
97
|
+
} else if (this.config.capabilities.pgSubscriptions) {
|
|
98
|
+
return 'pg-subscriptions';
|
|
99
|
+
} else if (this.config.capabilities.nativeWebSocket) {
|
|
100
|
+
return 'native-websocket';
|
|
101
|
+
} else {
|
|
102
|
+
return 'none';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Generate client configuration
|
|
107
|
+
generateClientConfig() {
|
|
108
|
+
const baseUrl = `http://localhost:${this.config.graphqlPort}`;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
graphqlEndpoint: `${baseUrl}/graphql`,
|
|
112
|
+
subscriptionEndpoint:
|
|
113
|
+
this.config.capabilities.pgSubscriptions || this.config.capabilities.liveQueries
|
|
114
|
+
? `ws://localhost:${this.config.graphqlPort}/graphql`
|
|
115
|
+
: undefined,
|
|
116
|
+
nativeWebSocketEndpoint: this.config.capabilities.nativeWebSocket
|
|
117
|
+
? `ws://localhost:${this.config.websocketPort || this.config.graphqlPort}`
|
|
118
|
+
: undefined,
|
|
119
|
+
capabilities: this.config.capabilities,
|
|
120
|
+
recommendedMethod: this.getRecommendedSubscriptionMethod()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Generate PostGraphile configuration - simplified version, only keep listen subscriptions
|
|
125
|
+
generatePostGraphileConfig() {
|
|
126
|
+
return {
|
|
127
|
+
subscriptions: this.config.enableSubscriptions,
|
|
128
|
+
live: false, // Disable live queries, only use listen subscriptions
|
|
129
|
+
simpleSubscriptions: this.config.capabilities.pgSubscriptions,
|
|
130
|
+
|
|
131
|
+
// Performance configuration - optimized for listen subscriptions
|
|
132
|
+
pgSettings: {
|
|
133
|
+
statement_timeout: '30s',
|
|
134
|
+
default_transaction_isolation: 'read committed'
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// Monitoring configuration
|
|
138
|
+
allowExplain: this.config.enablePerformanceMetrics,
|
|
139
|
+
disableQueryLog: !this.config.enableNotificationLogging
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Generate environment variable documentation
|
|
144
|
+
generateDocumentation(): string {
|
|
145
|
+
return `
|
|
146
|
+
# 📡 Subscription System Configuration Guide
|
|
147
|
+
|
|
148
|
+
## Basic Configuration
|
|
149
|
+
ENABLE_SUBSCRIPTIONS=false # Disable subscription features (default enabled, set to false to disable)
|
|
150
|
+
|
|
151
|
+
## Capability Configuration (optional, auto-detect by default)
|
|
152
|
+
ENABLE_LIVE_QUERIES=true # Enable @live directive (requires wal_level=logical)
|
|
153
|
+
ENABLE_PG_SUBSCRIPTIONS=true # Enable PostgreSQL subscriptions
|
|
154
|
+
ENABLE_NATIVE_WEBSOCKET=true # Enable native WebSocket
|
|
155
|
+
|
|
156
|
+
## Port Configuration
|
|
157
|
+
PORT=4000 # GraphQL port
|
|
158
|
+
REALTIME_PORT=4001 # Native WebSocket port (optional)
|
|
159
|
+
|
|
160
|
+
## Performance Configuration
|
|
161
|
+
MAX_SUBSCRIPTION_CONNECTIONS=1000 # Maximum connections
|
|
162
|
+
SUBSCRIPTION_HEARTBEAT_INTERVAL=30000 # Heartbeat interval (ms)
|
|
163
|
+
|
|
164
|
+
## Debug Configuration
|
|
165
|
+
DEBUG_NOTIFICATIONS=false # Notification logging
|
|
166
|
+
ENABLE_SUBSCRIPTION_METRICS=false # Performance metrics
|
|
167
|
+
|
|
168
|
+
## Current Configuration Status:
|
|
169
|
+
- Subscription Features: ${this.config.enableSubscriptions ? '✅ Enabled' : '❌ Disabled'}
|
|
170
|
+
- Live Queries: ${this.config.capabilities.liveQueries ? '✅ Available' : '❌ Not Available'}
|
|
171
|
+
- PG Subscriptions: ${
|
|
172
|
+
this.config.capabilities.pgSubscriptions ? '✅ Available' : '❌ Not Available'
|
|
173
|
+
}
|
|
174
|
+
- Native WebSocket: ${
|
|
175
|
+
this.config.capabilities.nativeWebSocket ? '✅ Available' : '❌ Not Available'
|
|
176
|
+
}
|
|
177
|
+
- WAL Level: ${this.config.walLevel}
|
|
178
|
+
- Recommended Method: ${this.getRecommendedSubscriptionMethod()}
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Export singleton instance
|
|
184
|
+
export const subscriptionConfig = new SubscriptionConfigManager(
|
|
185
|
+
process.env as Record<string, string>
|
|
186
|
+
);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { postgraphile } from 'postgraphile';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import * as dotenv from 'dotenv';
|
|
4
|
+
import {
|
|
5
|
+
dbLogger,
|
|
6
|
+
serverLogger,
|
|
7
|
+
systemLogger,
|
|
8
|
+
subscriptionLogger,
|
|
9
|
+
logPerformance
|
|
10
|
+
} from './utils/logger';
|
|
11
|
+
import {
|
|
12
|
+
DatabaseIntrospector,
|
|
13
|
+
createPostGraphileConfig,
|
|
14
|
+
PostGraphileConfigOptions,
|
|
15
|
+
WelcomePageConfig
|
|
16
|
+
} from './plugins';
|
|
17
|
+
import { EnhancedServerManager } from './plugins/enhanced-server-manager';
|
|
18
|
+
import { subscriptionConfig } from './config/subscription-config';
|
|
19
|
+
import {
|
|
20
|
+
generateStoreTablesInfo,
|
|
21
|
+
createUniversalSubscriptionsPlugin
|
|
22
|
+
} from './universal-subscriptions';
|
|
23
|
+
|
|
24
|
+
// Load environment variables
|
|
25
|
+
dotenv.config();
|
|
26
|
+
|
|
27
|
+
// Environment variable configuration
|
|
28
|
+
const {
|
|
29
|
+
DATABASE_URL = 'postgres://postgres:postgres@127.0.0.1:5432/postgres',
|
|
30
|
+
PORT = 4000,
|
|
31
|
+
NODE_ENV = 'development',
|
|
32
|
+
GRAPHQL_ENDPOINT = '/graphql',
|
|
33
|
+
PG_SCHEMA = 'public',
|
|
34
|
+
ENABLE_CORS = 'true'
|
|
35
|
+
} = process.env;
|
|
36
|
+
|
|
37
|
+
// Subscription feature is enabled by default unless explicitly set to false
|
|
38
|
+
const ENABLE_SUBSCRIPTIONS = process.env.ENABLE_SUBSCRIPTIONS !== 'false' ? 'true' : 'false';
|
|
39
|
+
|
|
40
|
+
// Create database connection pool
|
|
41
|
+
const pgPool = new Pool({
|
|
42
|
+
connectionString: DATABASE_URL
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Start server
|
|
46
|
+
const startServer = async (): Promise<void> => {
|
|
47
|
+
const startTime = Date.now();
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// 1. Test database connection and scan table structure
|
|
51
|
+
systemLogger.info('Initializing database connection and scanning table structure...', {
|
|
52
|
+
schema: PG_SCHEMA,
|
|
53
|
+
databaseUrl: DATABASE_URL.replace(/:[^:]*@/, ':****@') // Hide password
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const introspector = new DatabaseIntrospector(pgPool, PG_SCHEMA);
|
|
57
|
+
|
|
58
|
+
const isConnected = await introspector.testConnection();
|
|
59
|
+
if (!isConnected) {
|
|
60
|
+
throw new Error('Database connection failed');
|
|
61
|
+
}
|
|
62
|
+
dbLogger.info('Database connection successful', { schema: PG_SCHEMA });
|
|
63
|
+
|
|
64
|
+
const allTables = await introspector.getAllTables();
|
|
65
|
+
const tableNames = allTables.map((t) => t.table_name);
|
|
66
|
+
|
|
67
|
+
dbLogger.info('Table structure scan completed', {
|
|
68
|
+
tableCount: allTables.length,
|
|
69
|
+
storeTableCount: tableNames.filter((name) => name.startsWith('store_')).length,
|
|
70
|
+
tableNames: tableNames.slice(0, 10) // Only show first 10 table names
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 2. Display subscription configuration status
|
|
74
|
+
const config = subscriptionConfig.getConfig();
|
|
75
|
+
subscriptionLogger.info('📡 Subscription system configuration status', {
|
|
76
|
+
enableSubscriptions: config.enableSubscriptions,
|
|
77
|
+
capabilities: {
|
|
78
|
+
pgSubscriptions: config.capabilities.pgSubscriptions
|
|
79
|
+
},
|
|
80
|
+
recommendedMethod: 'pg-subscriptions',
|
|
81
|
+
walLevel: config.walLevel
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 3. Pre-generate store table information for dynamic queries
|
|
85
|
+
subscriptionLogger.info('Pre-generating store table information for tool queries...');
|
|
86
|
+
const storeTablesInfo = await generateStoreTablesInfo(pgPool);
|
|
87
|
+
const storeTableNames = Object.keys(storeTablesInfo);
|
|
88
|
+
|
|
89
|
+
subscriptionLogger.info(`Discovered store tables: ${storeTableNames.join(', ')}`);
|
|
90
|
+
|
|
91
|
+
// 4. Create PostGraphile configuration
|
|
92
|
+
const postgraphileConfigOptions: PostGraphileConfigOptions = {
|
|
93
|
+
port: PORT,
|
|
94
|
+
nodeEnv: NODE_ENV,
|
|
95
|
+
graphqlEndpoint: GRAPHQL_ENDPOINT,
|
|
96
|
+
enableSubscriptions: ENABLE_SUBSCRIPTIONS,
|
|
97
|
+
enableCors: ENABLE_CORS,
|
|
98
|
+
databaseUrl: DATABASE_URL,
|
|
99
|
+
availableTables: tableNames
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
serverLogger.info('Creating PostGraphile configuration', {
|
|
103
|
+
endpoint: GRAPHQL_ENDPOINT,
|
|
104
|
+
enableCors: ENABLE_CORS,
|
|
105
|
+
enableSubscriptions: ENABLE_SUBSCRIPTIONS
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Use simplified configuration
|
|
109
|
+
const postgraphileConfig = {
|
|
110
|
+
...createPostGraphileConfig(postgraphileConfigOptions),
|
|
111
|
+
...subscriptionConfig.generatePostGraphileConfig()
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Add tools query plugin
|
|
115
|
+
const toolsPlugin = createUniversalSubscriptionsPlugin(storeTablesInfo);
|
|
116
|
+
postgraphileConfig.appendPlugins = [...(postgraphileConfig.appendPlugins || []), toolsPlugin];
|
|
117
|
+
|
|
118
|
+
// 5. Create PostGraphile middleware
|
|
119
|
+
console.log('🔧 Creating PostGraphile middleware...');
|
|
120
|
+
const postgraphileMiddleware = postgraphile(pgPool, PG_SCHEMA, {
|
|
121
|
+
...postgraphileConfig
|
|
122
|
+
});
|
|
123
|
+
console.log('✅ PostGraphile middleware creation completed:', typeof postgraphileMiddleware);
|
|
124
|
+
|
|
125
|
+
// 6. Configure welcome page
|
|
126
|
+
const welcomeConfig: WelcomePageConfig = {
|
|
127
|
+
port: PORT,
|
|
128
|
+
graphqlEndpoint: GRAPHQL_ENDPOINT,
|
|
129
|
+
nodeEnv: NODE_ENV,
|
|
130
|
+
schema: PG_SCHEMA,
|
|
131
|
+
enableCors: ENABLE_CORS,
|
|
132
|
+
enableSubscriptions: ENABLE_SUBSCRIPTIONS
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// 7. Create Express server manager
|
|
136
|
+
const serverManager = new EnhancedServerManager();
|
|
137
|
+
|
|
138
|
+
// 8. Create Express server
|
|
139
|
+
await serverManager.createEnhancedServer({
|
|
140
|
+
postgraphileMiddleware,
|
|
141
|
+
pgPool,
|
|
142
|
+
tableNames,
|
|
143
|
+
databaseUrl: DATABASE_URL,
|
|
144
|
+
allTables,
|
|
145
|
+
welcomeConfig,
|
|
146
|
+
postgraphileConfigOptions
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// 9. Start Express server
|
|
150
|
+
await serverManager.startServer();
|
|
151
|
+
|
|
152
|
+
logPerformance('Express server startup', startTime, {
|
|
153
|
+
port: PORT,
|
|
154
|
+
tableCount: allTables.length,
|
|
155
|
+
storeTableCount: storeTableNames.length,
|
|
156
|
+
nodeEnv: NODE_ENV,
|
|
157
|
+
framework: 'Express',
|
|
158
|
+
capabilities: {
|
|
159
|
+
pgSubscriptions: config.capabilities.pgSubscriptions
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 10. Display usage instructions
|
|
164
|
+
if (NODE_ENV === 'development') {
|
|
165
|
+
console.log('\n' + '='.repeat(80));
|
|
166
|
+
console.log('📖 Quick Access (Express Architecture):');
|
|
167
|
+
console.log(`Visit http://localhost:${PORT}/ to view homepage`);
|
|
168
|
+
console.log(`Visit http://localhost:${PORT}/playground to use GraphQL Playground`);
|
|
169
|
+
console.log(`Visit http://localhost:${PORT}/health to check server status`);
|
|
170
|
+
console.log(`Visit http://localhost:${PORT}/subscription-config to get client configuration`);
|
|
171
|
+
console.log(`Visit http://localhost:${PORT}/subscription-docs to view configuration guide`);
|
|
172
|
+
console.log('='.repeat(80) + '\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 11. Set up simple and direct shutdown handling
|
|
176
|
+
let isShuttingDown = false;
|
|
177
|
+
const quickShutdown = (signal: string) => {
|
|
178
|
+
if (isShuttingDown) {
|
|
179
|
+
systemLogger.info('⚡ Force exiting process...');
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
isShuttingDown = true;
|
|
184
|
+
systemLogger.info(`🛑 Received ${signal} signal, shutting down Express server...`);
|
|
185
|
+
|
|
186
|
+
// Set 1 second force exit timeout
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
systemLogger.info('⚡ Quick exit');
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}, 1000);
|
|
191
|
+
|
|
192
|
+
// Try to shutdown Express server quickly
|
|
193
|
+
serverManager.quickShutdown().finally(() => {
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
process.on('SIGINT', () => quickShutdown('SIGINT'));
|
|
199
|
+
process.on('SIGTERM', () => quickShutdown('SIGTERM'));
|
|
200
|
+
|
|
201
|
+
// Simplified exception handling
|
|
202
|
+
process.on('unhandledRejection', (reason) => {
|
|
203
|
+
console.error('❌ Unhandled Promise rejection:', reason);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
process.on('uncaughtException', (error) => {
|
|
207
|
+
console.error('❌ Uncaught exception:', error.message);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
});
|
|
210
|
+
} catch (error) {
|
|
211
|
+
systemLogger.error('Failed to start Express server', error, {
|
|
212
|
+
databaseUrl: DATABASE_URL.replace(/:[^:]*@/, ':****@'),
|
|
213
|
+
schema: PG_SCHEMA,
|
|
214
|
+
port: PORT
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
systemLogger.info('💡 Possible causes:');
|
|
218
|
+
systemLogger.info('1. Database connection failed - check DATABASE_URL');
|
|
219
|
+
systemLogger.info(
|
|
220
|
+
'2. Expected table structure not found in database - ensure sui-rust-indexer is running'
|
|
221
|
+
);
|
|
222
|
+
systemLogger.info('3. Permission issues - ensure database user has sufficient permissions');
|
|
223
|
+
systemLogger.info('4. Missing dependencies - run pnpm install');
|
|
224
|
+
|
|
225
|
+
// Display subscription configuration help
|
|
226
|
+
console.log('\n' + subscriptionConfig.generateDocumentation());
|
|
227
|
+
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Start application
|
|
233
|
+
systemLogger.info('🚀 Starting Sui Indexer GraphQL Server (Express Architecture)...', {
|
|
234
|
+
nodeVersion: process.version,
|
|
235
|
+
platform: process.platform,
|
|
236
|
+
pid: process.pid
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
startServer();
|