@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,12 @@
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/docs' })
6
+
7
+ v1.get('/', Controllers.resolve)
8
+ v1.get('/(.*)', Controllers.resolve)
9
+
10
+ Router.use(v1.routes())
11
+
12
+ export { Router }
@@ -0,0 +1,30 @@
1
+ import { Logic } from './Logic.js'
2
+
3
+ export const Controllers = {
4
+ async exportAll (ctx) {
5
+ const { traceId } = ctx.state
6
+ const result = await Logic.exportAll({ traceId })
7
+ ctx.reply(result)
8
+ },
9
+ async exportPubsub (ctx) {
10
+ const { traceId } = ctx.state
11
+ const result = await Logic.exportPubsub({ traceId })
12
+ ctx.reply(result)
13
+ },
14
+ async exportLogging (ctx) {
15
+ const { traceId } = ctx.state
16
+ const result = await Logic.exportLogging({ traceId })
17
+ ctx.reply(result)
18
+ },
19
+ async exportMqtt (ctx) {
20
+ const { traceId } = ctx.state
21
+ const result = await Logic.exportMqtt({ traceId })
22
+ ctx.reply(result)
23
+ },
24
+ async importAll (ctx) {
25
+ const { traceId } = ctx.state
26
+ const { data, clearExisting } = ctx.request.body
27
+ const result = await Logic.importAll({ data, clearExisting, traceId })
28
+ ctx.reply(result)
29
+ }
30
+ }
@@ -0,0 +1,143 @@
1
+ import { SqliteStore } from '../../singletons/SqliteStore.js'
2
+ import {
3
+ PUBSUB_TOPICS,
4
+ PUBSUB_SUBSCRIPTIONS,
5
+ PUBSUB_MESSAGES,
6
+ LOGGING_ENTRIES,
7
+ MQTT_CLIENTS,
8
+ MQTT_MESSAGES
9
+ } from '../../db/Tables.js'
10
+
11
+ export const Logic = {
12
+ async exportAll (params) {
13
+ const { traceId } = params
14
+ const topics = SqliteStore.list(PUBSUB_TOPICS)
15
+ const subscriptions = SqliteStore.list(PUBSUB_SUBSCRIPTIONS)
16
+ const messages = SqliteStore.list(PUBSUB_MESSAGES)
17
+ const logEntries = SqliteStore.list(LOGGING_ENTRIES)
18
+ const mqttClients = SqliteStore.list(MQTT_CLIENTS)
19
+ const mqttMessages = SqliteStore.list(MQTT_MESSAGES)
20
+ return {
21
+ data: {
22
+ pubsub: {
23
+ topics: topics.data,
24
+ subscriptions: subscriptions.data,
25
+ messages: messages.data
26
+ },
27
+ logging: {
28
+ entries: logEntries.data
29
+ },
30
+ mqtt: {
31
+ clients: mqttClients.data,
32
+ messages: mqttMessages.data
33
+ },
34
+ exportedAt: new Date().toISOString()
35
+ },
36
+ traceId
37
+ }
38
+ },
39
+ async exportPubsub (params) {
40
+ const { traceId } = params
41
+ const topics = SqliteStore.list(PUBSUB_TOPICS)
42
+ const subscriptions = SqliteStore.list(PUBSUB_SUBSCRIPTIONS)
43
+ const messages = SqliteStore.list(PUBSUB_MESSAGES)
44
+ return {
45
+ data: {
46
+ topics: topics.data,
47
+ subscriptions: subscriptions.data,
48
+ messages: messages.data,
49
+ exportedAt: new Date().toISOString()
50
+ },
51
+ traceId
52
+ }
53
+ },
54
+ async exportLogging (params) {
55
+ const { traceId } = params
56
+ const logEntries = SqliteStore.list(LOGGING_ENTRIES)
57
+ return {
58
+ data: {
59
+ entries: logEntries.data,
60
+ exportedAt: new Date().toISOString()
61
+ },
62
+ traceId
63
+ }
64
+ },
65
+ async exportMqtt (params) {
66
+ const { traceId } = params
67
+ const mqttClients = SqliteStore.list(MQTT_CLIENTS)
68
+ const mqttMessages = SqliteStore.list(MQTT_MESSAGES)
69
+ return {
70
+ data: {
71
+ clients: mqttClients.data,
72
+ messages: mqttMessages.data,
73
+ exportedAt: new Date().toISOString()
74
+ },
75
+ traceId
76
+ }
77
+ },
78
+ async importAll (params) {
79
+ const { data, clearExisting = false, traceId } = params
80
+ const imported = {
81
+ topics: 0,
82
+ subscriptions: 0,
83
+ messages: 0,
84
+ logEntries: 0,
85
+ mqttClients: 0,
86
+ mqttMessages: 0
87
+ }
88
+ if (clearExisting) {
89
+ SqliteStore.clear('pubsub_topics')
90
+ SqliteStore.clear('pubsub_subscriptions')
91
+ SqliteStore.clear(PUBSUB_MESSAGES)
92
+ SqliteStore.clear(LOGGING_ENTRIES)
93
+ SqliteStore.clear(MQTT_CLIENTS)
94
+ SqliteStore.clear(MQTT_MESSAGES)
95
+ }
96
+ if (data.pubsub) {
97
+ if (data.pubsub.topics) {
98
+ for (const topic of data.pubsub.topics) {
99
+ SqliteStore.create('pubsub_topics', topic)
100
+ imported.topics++
101
+ }
102
+ }
103
+ if (data.pubsub.subscriptions) {
104
+ for (const subscription of data.pubsub.subscriptions) {
105
+ SqliteStore.create('pubsub_subscriptions', subscription)
106
+ imported.subscriptions++
107
+ }
108
+ }
109
+ if (data.pubsub.messages) {
110
+ for (const message of data.pubsub.messages) {
111
+ SqliteStore.create(PUBSUB_MESSAGES, message)
112
+ imported.messages++
113
+ }
114
+ }
115
+ }
116
+ if (data.logging && data.logging.entries) {
117
+ for (const entry of data.logging.entries) {
118
+ SqliteStore.create(LOGGING_ENTRIES, entry)
119
+ imported.logEntries++
120
+ }
121
+ }
122
+ if (data.mqtt) {
123
+ if (data.mqtt.clients) {
124
+ for (const client of data.mqtt.clients) {
125
+ SqliteStore.create(MQTT_CLIENTS, client)
126
+ imported.mqttClients++
127
+ }
128
+ }
129
+ if (data.mqtt.messages) {
130
+ for (const message of data.mqtt.messages) {
131
+ SqliteStore.create(MQTT_MESSAGES, message)
132
+ imported.mqttMessages++
133
+ }
134
+ }
135
+ }
136
+ SqliteStore.flushAll()
137
+ return {
138
+ message: 'Platform state imported successfully',
139
+ imported,
140
+ traceId
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,18 @@
1
+ import KoaRouter from 'koa-router'
2
+ import { Controllers } from './Controllers.js'
3
+
4
+ const Router = new KoaRouter()
5
+ const v1Export = new KoaRouter({ prefix: '/v1/export' })
6
+ const v1Import = new KoaRouter({ prefix: '/v1/import' })
7
+
8
+ v1Export.post('/all', Controllers.exportAll)
9
+ v1Export.post('/pubsub', Controllers.exportPubsub)
10
+ v1Export.post('/logging', Controllers.exportLogging)
11
+ v1Export.post('/mqtt', Controllers.exportMqtt)
12
+
13
+ v1Import.post('/all', Controllers.importAll)
14
+
15
+ Router.use(v1Export.routes())
16
+ Router.use(v1Import.routes())
17
+
18
+ export { Router }
@@ -0,0 +1,104 @@
1
+ import { Joi } from '@gokiteam/koa'
2
+
3
+ export const Schemas = {
4
+ exportAll: {
5
+ request: {
6
+ body: {}
7
+ },
8
+ responses: {
9
+ success: {
10
+ data: Joi.object({
11
+ pubsub: Joi.object({
12
+ topics: Joi.array().required(),
13
+ subscriptions: Joi.array().required(),
14
+ messages: Joi.array().required()
15
+ }).required(),
16
+ logging: Joi.object({
17
+ entries: Joi.array().required()
18
+ }).required(),
19
+ mqtt: Joi.object({
20
+ clients: Joi.array().required(),
21
+ messages: Joi.array().required()
22
+ }).required(),
23
+ exportedAt: Joi.string().isoDate().required()
24
+ }).required()
25
+ }
26
+ }
27
+ },
28
+ exportPubsub: {
29
+ request: {
30
+ body: {}
31
+ },
32
+ responses: {
33
+ success: {
34
+ data: Joi.object({
35
+ topics: Joi.array().required(),
36
+ subscriptions: Joi.array().required(),
37
+ messages: Joi.array().required(),
38
+ exportedAt: Joi.string().isoDate().required()
39
+ }).required()
40
+ }
41
+ }
42
+ },
43
+ exportLogging: {
44
+ request: {
45
+ body: {}
46
+ },
47
+ responses: {
48
+ success: {
49
+ data: Joi.object({
50
+ entries: Joi.array().required(),
51
+ exportedAt: Joi.string().isoDate().required()
52
+ }).required()
53
+ }
54
+ }
55
+ },
56
+ exportMqtt: {
57
+ request: {
58
+ body: {}
59
+ },
60
+ responses: {
61
+ success: {
62
+ data: Joi.object({
63
+ clients: Joi.array().required(),
64
+ messages: Joi.array().required(),
65
+ exportedAt: Joi.string().isoDate().required()
66
+ }).required()
67
+ }
68
+ }
69
+ },
70
+ importAll: {
71
+ request: {
72
+ body: {
73
+ data: Joi.object({
74
+ pubsub: Joi.object({
75
+ topics: Joi.array().required(),
76
+ subscriptions: Joi.array().required(),
77
+ messages: Joi.array().required()
78
+ }),
79
+ logging: Joi.object({
80
+ entries: Joi.array().required()
81
+ }),
82
+ mqtt: Joi.object({
83
+ clients: Joi.array().required(),
84
+ messages: Joi.array().required()
85
+ })
86
+ }).required(),
87
+ clearExisting: Joi.boolean().default(false)
88
+ }
89
+ },
90
+ responses: {
91
+ success: {
92
+ message: Joi.string().required(),
93
+ imported: Joi.object({
94
+ topics: Joi.number().integer().min(0).required(),
95
+ subscriptions: Joi.number().integer().min(0).required(),
96
+ messages: Joi.number().integer().min(0).required(),
97
+ logEntries: Joi.number().integer().min(0).required(),
98
+ mqttClients: Joi.number().integer().min(0).required(),
99
+ mqttMessages: Joi.number().integer().min(0).required()
100
+ }).required()
101
+ }
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,152 @@
1
+ import { Logic } from './Logic.js'
2
+ import { FirestoreBroadcaster } from '../../singletons/FirestoreBroadcaster.js'
3
+ import { Application } from '../../configs/Application.js'
4
+
5
+ export const Controllers = {
6
+ async listProjects (ctx) {
7
+ const { traceId } = ctx.state
8
+ const result = Logic.listProjects({ traceId })
9
+ ctx.reply(result)
10
+ },
11
+
12
+ async listCollections (ctx) {
13
+ const { traceId } = ctx.state
14
+ const { projectId } = ctx.request.body
15
+ const result = await Logic.listCollections({ projectId, traceId })
16
+ ctx.reply(result)
17
+ },
18
+
19
+ async listDocuments (ctx) {
20
+ const { traceId } = ctx.state
21
+ const { collectionPath, page, projectId } = ctx.request.body
22
+ const result = await Logic.listDocuments({ collectionPath, page, projectId, traceId })
23
+ ctx.reply(result)
24
+ },
25
+
26
+ async getDocument (ctx) {
27
+ const { traceId } = ctx.state
28
+ const { collectionPath, documentId, projectId } = ctx.request.body
29
+ const result = await Logic.getDocument({ collectionPath, documentId, projectId, traceId })
30
+ ctx.reply(result)
31
+ },
32
+
33
+ async createDocument (ctx) {
34
+ const { traceId } = ctx.state
35
+ const { collectionPath, documentId, fields, projectId } = ctx.request.body
36
+ const result = await Logic.createDocument({ collectionPath, documentId, fields, projectId, traceId })
37
+ ctx.reply(result)
38
+ },
39
+
40
+ async updateDocument (ctx) {
41
+ const { traceId } = ctx.state
42
+ const { collectionPath, documentId, fields, projectId } = ctx.request.body
43
+ const result = await Logic.updateDocument({ collectionPath, documentId, fields, projectId, traceId })
44
+ ctx.reply(result)
45
+ },
46
+
47
+ async deleteDocument (ctx) {
48
+ const { traceId } = ctx.state
49
+ const { collectionPath, documentId, projectId } = ctx.request.body
50
+ const result = await Logic.deleteDocument({ collectionPath, documentId, projectId, traceId })
51
+ ctx.reply(result)
52
+ },
53
+
54
+ async deleteByQuery (ctx) {
55
+ const { traceId } = ctx.state
56
+ const { collectionPath, where, projectId } = ctx.request.body
57
+ const result = await Logic.deleteByQuery({ collectionPath, where, projectId, traceId })
58
+ ctx.reply(result)
59
+ },
60
+
61
+ async deleteByPrefix (ctx) {
62
+ const { traceId } = ctx.state
63
+ const { collectionPath, prefix, projectId } = ctx.request.body
64
+ const result = await Logic.deleteByPrefix({ collectionPath, prefix, projectId, traceId })
65
+ ctx.reply(result)
66
+ },
67
+
68
+ async deleteBatch (ctx) {
69
+ const { traceId } = ctx.state
70
+ const { collectionPath, documentIds, projectId } = ctx.request.body
71
+ const result = await Logic.deleteBatch({ collectionPath, documentIds, projectId, traceId })
72
+ ctx.reply(result)
73
+ },
74
+
75
+ async executeQuery (ctx) {
76
+ const { traceId } = ctx.state
77
+ const { collectionPath, where, orderBy, limit, projectId } = ctx.request.body
78
+ const result = await Logic.executeQuery({ collectionPath, where, orderBy, limit, projectId, traceId })
79
+ ctx.reply(result)
80
+ },
81
+
82
+ async clearCollection (ctx) {
83
+ const { traceId } = ctx.state
84
+ const { collectionPath, projectId } = ctx.request.body
85
+ const result = await Logic.clearCollection({ collectionPath, projectId, traceId })
86
+ ctx.reply(result)
87
+ },
88
+
89
+ /**
90
+ * SSE streaming endpoint for real-time Firestore change updates
91
+ * GET /v1/firestore/changes/stream?projectId=X&collectionPath=Y
92
+ */
93
+ async streamChanges (ctx) {
94
+ const { traceId } = ctx.state
95
+ const { projectId, collectionPath } = ctx.query
96
+
97
+ // Validate required parameters
98
+ if (!collectionPath) {
99
+ ctx.status = 400
100
+ ctx.body = {
101
+ success: false,
102
+ status: 'error',
103
+ message: 'collectionPath is required',
104
+ traceId
105
+ }
106
+ return
107
+ }
108
+
109
+ const resolvedProjectId = projectId || Application.firestore.projectId
110
+
111
+ // Set SSE headers
112
+ ctx.set({
113
+ 'Content-Type': 'text/event-stream',
114
+ 'Cache-Control': 'no-cache',
115
+ Connection: 'keep-alive',
116
+ 'X-Accel-Buffering': 'no' // Disable Nginx buffering
117
+ })
118
+
119
+ ctx.status = 200
120
+ ctx.respond = false // Tell Koa not to auto-finish the response
121
+
122
+ // Send initial connection event
123
+ ctx.res.write(`event: connected\ndata: ${JSON.stringify({
124
+ connected: true,
125
+ traceId,
126
+ projectId: resolvedProjectId,
127
+ collectionPath
128
+ })}\n\n`)
129
+
130
+ // Register client with broadcaster
131
+ const filters = {
132
+ projectId: resolvedProjectId,
133
+ collectionPath
134
+ }
135
+
136
+ FirestoreBroadcaster.addClient(traceId, ctx, filters)
137
+
138
+ // Wait indefinitely until client disconnects
139
+ // Heartbeat is handled by the shared FirestoreBroadcaster interval
140
+ await new Promise((resolve) => {
141
+ ctx.req.on('close', () => {
142
+ FirestoreBroadcaster.removeClient(traceId)
143
+ resolve()
144
+ })
145
+
146
+ ctx.req.on('error', () => {
147
+ FirestoreBroadcaster.removeClient(traceId)
148
+ resolve()
149
+ })
150
+ })
151
+ }
152
+ }