@gokiteam/goki-dev 0.2.0
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/README.md +478 -0
- package/bin/goki-dev.js +452 -0
- package/bin/mcp-server.js +16 -0
- package/bin/secrets-cli.js +302 -0
- package/cli/ComposeOverrideGenerator.js +226 -0
- package/cli/ComposeParser.js +73 -0
- package/cli/ConfigGenerator.js +304 -0
- package/cli/ConfigManager.js +46 -0
- package/cli/DatabaseManager.js +94 -0
- package/cli/DevToolsChecker.js +21 -0
- package/cli/DevToolsDir.js +66 -0
- package/cli/DevToolsManager.js +451 -0
- package/cli/DockerManager.js +138 -0
- package/cli/FunctionManager.js +95 -0
- package/cli/HttpProxyRewriter.js +91 -0
- package/cli/Logger.js +10 -0
- package/cli/McpConfigManager.js +123 -0
- package/cli/NgrokManager.js +431 -0
- package/cli/ProjectCLI.js +2322 -0
- package/cli/PubSubManager.js +129 -0
- package/cli/SnapshotManager.js +88 -0
- package/cli/UiFormatter.js +292 -0
- package/cli/WebhookUrlRewriter.js +32 -0
- package/cli/secrets/BiometricAuth.js +125 -0
- package/cli/secrets/SecretInjector.js +47 -0
- package/cli/secrets/SecretsConfig.js +141 -0
- package/cli/secrets/SecretsDoctor.js +384 -0
- package/cli/secrets/SecretsManager.js +255 -0
- package/client/dist/client.d.ts +332 -0
- package/client/dist/client.js +507 -0
- package/client/dist/helpers.d.ts +62 -0
- package/client/dist/helpers.js +122 -0
- package/client/dist/index.d.ts +59 -0
- package/client/dist/index.js +78 -0
- package/client/dist/package.json +1 -0
- package/client/dist/types.d.ts +280 -0
- package/client/dist/types.js +7 -0
- package/config.development +46 -0
- package/config.test +18 -0
- package/guidelines/CodingStyleGuideline.md +148 -0
- package/guidelines/CommentingGuideline.md +10 -0
- package/guidelines/HttpApiImplementationGuideline.md +137 -0
- package/guidelines/NamingGuideline.md +182 -0
- package/package.json +138 -0
- package/patterns/api/[collectionName]/Controllers.md +62 -0
- package/patterns/api/[collectionName]/Logic.md +154 -0
- package/patterns/api/[collectionName]/Permissions.md +81 -0
- package/patterns/api/[collectionName]/Router.md +83 -0
- package/patterns/api/[collectionName]/Schemas.md +197 -0
- package/patterns/configs/Patterns.md +7 -0
- package/patterns/enums/Patterns.md +24 -0
- package/patterns/errorHandling/Patterns.md +185 -0
- package/patterns/testing/Patterns.md +232 -0
- package/src/Server.js +238 -0
- package/src/api/dashboard/Controllers.js +9 -0
- package/src/api/dashboard/Logic.js +76 -0
- package/src/api/dashboard/Router.js +11 -0
- package/src/api/dashboard/Schemas.js +47 -0
- package/src/api/data/Controllers.js +26 -0
- package/src/api/data/Logic.js +188 -0
- package/src/api/data/Router.js +16 -0
- package/src/api/docker/Controllers.js +33 -0
- package/src/api/docker/Logic.js +268 -0
- package/src/api/docker/Router.js +15 -0
- package/src/api/docker/Schemas.js +80 -0
- package/src/api/docs/Controllers.js +15 -0
- package/src/api/docs/Logic.js +85 -0
- package/src/api/docs/Router.js +12 -0
- package/src/api/export/Controllers.js +30 -0
- package/src/api/export/Logic.js +143 -0
- package/src/api/export/Router.js +18 -0
- package/src/api/export/Schemas.js +104 -0
- package/src/api/firestore/Controllers.js +152 -0
- package/src/api/firestore/Logic.js +474 -0
- package/src/api/firestore/Router.js +23 -0
- package/src/api/functions/Controllers.js +261 -0
- package/src/api/functions/Logic.js +710 -0
- package/src/api/functions/Router.js +50 -0
- package/src/api/functions/Schemas.js +193 -0
- package/src/api/gateway/Controllers.js +72 -0
- package/src/api/gateway/Logic.js +74 -0
- package/src/api/gateway/Router.js +10 -0
- package/src/api/gateway/Schemas.js +19 -0
- package/src/api/health/Controllers.js +14 -0
- package/src/api/health/Logic.js +24 -0
- package/src/api/health/Router.js +12 -0
- package/src/api/httpTraffic/Controllers.js +29 -0
- package/src/api/httpTraffic/Logic.js +33 -0
- package/src/api/httpTraffic/Router.js +9 -0
- package/src/api/httpTraffic/Schemas.js +23 -0
- package/src/api/logging/Controllers.js +80 -0
- package/src/api/logging/Logic.js +461 -0
- package/src/api/logging/Router.js +24 -0
- package/src/api/logging/Schemas.js +43 -0
- package/src/api/mqtt/Controllers.js +17 -0
- package/src/api/mqtt/Logic.js +66 -0
- package/src/api/mqtt/Router.js +12 -0
- package/src/api/postgres/Controllers.js +97 -0
- package/src/api/postgres/Logic.js +221 -0
- package/src/api/postgres/Router.js +21 -0
- package/src/api/pubsub/Controllers.js +236 -0
- package/src/api/pubsub/Logic.js +732 -0
- package/src/api/pubsub/Router.js +41 -0
- package/src/api/pubsub/Schemas.js +355 -0
- package/src/api/redis/Controllers.js +63 -0
- package/src/api/redis/Logic.js +239 -0
- package/src/api/redis/Router.js +21 -0
- package/src/api/scheduler/Controllers.js +27 -0
- package/src/api/scheduler/Logic.js +49 -0
- package/src/api/scheduler/Router.js +16 -0
- package/src/api/services/Controllers.js +26 -0
- package/src/api/services/Logic.js +205 -0
- package/src/api/services/Router.js +14 -0
- package/src/api/services/Schemas.js +66 -0
- package/src/api/snapshots/Controllers.js +37 -0
- package/src/api/snapshots/Logic.js +797 -0
- package/src/api/snapshots/Router.js +15 -0
- package/src/api/snapshots/Schemas.js +23 -0
- package/src/api/webhooks/Controllers.js +49 -0
- package/src/api/webhooks/Logic.js +137 -0
- package/src/api/webhooks/Router.js +12 -0
- package/src/api/webhooks/Schemas.js +31 -0
- package/src/configs/Application.js +147 -0
- package/src/configs/Default.js +13 -0
- package/src/consumers/BlackboxLogsConsumer.js +235 -0
- package/src/consumers/DockerLogsConsumer.js +687 -0
- package/src/db/Tables.js +66 -0
- package/src/db/schemas/firestore.js +18 -0
- package/src/db/schemas/functions.js +65 -0
- package/src/db/schemas/httpTraffic.js +43 -0
- package/src/db/schemas/logging.js +74 -0
- package/src/db/schemas/migrations.js +64 -0
- package/src/db/schemas/mqtt.js +56 -0
- package/src/db/schemas/pubsub.js +90 -0
- package/src/db/schemas/pubsubRegistry.js +22 -0
- package/src/db/schemas/webhooks.js +28 -0
- package/src/emulation/awsiot/Controllers.js +91 -0
- package/src/emulation/awsiot/Logic.js +70 -0
- package/src/emulation/awsiot/Router.js +19 -0
- package/src/emulation/awsiot/Server.js +100 -0
- package/src/emulation/firestore/Server.js +136 -0
- package/src/emulation/logging/Controllers.js +212 -0
- package/src/emulation/logging/Logic.js +416 -0
- package/src/emulation/logging/Router.js +36 -0
- package/src/emulation/logging/Schemas.js +82 -0
- package/src/emulation/logging/Server.js +108 -0
- package/src/emulation/pubsub/Controllers.js +279 -0
- package/src/emulation/pubsub/DefaultTopics.js +162 -0
- package/src/emulation/pubsub/Logic.js +427 -0
- package/src/emulation/pubsub/README.md +309 -0
- package/src/emulation/pubsub/Router.js +33 -0
- package/src/emulation/pubsub/Server.js +104 -0
- package/src/emulation/pubsub/ShadowPoller.js +276 -0
- package/src/emulation/pubsub/ShadowSubscriptionManager.js +199 -0
- package/src/enums/ContainerNames.js +106 -0
- package/src/enums/ErrorReason.js +28 -0
- package/src/enums/FunctionStatuses.js +15 -0
- package/src/enums/FunctionTriggerTypes.js +15 -0
- package/src/enums/GatewayState.js +7 -0
- package/src/enums/ServiceNames.js +68 -0
- package/src/jobs/DatabaseMaintenance.js +184 -0
- package/src/jobs/MessageHistoryCleanup.js +152 -0
- package/src/mcp/ApiClient.js +25 -0
- package/src/mcp/Server.js +52 -0
- package/src/mcp/prompts/debugging.js +104 -0
- package/src/mcp/resources/platform.js +118 -0
- package/src/mcp/tools/data.js +84 -0
- package/src/mcp/tools/docker.js +166 -0
- package/src/mcp/tools/firestore.js +162 -0
- package/src/mcp/tools/functions.js +380 -0
- package/src/mcp/tools/httpTraffic.js +69 -0
- package/src/mcp/tools/logging.js +174 -0
- package/src/mcp/tools/mqtt.js +37 -0
- package/src/mcp/tools/postgres.js +130 -0
- package/src/mcp/tools/pubsub.js +316 -0
- package/src/mcp/tools/redis.js +146 -0
- package/src/mcp/tools/services.js +169 -0
- package/src/mcp/tools/snapshots.js +88 -0
- package/src/mcp/tools/webhooks.js +115 -0
- package/src/middleware/DevProxy.js +67 -0
- package/src/middleware/ErrorCatcher.js +35 -0
- package/src/middleware/HttpProxy.js +215 -0
- package/src/middleware/Reply.js +24 -0
- package/src/middleware/TraceId.js +9 -0
- package/src/middleware/WebhookProxy.js +234 -0
- package/src/protocols/mqtt/Broker.js +92 -0
- package/src/protocols/mqtt/Handlers.js +175 -0
- package/src/protocols/mqtt/PubSubBridge.js +162 -0
- package/src/protocols/mqtt/Server.js +116 -0
- package/src/runtime/FunctionRunner.js +179 -0
- package/src/services/AppGatewayService.js +582 -0
- package/src/singletons/FirestoreBroadcaster.js +367 -0
- package/src/singletons/FunctionTriggerDispatcher.js +456 -0
- package/src/singletons/FunctionsService.js +418 -0
- package/src/singletons/HttpProxy.js +224 -0
- package/src/singletons/LogBroadcaster.js +159 -0
- package/src/singletons/Logger.js +49 -0
- package/src/singletons/MemoryJsonStore.js +175 -0
- package/src/singletons/MessageBroadcaster.js +190 -0
- package/src/singletons/PostgresBroadcaster.js +367 -0
- package/src/singletons/PostgresClient.js +180 -0
- package/src/singletons/RedisClient.js +184 -0
- package/src/singletons/SqliteStore.js +480 -0
- package/src/singletons/TickService.js +151 -0
- package/src/singletons/WebhookProxy.js +223 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Logic } from './Logic.js'
|
|
2
|
+
import { PostgresBroadcaster } from '../../singletons/PostgresBroadcaster.js'
|
|
3
|
+
|
|
4
|
+
export const Controllers = {
|
|
5
|
+
async testConnection (ctx) {
|
|
6
|
+
const { traceId } = ctx.state
|
|
7
|
+
const { database } = ctx.request.body
|
|
8
|
+
const result = await Logic.testConnection({ database, traceId })
|
|
9
|
+
ctx.reply(result)
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
async listDatabases (ctx) {
|
|
13
|
+
const { traceId } = ctx.state
|
|
14
|
+
const result = await Logic.listDatabases({ traceId })
|
|
15
|
+
ctx.reply(result)
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async listSchemas (ctx) {
|
|
19
|
+
const { traceId } = ctx.state
|
|
20
|
+
const { database } = ctx.request.body
|
|
21
|
+
const result = await Logic.listSchemas({ database, traceId })
|
|
22
|
+
ctx.reply(result)
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async listTables (ctx) {
|
|
26
|
+
const { traceId } = ctx.state
|
|
27
|
+
const { schema, database } = ctx.request.body
|
|
28
|
+
const result = await Logic.listTables({ schema, database, traceId })
|
|
29
|
+
ctx.reply(result)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async listColumns (ctx) {
|
|
33
|
+
const { traceId } = ctx.state
|
|
34
|
+
const { schema, table, database } = ctx.request.body
|
|
35
|
+
const result = await Logic.listColumns({ schema, table, database, traceId })
|
|
36
|
+
ctx.reply(result)
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
async listRows (ctx) {
|
|
40
|
+
const { traceId } = ctx.state
|
|
41
|
+
const { schema, table, limit, offset, database } = ctx.request.body
|
|
42
|
+
const result = await Logic.listRows({ schema, table, limit, offset, database, traceId })
|
|
43
|
+
ctx.reply(result)
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async executeQuery (ctx) {
|
|
47
|
+
const { traceId } = ctx.state
|
|
48
|
+
const { sql, database } = ctx.request.body
|
|
49
|
+
const result = await Logic.executeQuery({ sql, database, traceId })
|
|
50
|
+
ctx.reply(result)
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async streamChanges (ctx) {
|
|
54
|
+
const { traceId } = ctx.state
|
|
55
|
+
const { database, schema, table } = ctx.query
|
|
56
|
+
if (!database || !table) {
|
|
57
|
+
ctx.status = 400
|
|
58
|
+
ctx.body = { success: false, status: 'error', message: 'database and table are required query parameters', traceId }
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
const resolvedSchema = schema || 'public'
|
|
62
|
+
ctx.set({
|
|
63
|
+
'Content-Type': 'text/event-stream',
|
|
64
|
+
'Cache-Control': 'no-cache',
|
|
65
|
+
Connection: 'keep-alive',
|
|
66
|
+
'X-Accel-Buffering': 'no'
|
|
67
|
+
})
|
|
68
|
+
ctx.status = 200
|
|
69
|
+
ctx.respond = false
|
|
70
|
+
ctx.res.write(`event: connected\ndata: ${JSON.stringify({
|
|
71
|
+
connected: true,
|
|
72
|
+
traceId,
|
|
73
|
+
database,
|
|
74
|
+
schema: resolvedSchema,
|
|
75
|
+
table
|
|
76
|
+
})}\n\n`)
|
|
77
|
+
const filters = { database, schema: resolvedSchema, table }
|
|
78
|
+
PostgresBroadcaster.addClient(traceId, ctx, filters)
|
|
79
|
+
await new Promise((resolve) => {
|
|
80
|
+
ctx.req.on('close', () => {
|
|
81
|
+
PostgresBroadcaster.removeClient(traceId)
|
|
82
|
+
resolve()
|
|
83
|
+
})
|
|
84
|
+
ctx.req.on('error', () => {
|
|
85
|
+
PostgresBroadcaster.removeClient(traceId)
|
|
86
|
+
resolve()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async waitForCondition (ctx) {
|
|
92
|
+
const { traceId } = ctx.state
|
|
93
|
+
const { condition, timeout, pollInterval } = ctx.request.body
|
|
94
|
+
const result = await Logic.waitForCondition({ condition, timeout, pollInterval, traceId })
|
|
95
|
+
ctx.reply(result)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { PostgresClient } from '../../singletons/PostgresClient.js'
|
|
2
|
+
import { PostgresBroadcaster } from '../../singletons/PostgresBroadcaster.js'
|
|
3
|
+
|
|
4
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
|
5
|
+
|
|
6
|
+
const deepEqual = (a, b) => {
|
|
7
|
+
if (a === b) return true
|
|
8
|
+
if (a == null || b == null) return false
|
|
9
|
+
if (typeof a !== 'object' || typeof b !== 'object') return false
|
|
10
|
+
const keysA = Object.keys(a)
|
|
11
|
+
const keysB = Object.keys(b)
|
|
12
|
+
if (keysA.length !== keysB.length) return false
|
|
13
|
+
for (const key of keysA) {
|
|
14
|
+
if (!keysB.includes(key)) return false
|
|
15
|
+
if (!deepEqual(a[key], b[key])) return false
|
|
16
|
+
}
|
|
17
|
+
return true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Logic = {
|
|
21
|
+
async testConnection (params) {
|
|
22
|
+
const { database, traceId } = params
|
|
23
|
+
try {
|
|
24
|
+
const result = await PostgresClient.testConnection(database)
|
|
25
|
+
return {
|
|
26
|
+
...result,
|
|
27
|
+
traceId
|
|
28
|
+
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
status: 'error',
|
|
32
|
+
message: error.message,
|
|
33
|
+
traceId
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async listDatabases (params) {
|
|
39
|
+
const { traceId } = params
|
|
40
|
+
try {
|
|
41
|
+
const databases = await PostgresClient.getDatabases()
|
|
42
|
+
return {
|
|
43
|
+
databases,
|
|
44
|
+
total: databases.length,
|
|
45
|
+
traceId
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
status: 'error',
|
|
50
|
+
message: error.message,
|
|
51
|
+
traceId
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async listSchemas (params) {
|
|
57
|
+
const { database, traceId } = params
|
|
58
|
+
try {
|
|
59
|
+
const schemas = await PostgresClient.getSchemas(database)
|
|
60
|
+
return {
|
|
61
|
+
schemas,
|
|
62
|
+
total: schemas.length,
|
|
63
|
+
traceId
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
status: 'error',
|
|
68
|
+
message: error.message,
|
|
69
|
+
traceId
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async listTables (params) {
|
|
75
|
+
const { schema = 'public', database, traceId } = params
|
|
76
|
+
try {
|
|
77
|
+
const tables = await PostgresClient.getTables(schema, database)
|
|
78
|
+
return {
|
|
79
|
+
tables,
|
|
80
|
+
total: tables.length,
|
|
81
|
+
schema,
|
|
82
|
+
traceId
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
status: 'error',
|
|
87
|
+
message: error.message,
|
|
88
|
+
traceId
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async listColumns (params) {
|
|
94
|
+
const { schema = 'public', table, database, traceId } = params
|
|
95
|
+
try {
|
|
96
|
+
if (!table) {
|
|
97
|
+
return {
|
|
98
|
+
status: 'error',
|
|
99
|
+
message: 'Table name is required',
|
|
100
|
+
traceId
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const columns = await PostgresClient.getColumns(schema, table, database)
|
|
104
|
+
return {
|
|
105
|
+
columns,
|
|
106
|
+
total: columns.length,
|
|
107
|
+
schema,
|
|
108
|
+
table,
|
|
109
|
+
traceId
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
status: 'error',
|
|
114
|
+
message: error.message,
|
|
115
|
+
traceId
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
async listRows (params) {
|
|
121
|
+
const { schema = 'public', table, limit = 50, offset = 0, database, traceId } = params
|
|
122
|
+
try {
|
|
123
|
+
if (!table) {
|
|
124
|
+
return {
|
|
125
|
+
status: 'error',
|
|
126
|
+
message: 'Table name is required',
|
|
127
|
+
traceId
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const result = await PostgresClient.getRows(schema, table, limit, offset, database)
|
|
131
|
+
return {
|
|
132
|
+
rows: result.rows,
|
|
133
|
+
total: result.total,
|
|
134
|
+
fields: result.fields,
|
|
135
|
+
schema,
|
|
136
|
+
table,
|
|
137
|
+
limit,
|
|
138
|
+
offset,
|
|
139
|
+
traceId
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return {
|
|
143
|
+
status: 'error',
|
|
144
|
+
message: error.message,
|
|
145
|
+
traceId
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
async executeQuery (params) {
|
|
151
|
+
const { sql, database, traceId } = params
|
|
152
|
+
try {
|
|
153
|
+
if (!sql) {
|
|
154
|
+
return {
|
|
155
|
+
status: 'error',
|
|
156
|
+
message: 'SQL query is required',
|
|
157
|
+
traceId
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const start = performance.now()
|
|
161
|
+
const result = await PostgresClient.executeQuery(sql, database)
|
|
162
|
+
const executionTime = Math.round(performance.now() - start)
|
|
163
|
+
// Notify SSE clients after write queries so other tabs refresh immediately
|
|
164
|
+
const firstWord = sql.trim().split(/\s/)[0].toUpperCase()
|
|
165
|
+
if (['INSERT', 'UPDATE', 'DELETE'].includes(firstWord)) {
|
|
166
|
+
const tableMatch = sql.match(/(?:INTO|UPDATE|FROM)\s+"?(\w+)"?/i)
|
|
167
|
+
const table = tableMatch?.[1]
|
|
168
|
+
PostgresBroadcaster.notifyChange(database, table ? 'public' : null, table || null)
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
rows: result.rows,
|
|
172
|
+
rowCount: result.rowCount,
|
|
173
|
+
fields: result.fields,
|
|
174
|
+
executionTime,
|
|
175
|
+
traceId
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
status: 'error',
|
|
180
|
+
message: error.message,
|
|
181
|
+
traceId
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
async waitForCondition (params) {
|
|
187
|
+
const { condition, timeout = 5000, pollInterval = 100, traceId } = params
|
|
188
|
+
const startTime = Date.now()
|
|
189
|
+
try {
|
|
190
|
+
while (Date.now() - startTime < timeout) {
|
|
191
|
+
const result = await PostgresClient.query(
|
|
192
|
+
condition.query,
|
|
193
|
+
condition.params || [],
|
|
194
|
+
condition.database
|
|
195
|
+
)
|
|
196
|
+
const actualValue = result.rows
|
|
197
|
+
const met = deepEqual(actualValue, condition.expect)
|
|
198
|
+
if (met) {
|
|
199
|
+
return {
|
|
200
|
+
met: true,
|
|
201
|
+
foundAt: Date.now() - startTime,
|
|
202
|
+
actualValue,
|
|
203
|
+
traceId
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
await sleep(pollInterval)
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
status: 'error',
|
|
210
|
+
message: 'Timeout waiting for condition',
|
|
211
|
+
traceId
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
status: 'error',
|
|
216
|
+
message: error.message,
|
|
217
|
+
traceId
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import KoaRouter from 'koa-router'
|
|
2
|
+
import { Controllers } from './Controllers.js'
|
|
3
|
+
|
|
4
|
+
const Router = new KoaRouter()
|
|
5
|
+
const v1 = new KoaRouter({ prefix: '/v1/postgres' })
|
|
6
|
+
|
|
7
|
+
v1.post('/connection/test', Controllers.testConnection)
|
|
8
|
+
v1.post('/databases/list', Controllers.listDatabases)
|
|
9
|
+
v1.post('/schemas/list', Controllers.listSchemas)
|
|
10
|
+
v1.post('/tables/list', Controllers.listTables)
|
|
11
|
+
v1.post('/columns/list', Controllers.listColumns)
|
|
12
|
+
v1.post('/rows/list', Controllers.listRows)
|
|
13
|
+
v1.post('/query/execute', Controllers.executeQuery)
|
|
14
|
+
v1.get('/changes/stream', Controllers.streamChanges)
|
|
15
|
+
|
|
16
|
+
// Testing Helpers
|
|
17
|
+
v1.post('/rows/wait-for', Controllers.waitForCondition)
|
|
18
|
+
|
|
19
|
+
Router.use(v1.routes())
|
|
20
|
+
|
|
21
|
+
export { Router }
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Logic } from './Logic.js'
|
|
2
|
+
import { MessageBroadcaster } from '../../singletons/MessageBroadcaster.js'
|
|
3
|
+
|
|
4
|
+
export const Controllers = {
|
|
5
|
+
async listTopics (ctx) {
|
|
6
|
+
const { traceId } = ctx.state
|
|
7
|
+
const { filter, page } = ctx.request.body
|
|
8
|
+
const result = await Logic.listTopics({ filter, page, traceId })
|
|
9
|
+
ctx.reply(result)
|
|
10
|
+
},
|
|
11
|
+
async createTopic (ctx) {
|
|
12
|
+
const { traceId } = ctx.state
|
|
13
|
+
const { topicName } = ctx.request.body
|
|
14
|
+
const result = await Logic.createTopic({ topicName, traceId })
|
|
15
|
+
ctx.reply(result)
|
|
16
|
+
},
|
|
17
|
+
async deleteTopic (ctx) {
|
|
18
|
+
const { traceId } = ctx.state
|
|
19
|
+
const { topicName } = ctx.request.body
|
|
20
|
+
const result = await Logic.deleteTopic({ topicName, traceId })
|
|
21
|
+
ctx.reply(result)
|
|
22
|
+
},
|
|
23
|
+
async listSubscriptions (ctx) {
|
|
24
|
+
const { traceId } = ctx.state
|
|
25
|
+
const { filter, page } = ctx.request.body
|
|
26
|
+
const result = await Logic.listSubscriptions({ filter, page, traceId })
|
|
27
|
+
ctx.reply(result)
|
|
28
|
+
},
|
|
29
|
+
async createSubscription (ctx) {
|
|
30
|
+
const { traceId } = ctx.state
|
|
31
|
+
const { topicName, subscriptionName } = ctx.request.body
|
|
32
|
+
const result = await Logic.createSubscription({ topicName, subscriptionName, traceId })
|
|
33
|
+
ctx.reply(result)
|
|
34
|
+
},
|
|
35
|
+
async deleteSubscription (ctx) {
|
|
36
|
+
const { traceId } = ctx.state
|
|
37
|
+
const { subscriptionName } = ctx.request.body
|
|
38
|
+
const result = await Logic.deleteSubscription({ subscriptionName, traceId })
|
|
39
|
+
ctx.reply(result)
|
|
40
|
+
},
|
|
41
|
+
async listMessages (ctx) {
|
|
42
|
+
const { traceId } = ctx.state
|
|
43
|
+
const { filter, page } = ctx.request.body
|
|
44
|
+
const result = await Logic.listMessages({ filter, page, traceId })
|
|
45
|
+
ctx.reply(result)
|
|
46
|
+
},
|
|
47
|
+
async publishMessage (ctx) {
|
|
48
|
+
const { traceId } = ctx.state
|
|
49
|
+
const { topicName, message, attributes } = ctx.request.body
|
|
50
|
+
const result = await Logic.publishMessage({ topicName, message, attributes, traceId })
|
|
51
|
+
ctx.reply(result)
|
|
52
|
+
},
|
|
53
|
+
async pullMessages (ctx) {
|
|
54
|
+
const { traceId } = ctx.state
|
|
55
|
+
const { subscriptionName, maxMessages } = ctx.request.body
|
|
56
|
+
const result = await Logic.pullMessages({ subscriptionName, maxMessages, traceId })
|
|
57
|
+
ctx.reply(result)
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* SSE streaming endpoint for real-time message updates
|
|
62
|
+
* GET /v1/pubsub/messages/stream?topic=X&sender=Y&startTime=ISO
|
|
63
|
+
*/
|
|
64
|
+
async streamMessages (ctx) {
|
|
65
|
+
const { traceId } = ctx.state
|
|
66
|
+
const { topic, sender, startTime } = ctx.query
|
|
67
|
+
|
|
68
|
+
// Set SSE headers
|
|
69
|
+
ctx.set({
|
|
70
|
+
'Content-Type': 'text/event-stream',
|
|
71
|
+
'Cache-Control': 'no-cache',
|
|
72
|
+
Connection: 'keep-alive',
|
|
73
|
+
'X-Accel-Buffering': 'no' // Disable Nginx buffering
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
ctx.status = 200
|
|
77
|
+
ctx.respond = false // Tell Koa not to auto-finish the response
|
|
78
|
+
|
|
79
|
+
// Send initial connection event
|
|
80
|
+
ctx.res.write(`event: connected\ndata: ${JSON.stringify({ connected: true, traceId })}\n\n`)
|
|
81
|
+
|
|
82
|
+
// Register client with broadcaster
|
|
83
|
+
const filters = {}
|
|
84
|
+
if (topic) filters.topic = topic
|
|
85
|
+
if (sender) filters.sender = sender
|
|
86
|
+
|
|
87
|
+
MessageBroadcaster.addClient(traceId, ctx, filters)
|
|
88
|
+
|
|
89
|
+
// Send historical messages if startTime provided
|
|
90
|
+
if (startTime) {
|
|
91
|
+
try {
|
|
92
|
+
const history = await Logic.getHistory({
|
|
93
|
+
filter: {
|
|
94
|
+
topic,
|
|
95
|
+
sender,
|
|
96
|
+
timeRange: {
|
|
97
|
+
start: startTime,
|
|
98
|
+
end: new Date().toISOString()
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
page: { limit: 100 },
|
|
102
|
+
traceId
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
for (const msg of history.messages) {
|
|
106
|
+
ctx.res.write(`event: history\ndata: ${JSON.stringify(msg)}\n\n`)
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
ctx.res.write(`event: error\ndata: ${JSON.stringify({ error: error.message })}\n\n`)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Wait indefinitely until client disconnects
|
|
114
|
+
// Heartbeat is handled by the shared MessageBroadcaster interval
|
|
115
|
+
await new Promise((resolve) => {
|
|
116
|
+
ctx.req.on('close', () => {
|
|
117
|
+
MessageBroadcaster.removeClient(traceId)
|
|
118
|
+
resolve()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
ctx.req.on('error', () => {
|
|
122
|
+
MessageBroadcaster.removeClient(traceId)
|
|
123
|
+
resolve()
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Query message history with filters
|
|
130
|
+
* POST /v1/pubsub/messages/history
|
|
131
|
+
*/
|
|
132
|
+
async getHistory (ctx) {
|
|
133
|
+
const { traceId } = ctx.state
|
|
134
|
+
const { filter, page } = ctx.request.body
|
|
135
|
+
const result = await Logic.getHistory({ filter, page, traceId })
|
|
136
|
+
ctx.reply(result)
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Auto-suggest topics based on query
|
|
141
|
+
* POST /v1/pubsub/topics/suggest
|
|
142
|
+
*/
|
|
143
|
+
async suggestTopics (ctx) {
|
|
144
|
+
const { traceId } = ctx.state
|
|
145
|
+
const { query, limit } = ctx.request.body
|
|
146
|
+
const result = await Logic.suggestTopics({ query, limit, traceId })
|
|
147
|
+
ctx.reply(result)
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Search messages by content
|
|
152
|
+
* POST /v1/pubsub/messages/search
|
|
153
|
+
*/
|
|
154
|
+
async searchMessages (ctx) {
|
|
155
|
+
const { traceId } = ctx.state
|
|
156
|
+
const { query, searchIn, filter, page } = ctx.request.body
|
|
157
|
+
const result = await Logic.searchMessages({ query, searchIn, filter, page, traceId })
|
|
158
|
+
ctx.reply(result)
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Clear message history
|
|
163
|
+
* POST /v1/pubsub/history/clear
|
|
164
|
+
*/
|
|
165
|
+
async clearHistory (ctx) {
|
|
166
|
+
const { traceId } = ctx.state
|
|
167
|
+
const { keepCount, topic } = ctx.request.body
|
|
168
|
+
const result = await Logic.clearHistory({ keepCount, topic, traceId })
|
|
169
|
+
ctx.reply(result)
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get message history statistics
|
|
174
|
+
* POST /v1/pubsub/history/stats
|
|
175
|
+
*/
|
|
176
|
+
async getHistoryStats (ctx) {
|
|
177
|
+
const { traceId } = ctx.state
|
|
178
|
+
const result = await Logic.getHistoryStats({ traceId })
|
|
179
|
+
ctx.reply(result)
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Wait for a message matching filter criteria
|
|
184
|
+
* POST /v1/pubsub/messages/wait-for
|
|
185
|
+
*/
|
|
186
|
+
async waitForMessage (ctx) {
|
|
187
|
+
const { traceId } = ctx.state
|
|
188
|
+
const { filter, timeout } = ctx.request.body
|
|
189
|
+
const result = await Logic.waitForMessage({ filter, timeout, traceId })
|
|
190
|
+
ctx.reply(result)
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Assert that a message was published with specific criteria
|
|
195
|
+
* POST /v1/pubsub/messages/assert-published
|
|
196
|
+
*/
|
|
197
|
+
async assertMessagePublished (ctx) {
|
|
198
|
+
const { traceId } = ctx.state
|
|
199
|
+
const { filter } = ctx.request.body
|
|
200
|
+
const result = await Logic.assertMessagePublished({ filter, traceId })
|
|
201
|
+
ctx.reply(result)
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Register topics in the persistent registry
|
|
206
|
+
* POST /v1/pubsub/registry/register
|
|
207
|
+
*/
|
|
208
|
+
async registerTopics (ctx) {
|
|
209
|
+
const { traceId } = ctx.state
|
|
210
|
+
const { projectName, topics } = ctx.request.body
|
|
211
|
+
const result = await Logic.registerTopics({ projectName, topics, traceId })
|
|
212
|
+
ctx.reply(result)
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* List registered topics from the persistent registry
|
|
217
|
+
* POST /v1/pubsub/registry/list
|
|
218
|
+
*/
|
|
219
|
+
async listRegistry (ctx) {
|
|
220
|
+
const { traceId } = ctx.state
|
|
221
|
+
const { projectName } = ctx.request.body
|
|
222
|
+
const result = await Logic.listRegistry({ projectName, traceId })
|
|
223
|
+
ctx.reply(result)
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Unregister topics for a project
|
|
228
|
+
* POST /v1/pubsub/registry/unregister
|
|
229
|
+
*/
|
|
230
|
+
async unregisterTopics (ctx) {
|
|
231
|
+
const { traceId } = ctx.state
|
|
232
|
+
const { projectName, topicNames } = ctx.request.body
|
|
233
|
+
const result = await Logic.unregisterTopics({ projectName, topicNames, traceId })
|
|
234
|
+
ctx.reply(result)
|
|
235
|
+
}
|
|
236
|
+
}
|