@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.
Files changed (205) hide show
  1. package/README.md +478 -0
  2. package/bin/goki-dev.js +452 -0
  3. package/bin/mcp-server.js +16 -0
  4. package/bin/secrets-cli.js +302 -0
  5. package/cli/ComposeOverrideGenerator.js +226 -0
  6. package/cli/ComposeParser.js +73 -0
  7. package/cli/ConfigGenerator.js +304 -0
  8. package/cli/ConfigManager.js +46 -0
  9. package/cli/DatabaseManager.js +94 -0
  10. package/cli/DevToolsChecker.js +21 -0
  11. package/cli/DevToolsDir.js +66 -0
  12. package/cli/DevToolsManager.js +451 -0
  13. package/cli/DockerManager.js +138 -0
  14. package/cli/FunctionManager.js +95 -0
  15. package/cli/HttpProxyRewriter.js +91 -0
  16. package/cli/Logger.js +10 -0
  17. package/cli/McpConfigManager.js +123 -0
  18. package/cli/NgrokManager.js +431 -0
  19. package/cli/ProjectCLI.js +2322 -0
  20. package/cli/PubSubManager.js +129 -0
  21. package/cli/SnapshotManager.js +88 -0
  22. package/cli/UiFormatter.js +292 -0
  23. package/cli/WebhookUrlRewriter.js +32 -0
  24. package/cli/secrets/BiometricAuth.js +125 -0
  25. package/cli/secrets/SecretInjector.js +47 -0
  26. package/cli/secrets/SecretsConfig.js +141 -0
  27. package/cli/secrets/SecretsDoctor.js +384 -0
  28. package/cli/secrets/SecretsManager.js +255 -0
  29. package/client/dist/client.d.ts +332 -0
  30. package/client/dist/client.js +507 -0
  31. package/client/dist/helpers.d.ts +62 -0
  32. package/client/dist/helpers.js +122 -0
  33. package/client/dist/index.d.ts +59 -0
  34. package/client/dist/index.js +78 -0
  35. package/client/dist/package.json +1 -0
  36. package/client/dist/types.d.ts +280 -0
  37. package/client/dist/types.js +7 -0
  38. package/config.development +46 -0
  39. package/config.test +18 -0
  40. package/guidelines/CodingStyleGuideline.md +148 -0
  41. package/guidelines/CommentingGuideline.md +10 -0
  42. package/guidelines/HttpApiImplementationGuideline.md +137 -0
  43. package/guidelines/NamingGuideline.md +182 -0
  44. package/package.json +138 -0
  45. package/patterns/api/[collectionName]/Controllers.md +62 -0
  46. package/patterns/api/[collectionName]/Logic.md +154 -0
  47. package/patterns/api/[collectionName]/Permissions.md +81 -0
  48. package/patterns/api/[collectionName]/Router.md +83 -0
  49. package/patterns/api/[collectionName]/Schemas.md +197 -0
  50. package/patterns/configs/Patterns.md +7 -0
  51. package/patterns/enums/Patterns.md +24 -0
  52. package/patterns/errorHandling/Patterns.md +185 -0
  53. package/patterns/testing/Patterns.md +232 -0
  54. package/src/Server.js +238 -0
  55. package/src/api/dashboard/Controllers.js +9 -0
  56. package/src/api/dashboard/Logic.js +76 -0
  57. package/src/api/dashboard/Router.js +11 -0
  58. package/src/api/dashboard/Schemas.js +47 -0
  59. package/src/api/data/Controllers.js +26 -0
  60. package/src/api/data/Logic.js +188 -0
  61. package/src/api/data/Router.js +16 -0
  62. package/src/api/docker/Controllers.js +33 -0
  63. package/src/api/docker/Logic.js +268 -0
  64. package/src/api/docker/Router.js +15 -0
  65. package/src/api/docker/Schemas.js +80 -0
  66. package/src/api/docs/Controllers.js +15 -0
  67. package/src/api/docs/Logic.js +85 -0
  68. package/src/api/docs/Router.js +12 -0
  69. package/src/api/export/Controllers.js +30 -0
  70. package/src/api/export/Logic.js +143 -0
  71. package/src/api/export/Router.js +18 -0
  72. package/src/api/export/Schemas.js +104 -0
  73. package/src/api/firestore/Controllers.js +152 -0
  74. package/src/api/firestore/Logic.js +474 -0
  75. package/src/api/firestore/Router.js +23 -0
  76. package/src/api/functions/Controllers.js +261 -0
  77. package/src/api/functions/Logic.js +710 -0
  78. package/src/api/functions/Router.js +50 -0
  79. package/src/api/functions/Schemas.js +193 -0
  80. package/src/api/gateway/Controllers.js +72 -0
  81. package/src/api/gateway/Logic.js +74 -0
  82. package/src/api/gateway/Router.js +10 -0
  83. package/src/api/gateway/Schemas.js +19 -0
  84. package/src/api/health/Controllers.js +14 -0
  85. package/src/api/health/Logic.js +24 -0
  86. package/src/api/health/Router.js +12 -0
  87. package/src/api/httpTraffic/Controllers.js +29 -0
  88. package/src/api/httpTraffic/Logic.js +33 -0
  89. package/src/api/httpTraffic/Router.js +9 -0
  90. package/src/api/httpTraffic/Schemas.js +23 -0
  91. package/src/api/logging/Controllers.js +80 -0
  92. package/src/api/logging/Logic.js +461 -0
  93. package/src/api/logging/Router.js +24 -0
  94. package/src/api/logging/Schemas.js +43 -0
  95. package/src/api/mqtt/Controllers.js +17 -0
  96. package/src/api/mqtt/Logic.js +66 -0
  97. package/src/api/mqtt/Router.js +12 -0
  98. package/src/api/postgres/Controllers.js +97 -0
  99. package/src/api/postgres/Logic.js +221 -0
  100. package/src/api/postgres/Router.js +21 -0
  101. package/src/api/pubsub/Controllers.js +236 -0
  102. package/src/api/pubsub/Logic.js +732 -0
  103. package/src/api/pubsub/Router.js +41 -0
  104. package/src/api/pubsub/Schemas.js +355 -0
  105. package/src/api/redis/Controllers.js +63 -0
  106. package/src/api/redis/Logic.js +239 -0
  107. package/src/api/redis/Router.js +21 -0
  108. package/src/api/scheduler/Controllers.js +27 -0
  109. package/src/api/scheduler/Logic.js +49 -0
  110. package/src/api/scheduler/Router.js +16 -0
  111. package/src/api/services/Controllers.js +26 -0
  112. package/src/api/services/Logic.js +205 -0
  113. package/src/api/services/Router.js +14 -0
  114. package/src/api/services/Schemas.js +66 -0
  115. package/src/api/snapshots/Controllers.js +37 -0
  116. package/src/api/snapshots/Logic.js +797 -0
  117. package/src/api/snapshots/Router.js +15 -0
  118. package/src/api/snapshots/Schemas.js +23 -0
  119. package/src/api/webhooks/Controllers.js +49 -0
  120. package/src/api/webhooks/Logic.js +137 -0
  121. package/src/api/webhooks/Router.js +12 -0
  122. package/src/api/webhooks/Schemas.js +31 -0
  123. package/src/configs/Application.js +147 -0
  124. package/src/configs/Default.js +13 -0
  125. package/src/consumers/BlackboxLogsConsumer.js +235 -0
  126. package/src/consumers/DockerLogsConsumer.js +687 -0
  127. package/src/db/Tables.js +66 -0
  128. package/src/db/schemas/firestore.js +18 -0
  129. package/src/db/schemas/functions.js +65 -0
  130. package/src/db/schemas/httpTraffic.js +43 -0
  131. package/src/db/schemas/logging.js +74 -0
  132. package/src/db/schemas/migrations.js +64 -0
  133. package/src/db/schemas/mqtt.js +56 -0
  134. package/src/db/schemas/pubsub.js +90 -0
  135. package/src/db/schemas/pubsubRegistry.js +22 -0
  136. package/src/db/schemas/webhooks.js +28 -0
  137. package/src/emulation/awsiot/Controllers.js +91 -0
  138. package/src/emulation/awsiot/Logic.js +70 -0
  139. package/src/emulation/awsiot/Router.js +19 -0
  140. package/src/emulation/awsiot/Server.js +100 -0
  141. package/src/emulation/firestore/Server.js +136 -0
  142. package/src/emulation/logging/Controllers.js +212 -0
  143. package/src/emulation/logging/Logic.js +416 -0
  144. package/src/emulation/logging/Router.js +36 -0
  145. package/src/emulation/logging/Schemas.js +82 -0
  146. package/src/emulation/logging/Server.js +108 -0
  147. package/src/emulation/pubsub/Controllers.js +279 -0
  148. package/src/emulation/pubsub/DefaultTopics.js +162 -0
  149. package/src/emulation/pubsub/Logic.js +427 -0
  150. package/src/emulation/pubsub/README.md +309 -0
  151. package/src/emulation/pubsub/Router.js +33 -0
  152. package/src/emulation/pubsub/Server.js +104 -0
  153. package/src/emulation/pubsub/ShadowPoller.js +276 -0
  154. package/src/emulation/pubsub/ShadowSubscriptionManager.js +199 -0
  155. package/src/enums/ContainerNames.js +106 -0
  156. package/src/enums/ErrorReason.js +28 -0
  157. package/src/enums/FunctionStatuses.js +15 -0
  158. package/src/enums/FunctionTriggerTypes.js +15 -0
  159. package/src/enums/GatewayState.js +7 -0
  160. package/src/enums/ServiceNames.js +68 -0
  161. package/src/jobs/DatabaseMaintenance.js +184 -0
  162. package/src/jobs/MessageHistoryCleanup.js +152 -0
  163. package/src/mcp/ApiClient.js +25 -0
  164. package/src/mcp/Server.js +52 -0
  165. package/src/mcp/prompts/debugging.js +104 -0
  166. package/src/mcp/resources/platform.js +118 -0
  167. package/src/mcp/tools/data.js +84 -0
  168. package/src/mcp/tools/docker.js +166 -0
  169. package/src/mcp/tools/firestore.js +162 -0
  170. package/src/mcp/tools/functions.js +380 -0
  171. package/src/mcp/tools/httpTraffic.js +69 -0
  172. package/src/mcp/tools/logging.js +174 -0
  173. package/src/mcp/tools/mqtt.js +37 -0
  174. package/src/mcp/tools/postgres.js +130 -0
  175. package/src/mcp/tools/pubsub.js +316 -0
  176. package/src/mcp/tools/redis.js +146 -0
  177. package/src/mcp/tools/services.js +169 -0
  178. package/src/mcp/tools/snapshots.js +88 -0
  179. package/src/mcp/tools/webhooks.js +115 -0
  180. package/src/middleware/DevProxy.js +67 -0
  181. package/src/middleware/ErrorCatcher.js +35 -0
  182. package/src/middleware/HttpProxy.js +215 -0
  183. package/src/middleware/Reply.js +24 -0
  184. package/src/middleware/TraceId.js +9 -0
  185. package/src/middleware/WebhookProxy.js +234 -0
  186. package/src/protocols/mqtt/Broker.js +92 -0
  187. package/src/protocols/mqtt/Handlers.js +175 -0
  188. package/src/protocols/mqtt/PubSubBridge.js +162 -0
  189. package/src/protocols/mqtt/Server.js +116 -0
  190. package/src/runtime/FunctionRunner.js +179 -0
  191. package/src/services/AppGatewayService.js +582 -0
  192. package/src/singletons/FirestoreBroadcaster.js +367 -0
  193. package/src/singletons/FunctionTriggerDispatcher.js +456 -0
  194. package/src/singletons/FunctionsService.js +418 -0
  195. package/src/singletons/HttpProxy.js +224 -0
  196. package/src/singletons/LogBroadcaster.js +159 -0
  197. package/src/singletons/Logger.js +49 -0
  198. package/src/singletons/MemoryJsonStore.js +175 -0
  199. package/src/singletons/MessageBroadcaster.js +190 -0
  200. package/src/singletons/PostgresBroadcaster.js +367 -0
  201. package/src/singletons/PostgresClient.js +180 -0
  202. package/src/singletons/RedisClient.js +184 -0
  203. package/src/singletons/SqliteStore.js +480 -0
  204. package/src/singletons/TickService.js +151 -0
  205. 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
+ }