@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,41 @@
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/pubsub' })
6
+
7
+ // Topics
8
+ v1.post('/topics/list', Controllers.listTopics)
9
+ v1.post('/topics/create', Controllers.createTopic)
10
+ v1.post('/topics/delete', Controllers.deleteTopic)
11
+ v1.post('/topics/suggest', Controllers.suggestTopics)
12
+
13
+ // Subscriptions
14
+ v1.post('/subscriptions/list', Controllers.listSubscriptions)
15
+ v1.post('/subscriptions/create', Controllers.createSubscription)
16
+ v1.post('/subscriptions/delete', Controllers.deleteSubscription)
17
+
18
+ // Messages
19
+ v1.post('/messages/list', Controllers.listMessages)
20
+ v1.post('/messages/publish', Controllers.publishMessage)
21
+ v1.post('/messages/pull', Controllers.pullMessages)
22
+ v1.get('/messages/stream', Controllers.streamMessages) // SSE endpoint
23
+ v1.post('/messages/search', Controllers.searchMessages)
24
+
25
+ // History
26
+ v1.post('/messages/history', Controllers.getHistory)
27
+ v1.post('/history/clear', Controllers.clearHistory)
28
+ v1.post('/history/stats', Controllers.getHistoryStats)
29
+
30
+ // Testing Helpers
31
+ v1.post('/messages/wait-for', Controllers.waitForMessage)
32
+ v1.post('/messages/assert-published', Controllers.assertMessagePublished)
33
+
34
+ // Registry (persistent topic declarations)
35
+ v1.post('/registry/register', Controllers.registerTopics)
36
+ v1.post('/registry/list', Controllers.listRegistry)
37
+ v1.post('/registry/unregister', Controllers.unregisterTopics)
38
+
39
+ Router.use(v1.routes())
40
+
41
+ export { Router }
@@ -0,0 +1,355 @@
1
+ import { Joi } from '@gokiteam/koa'
2
+
3
+ export const Schemas = {
4
+ listTopics: {
5
+ request: {
6
+ body: {
7
+ filter: Joi.object().optional(),
8
+ page: Joi.object({
9
+ limit: Joi.number().integer().min(1).max(1000).default(50),
10
+ offset: Joi.number().integer().min(0).default(0)
11
+ }).optional()
12
+ }
13
+ },
14
+ responses: {
15
+ success: {
16
+ topics: Joi.array().items(Joi.object()).required(),
17
+ total: Joi.number().integer().min(0).required()
18
+ }
19
+ }
20
+ },
21
+
22
+ createTopic: {
23
+ request: {
24
+ body: {
25
+ topicName: Joi.string().required()
26
+ }
27
+ },
28
+ responses: {
29
+ success: {
30
+ topic: Joi.object().required()
31
+ }
32
+ }
33
+ },
34
+
35
+ deleteTopic: {
36
+ request: {
37
+ body: {
38
+ topicName: Joi.string().required()
39
+ }
40
+ },
41
+ responses: {
42
+ success: {
43
+ message: Joi.string().required()
44
+ }
45
+ }
46
+ },
47
+
48
+ suggestTopics: {
49
+ request: {
50
+ body: {
51
+ query: Joi.string().allow('').default(''),
52
+ limit: Joi.number().integer().min(1).max(100).default(10)
53
+ }
54
+ },
55
+ responses: {
56
+ success: {
57
+ topics: Joi.array().items(
58
+ Joi.object({
59
+ name: Joi.string().required(),
60
+ messageCount: Joi.number().integer().min(0).required(),
61
+ lastPublishTime: Joi.string().isoDate().allow(null).optional()
62
+ })
63
+ ).required()
64
+ }
65
+ }
66
+ },
67
+
68
+ listSubscriptions: {
69
+ request: {
70
+ body: {
71
+ filter: Joi.object().optional(),
72
+ page: Joi.object({
73
+ limit: Joi.number().integer().min(1).max(1000).default(50),
74
+ offset: Joi.number().integer().min(0).default(0)
75
+ }).optional()
76
+ }
77
+ },
78
+ responses: {
79
+ success: {
80
+ subscriptions: Joi.array().items(Joi.object()).required(),
81
+ total: Joi.number().integer().min(0).required()
82
+ }
83
+ }
84
+ },
85
+
86
+ createSubscription: {
87
+ request: {
88
+ body: {
89
+ topicName: Joi.string().required(),
90
+ subscriptionName: Joi.string().required()
91
+ }
92
+ },
93
+ responses: {
94
+ success: {
95
+ subscription: Joi.object().required()
96
+ }
97
+ }
98
+ },
99
+
100
+ deleteSubscription: {
101
+ request: {
102
+ body: {
103
+ subscriptionName: Joi.string().required()
104
+ }
105
+ },
106
+ responses: {
107
+ success: {
108
+ message: Joi.string().required()
109
+ }
110
+ }
111
+ },
112
+
113
+ listMessages: {
114
+ request: {
115
+ body: {
116
+ filter: Joi.object().optional(),
117
+ page: Joi.object({
118
+ limit: Joi.number().integer().min(1).max(1000).default(50),
119
+ offset: Joi.number().integer().min(0).default(0)
120
+ }).optional()
121
+ }
122
+ },
123
+ responses: {
124
+ success: {
125
+ messages: Joi.array().items(Joi.object()).required(),
126
+ total: Joi.number().integer().min(0).required()
127
+ }
128
+ }
129
+ },
130
+
131
+ publishMessage: {
132
+ request: {
133
+ body: {
134
+ topicName: Joi.string().required(),
135
+ message: Joi.alternatives().try(Joi.string(), Joi.object()).required(),
136
+ attributes: Joi.object().optional()
137
+ }
138
+ },
139
+ responses: {
140
+ success: {
141
+ messageIds: Joi.array().items(Joi.string()).required()
142
+ }
143
+ }
144
+ },
145
+
146
+ pullMessages: {
147
+ request: {
148
+ body: {
149
+ subscriptionName: Joi.string().required(),
150
+ maxMessages: Joi.number().integer().min(1).max(1000).default(10)
151
+ }
152
+ },
153
+ responses: {
154
+ success: {
155
+ messages: Joi.array().items(Joi.object()).required()
156
+ }
157
+ }
158
+ },
159
+
160
+ getHistory: {
161
+ request: {
162
+ body: {
163
+ filter: Joi.object({
164
+ topic: Joi.string().optional(),
165
+ sender: Joi.string().optional(),
166
+ before: Joi.string().isoDate().optional(),
167
+ timeRange: Joi.object({
168
+ start: Joi.string().isoDate().required(),
169
+ end: Joi.string().isoDate().required()
170
+ }).optional()
171
+ }).optional(),
172
+ page: Joi.object({
173
+ limit: Joi.number().integer().min(1).max(1000).default(50),
174
+ offset: Joi.number().integer().min(0).default(0)
175
+ }).optional()
176
+ }
177
+ },
178
+ responses: {
179
+ success: {
180
+ messages: Joi.array().items(
181
+ Joi.object({
182
+ id: Joi.string().required(),
183
+ message_id: Joi.string().required(),
184
+ data: Joi.string().allow('').optional(),
185
+ attributes: Joi.object().optional(),
186
+ ordering_key: Joi.string().allow('').optional(),
187
+ publish_time: Joi.string().isoDate().required(),
188
+ topic: Joi.string().required(),
189
+ sender: Joi.string().allow(null).optional(),
190
+ view_count: Joi.number().integer().min(0).required(),
191
+ last_viewed_at: Joi.number().integer().allow(null).optional(),
192
+ created_at: Joi.number().integer().required()
193
+ })
194
+ ).required(),
195
+ total: Joi.number().integer().min(0).required()
196
+ }
197
+ }
198
+ },
199
+
200
+ searchMessages: {
201
+ request: {
202
+ body: {
203
+ query: Joi.string().required(),
204
+ searchIn: Joi.array().items(Joi.string().valid('data', 'attributes')).default(['data']),
205
+ filter: Joi.object({
206
+ topic: Joi.string().optional(),
207
+ sender: Joi.string().optional()
208
+ }).optional(),
209
+ page: Joi.object({
210
+ limit: Joi.number().integer().min(1).max(1000).default(50),
211
+ offset: Joi.number().integer().min(0).default(0)
212
+ }).optional()
213
+ }
214
+ },
215
+ responses: {
216
+ success: {
217
+ messages: Joi.array().items(Joi.object()).required(),
218
+ total: Joi.number().integer().min(0).required()
219
+ }
220
+ }
221
+ },
222
+
223
+ clearHistory: {
224
+ request: {
225
+ body: {
226
+ keepCount: Joi.number().integer().min(100).optional(),
227
+ topic: Joi.string().optional()
228
+ }
229
+ },
230
+ responses: {
231
+ success: {
232
+ deletedCount: Joi.number().integer().min(0).required()
233
+ }
234
+ }
235
+ },
236
+
237
+ getHistoryStats: {
238
+ request: {
239
+ body: {}
240
+ },
241
+ responses: {
242
+ success: {
243
+ totalMessages: Joi.number().integer().min(0).required(),
244
+ totalTopics: Joi.number().integer().min(0).required(),
245
+ oldestMessage: Joi.string().isoDate().allow(null).optional(),
246
+ newestMessage: Joi.string().isoDate().allow(null).optional(),
247
+ topTopics: Joi.array().items(
248
+ Joi.object({
249
+ topic: Joi.string().required(),
250
+ count: Joi.number().integer().min(0).required()
251
+ })
252
+ ).required()
253
+ }
254
+ }
255
+ },
256
+
257
+ waitForMessage: {
258
+ request: {
259
+ body: {
260
+ filter: Joi.object({
261
+ topic: Joi.string().required(),
262
+ predicate: Joi.object({
263
+ jsonPath: Joi.string().required(),
264
+ operator: Joi.string().valid('equals', 'notEquals', 'contains', 'greaterThan', 'lessThan').required(),
265
+ value: Joi.any().required()
266
+ }).optional(),
267
+ sender: Joi.string().optional()
268
+ }).required(),
269
+ timeout: Joi.number().integer().min(100).max(30000).default(5000),
270
+ traceId: Joi.string().optional()
271
+ }
272
+ },
273
+ responses: {
274
+ success: {
275
+ message: Joi.object().required(),
276
+ foundAt: Joi.number().integer().required(),
277
+ traceId: Joi.string().required()
278
+ }
279
+ }
280
+ },
281
+
282
+ assertMessagePublished: {
283
+ request: {
284
+ body: {
285
+ filter: Joi.object({
286
+ topic: Joi.string().required(),
287
+ since: Joi.string().isoDate().optional(),
288
+ dataContains: Joi.string().optional(),
289
+ attributesMatch: Joi.object().optional()
290
+ }).required(),
291
+ traceId: Joi.string().optional()
292
+ }
293
+ },
294
+ responses: {
295
+ success: {
296
+ found: Joi.boolean().required(),
297
+ count: Joi.number().integer().min(0).required(),
298
+ firstMatch: Joi.object().optional(),
299
+ traceId: Joi.string().required()
300
+ }
301
+ }
302
+ },
303
+
304
+ // Registry
305
+ registerTopics: {
306
+ request: {
307
+ body: {
308
+ projectName: Joi.string().required(),
309
+ topics: Joi.array().items(
310
+ Joi.alternatives().try(
311
+ Joi.string(),
312
+ Joi.object({
313
+ name: Joi.string().required(),
314
+ subscription: Joi.string().optional()
315
+ })
316
+ )
317
+ ).min(1).required()
318
+ }
319
+ },
320
+ responses: {
321
+ success: {
322
+ registered: Joi.array().items(Joi.object()).required(),
323
+ count: Joi.number().integer().min(0).required()
324
+ }
325
+ }
326
+ },
327
+
328
+ listRegistry: {
329
+ request: {
330
+ body: {
331
+ projectName: Joi.string().optional()
332
+ }
333
+ },
334
+ responses: {
335
+ success: {
336
+ entries: Joi.array().items(Joi.object()).required(),
337
+ total: Joi.number().integer().min(0).required()
338
+ }
339
+ }
340
+ },
341
+
342
+ unregisterTopics: {
343
+ request: {
344
+ body: {
345
+ projectName: Joi.string().required(),
346
+ topicNames: Joi.array().items(Joi.string()).min(1).required()
347
+ }
348
+ },
349
+ responses: {
350
+ success: {
351
+ removed: Joi.number().integer().min(0).required()
352
+ }
353
+ }
354
+ }
355
+ }
@@ -0,0 +1,63 @@
1
+ import { Logic } from './Logic.js'
2
+
3
+ export const Controllers = {
4
+ async testConnection (ctx) {
5
+ const { traceId } = ctx.state
6
+ const result = await Logic.testConnection({ traceId })
7
+ ctx.reply(result)
8
+ },
9
+
10
+ async getInfo (ctx) {
11
+ const { traceId } = ctx.state
12
+ const result = await Logic.getInfo({ traceId })
13
+ ctx.reply(result)
14
+ },
15
+
16
+ async scanKeys (ctx) {
17
+ const { traceId } = ctx.state
18
+ const { pattern, cursor, count } = ctx.request.body
19
+ const result = await Logic.scanKeys({ pattern, cursor, count, traceId })
20
+ ctx.reply(result)
21
+ },
22
+
23
+ async getKey (ctx) {
24
+ const { traceId } = ctx.state
25
+ const { key } = ctx.request.body
26
+ const result = await Logic.getKey({ key, traceId })
27
+ ctx.reply(result)
28
+ },
29
+
30
+ async getKeyTtl (ctx) {
31
+ const { traceId } = ctx.state
32
+ const { key } = ctx.request.body
33
+ const result = await Logic.getKeyTtl({ key, traceId })
34
+ ctx.reply(result)
35
+ },
36
+
37
+ async deleteKey (ctx) {
38
+ const { traceId } = ctx.state
39
+ const { key } = ctx.request.body
40
+ const result = await Logic.deleteKey({ key, traceId })
41
+ ctx.reply(result)
42
+ },
43
+
44
+ async updateKey (ctx) {
45
+ const { traceId } = ctx.state
46
+ const { key, value, ttl } = ctx.request.body
47
+ const result = await Logic.updateKey({ key, value, ttl, traceId })
48
+ ctx.reply(result)
49
+ },
50
+
51
+ async deleteAll (ctx) {
52
+ const { traceId } = ctx.state
53
+ const result = await Logic.deleteAll({ traceId })
54
+ ctx.reply(result)
55
+ },
56
+
57
+ async waitForCondition (ctx) {
58
+ const { traceId } = ctx.state
59
+ const { condition, timeout, pollInterval } = ctx.request.body
60
+ const result = await Logic.waitForCondition({ condition, timeout, pollInterval, traceId })
61
+ ctx.reply(result)
62
+ }
63
+ }
@@ -0,0 +1,239 @@
1
+ import { RedisClient } from '../../singletons/RedisClient.js'
2
+
3
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
4
+
5
+ const deepEqual = (a, b) => {
6
+ if (a === b) return true
7
+ if (a == null || b == null) return false
8
+ if (typeof a !== 'object' || typeof b !== 'object') return false
9
+ const keysA = Object.keys(a)
10
+ const keysB = Object.keys(b)
11
+ if (keysA.length !== keysB.length) return false
12
+ for (const key of keysA) {
13
+ if (!keysB.includes(key)) return false
14
+ if (!deepEqual(a[key], b[key])) return false
15
+ }
16
+ return true
17
+ }
18
+
19
+ export const Logic = {
20
+ async testConnection (params) {
21
+ const { traceId } = params
22
+ try {
23
+ const result = await RedisClient.testConnection()
24
+ return {
25
+ ...result,
26
+ traceId
27
+ }
28
+ } catch (error) {
29
+ return {
30
+ status: 'error',
31
+ message: error.message,
32
+ traceId
33
+ }
34
+ }
35
+ },
36
+
37
+ async getInfo (params) {
38
+ const { traceId } = params
39
+ try {
40
+ const info = await RedisClient.getInfo()
41
+ const dbSize = await RedisClient.getDbSize()
42
+ return {
43
+ info,
44
+ dbSize,
45
+ traceId
46
+ }
47
+ } catch (error) {
48
+ return {
49
+ status: 'error',
50
+ message: error.message,
51
+ traceId
52
+ }
53
+ }
54
+ },
55
+
56
+ async scanKeys (params) {
57
+ const { pattern = '*', cursor = '0', count = 100, traceId } = params
58
+ try {
59
+ const result = await RedisClient.scanKeys(pattern, cursor, count)
60
+ // Get type for each key
61
+ const keysWithTypes = await Promise.all(
62
+ result.keys.map(async (key) => {
63
+ const type = await RedisClient.getKeyType(key)
64
+ const ttl = await RedisClient.getKeyTtl(key)
65
+ return { key, type, ttl }
66
+ })
67
+ )
68
+ return {
69
+ keys: keysWithTypes,
70
+ cursor: result.cursor,
71
+ done: result.done,
72
+ traceId
73
+ }
74
+ } catch (error) {
75
+ return {
76
+ status: 'error',
77
+ message: error.message,
78
+ traceId
79
+ }
80
+ }
81
+ },
82
+
83
+ async getKey (params) {
84
+ const { key, traceId } = params
85
+ try {
86
+ if (!key) {
87
+ return {
88
+ status: 'error',
89
+ message: 'Key is required',
90
+ traceId
91
+ }
92
+ }
93
+ const result = await RedisClient.getKeyValue(key)
94
+ return {
95
+ ...result,
96
+ traceId
97
+ }
98
+ } catch (error) {
99
+ return {
100
+ status: 'error',
101
+ message: error.message,
102
+ traceId
103
+ }
104
+ }
105
+ },
106
+
107
+ async getKeyTtl (params) {
108
+ const { key, traceId } = params
109
+ try {
110
+ if (!key) {
111
+ return {
112
+ status: 'error',
113
+ message: 'Key is required',
114
+ traceId
115
+ }
116
+ }
117
+ const ttl = await RedisClient.getKeyTtl(key)
118
+ return {
119
+ key,
120
+ ttl,
121
+ traceId
122
+ }
123
+ } catch (error) {
124
+ return {
125
+ status: 'error',
126
+ message: error.message,
127
+ traceId
128
+ }
129
+ }
130
+ },
131
+
132
+ async deleteKey (params) {
133
+ const { key, traceId } = params
134
+ try {
135
+ if (!key) {
136
+ return {
137
+ status: 'error',
138
+ message: 'Key is required',
139
+ traceId
140
+ }
141
+ }
142
+ const deleted = await RedisClient.deleteKey(key)
143
+ return {
144
+ deleted: deleted === 1,
145
+ key,
146
+ traceId
147
+ }
148
+ } catch (error) {
149
+ return {
150
+ status: 'error',
151
+ message: error.message,
152
+ traceId
153
+ }
154
+ }
155
+ },
156
+
157
+ async updateKey (params) {
158
+ const { key, value, ttl, traceId } = params
159
+ try {
160
+ if (!key) {
161
+ return {
162
+ status: 'error',
163
+ message: 'Key is required',
164
+ traceId
165
+ }
166
+ }
167
+ if (value === undefined) {
168
+ return {
169
+ status: 'error',
170
+ message: 'Value is required',
171
+ traceId
172
+ }
173
+ }
174
+ await RedisClient.setKey(key, value, ttl)
175
+ return {
176
+ updated: true,
177
+ key,
178
+ traceId
179
+ }
180
+ } catch (error) {
181
+ return {
182
+ status: 'error',
183
+ message: error.message,
184
+ traceId
185
+ }
186
+ }
187
+ },
188
+
189
+ async deleteAll (params) {
190
+ const { traceId } = params
191
+ try {
192
+ const countBefore = await RedisClient.getDbSize()
193
+ await RedisClient.deleteAll()
194
+ return {
195
+ deleted: countBefore,
196
+ traceId
197
+ }
198
+ } catch (error) {
199
+ return {
200
+ status: 'error',
201
+ message: error.message,
202
+ traceId
203
+ }
204
+ }
205
+ },
206
+
207
+ async waitForCondition (params) {
208
+ const { condition, timeout = 5000, pollInterval = 100, traceId } = params
209
+ const startTime = Date.now()
210
+ try {
211
+ while (Date.now() - startTime < timeout) {
212
+ await RedisClient.connect()
213
+ const keyData = await RedisClient.getKeyValue(condition.key)
214
+ const actualValue = keyData.value
215
+ const met = deepEqual(actualValue, condition.expect)
216
+ if (met) {
217
+ return {
218
+ met: true,
219
+ foundAt: Date.now() - startTime,
220
+ actualValue,
221
+ traceId
222
+ }
223
+ }
224
+ await sleep(pollInterval)
225
+ }
226
+ return {
227
+ status: 'error',
228
+ message: 'Timeout waiting for condition',
229
+ traceId
230
+ }
231
+ } catch (error) {
232
+ return {
233
+ status: 'error',
234
+ message: error.message,
235
+ traceId
236
+ }
237
+ }
238
+ }
239
+ }
@@ -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/redis' })
6
+
7
+ v1.post('/connection/test', Controllers.testConnection)
8
+ v1.post('/info', Controllers.getInfo)
9
+ v1.post('/keys/scan', Controllers.scanKeys)
10
+ v1.post('/keys/get', Controllers.getKey)
11
+ v1.post('/keys/ttl', Controllers.getKeyTtl)
12
+ v1.post('/keys/delete', Controllers.deleteKey)
13
+ v1.post('/keys/update', Controllers.updateKey)
14
+ v1.post('/keys/delete-all', Controllers.deleteAll)
15
+
16
+ // Testing Helpers
17
+ v1.post('/keys/wait-for', Controllers.waitForCondition)
18
+
19
+ Router.use(v1.routes())
20
+
21
+ export { Router }