@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,279 @@
1
+ import {
2
+ CreateTopic,
3
+ GetTopic,
4
+ ListTopics,
5
+ DeleteTopic,
6
+ PublishMessages,
7
+ CreateSubscription,
8
+ GetSubscription,
9
+ ListSubscriptions,
10
+ DeleteSubscription,
11
+ PullMessages,
12
+ AcknowledgeMessages
13
+ } from './Logic.js'
14
+
15
+ /**
16
+ * Helper to send GCP-style error responses
17
+ */
18
+ const sendError = (ctx, errorCode, errorMessage, statusCode = 400) => {
19
+ ctx.status = statusCode
20
+ ctx.body = {
21
+ error: {
22
+ code: statusCode,
23
+ message: errorMessage,
24
+ status: errorCode
25
+ }
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Create Topic Controller
31
+ * PUT /v1/projects/{project}/topics/{topic}
32
+ */
33
+ export const createTopicController = async ctx => {
34
+ const { project, topic } = ctx.params
35
+
36
+ const result = CreateTopic({
37
+ projectId: project,
38
+ topicId: topic
39
+ })
40
+
41
+ if (!result.isSuccessful) {
42
+ const statusCode = result.errorCode === 'ALREADY_EXISTS' ? 409 : 400
43
+ return sendError(ctx, result.errorCode, result.errorMessage, statusCode)
44
+ }
45
+
46
+ ctx.status = 200
47
+ ctx.body = result.topic
48
+ }
49
+
50
+ /**
51
+ * Get Topic Controller
52
+ * GET /v1/projects/{project}/topics/{topic}
53
+ */
54
+ export const getTopicController = async ctx => {
55
+ const { project, topic } = ctx.params
56
+
57
+ const result = GetTopic({
58
+ projectId: project,
59
+ topicId: topic
60
+ })
61
+
62
+ if (!result.isSuccessful) {
63
+ return sendError(ctx, result.errorCode, result.errorMessage, 404)
64
+ }
65
+
66
+ ctx.status = 200
67
+ ctx.body = result.topic
68
+ }
69
+
70
+ /**
71
+ * List Topics Controller
72
+ * GET /v1/projects/{project}/topics
73
+ */
74
+ export const listTopicsController = async ctx => {
75
+ const { project } = ctx.params
76
+ const { pageSize, pageToken } = ctx.query
77
+
78
+ const result = ListTopics({
79
+ projectId: project,
80
+ pageSize: pageSize ? parseInt(pageSize, 10) : 50,
81
+ pageToken
82
+ })
83
+
84
+ ctx.status = 200
85
+ ctx.body = {
86
+ topics: result.topics,
87
+ nextPageToken: result.nextPageToken
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Delete Topic Controller
93
+ * DELETE /v1/projects/{project}/topics/{topic}
94
+ */
95
+ export const deleteTopicController = async ctx => {
96
+ const { project, topic } = ctx.params
97
+
98
+ const result = DeleteTopic({
99
+ projectId: project,
100
+ topicId: topic
101
+ })
102
+
103
+ if (!result.isSuccessful) {
104
+ return sendError(ctx, result.errorCode, result.errorMessage, 404)
105
+ }
106
+
107
+ ctx.status = 200
108
+ ctx.body = {}
109
+ }
110
+
111
+ /**
112
+ * Publish Messages Controller
113
+ * POST /v1/projects/{project}/topics/{topic}:publish
114
+ */
115
+ export const publishMessagesController = async ctx => {
116
+ const { project, topic } = ctx.params
117
+ const { messages } = ctx.request.body
118
+
119
+ if (!messages || !Array.isArray(messages)) {
120
+ return sendError(ctx, 'INVALID_ARGUMENT', 'messages must be an array', 400)
121
+ }
122
+
123
+ const result = PublishMessages({
124
+ projectId: project,
125
+ topicId: topic,
126
+ messages
127
+ })
128
+
129
+ if (!result.isSuccessful) {
130
+ const statusCode = result.errorCode === 'NOT_FOUND' ? 404 : 400
131
+ return sendError(ctx, result.errorCode, result.errorMessage, statusCode)
132
+ }
133
+
134
+ ctx.status = 200
135
+ ctx.body = {
136
+ messageIds: result.messageIds
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Create Subscription Controller
142
+ * PUT /v1/projects/{project}/subscriptions/{subscription}
143
+ */
144
+ export const createSubscriptionController = async ctx => {
145
+ const { project, subscription } = ctx.params
146
+ const { topic, ackDeadlineSeconds, pushConfig } = ctx.request.body
147
+
148
+ if (!topic) {
149
+ return sendError(ctx, 'INVALID_ARGUMENT', 'topic is required', 400)
150
+ }
151
+
152
+ const result = CreateSubscription({
153
+ projectId: project,
154
+ subscriptionId: subscription,
155
+ topic,
156
+ ackDeadlineSeconds,
157
+ pushConfig
158
+ })
159
+
160
+ if (!result.isSuccessful) {
161
+ const statusCode = result.errorCode === 'ALREADY_EXISTS' ? 409 : result.errorCode === 'NOT_FOUND' ? 404 : 400
162
+ return sendError(ctx, result.errorCode, result.errorMessage, statusCode)
163
+ }
164
+
165
+ ctx.status = 200
166
+ ctx.body = result.subscription
167
+ }
168
+
169
+ /**
170
+ * Get Subscription Controller
171
+ * GET /v1/projects/{project}/subscriptions/{subscription}
172
+ */
173
+ export const getSubscriptionController = async ctx => {
174
+ const { project, subscription } = ctx.params
175
+
176
+ const result = GetSubscription({
177
+ projectId: project,
178
+ subscriptionId: subscription
179
+ })
180
+
181
+ if (!result.isSuccessful) {
182
+ return sendError(ctx, result.errorCode, result.errorMessage, 404)
183
+ }
184
+
185
+ ctx.status = 200
186
+ ctx.body = result.subscription
187
+ }
188
+
189
+ /**
190
+ * List Subscriptions Controller
191
+ * GET /v1/projects/{project}/subscriptions
192
+ */
193
+ export const listSubscriptionsController = async ctx => {
194
+ const { project } = ctx.params
195
+ const { pageSize, pageToken } = ctx.query
196
+
197
+ const result = ListSubscriptions({
198
+ projectId: project,
199
+ pageSize: pageSize ? parseInt(pageSize, 10) : 50,
200
+ pageToken
201
+ })
202
+
203
+ ctx.status = 200
204
+ ctx.body = {
205
+ subscriptions: result.subscriptions,
206
+ nextPageToken: result.nextPageToken
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Delete Subscription Controller
212
+ * DELETE /v1/projects/{project}/subscriptions/{subscription}
213
+ */
214
+ export const deleteSubscriptionController = async ctx => {
215
+ const { project, subscription } = ctx.params
216
+
217
+ const result = DeleteSubscription({
218
+ projectId: project,
219
+ subscriptionId: subscription
220
+ })
221
+
222
+ if (!result.isSuccessful) {
223
+ return sendError(ctx, result.errorCode, result.errorMessage, 404)
224
+ }
225
+
226
+ ctx.status = 200
227
+ ctx.body = {}
228
+ }
229
+
230
+ /**
231
+ * Pull Messages Controller
232
+ * POST /v1/projects/{project}/subscriptions/{subscription}:pull
233
+ */
234
+ export const pullMessagesController = async ctx => {
235
+ const { project, subscription } = ctx.params
236
+ const { maxMessages, returnImmediately } = ctx.request.body || {}
237
+
238
+ const result = PullMessages({
239
+ projectId: project,
240
+ subscriptionId: subscription,
241
+ maxMessages: maxMessages || 100,
242
+ returnImmediately: returnImmediately || false
243
+ })
244
+
245
+ if (!result.isSuccessful) {
246
+ return sendError(ctx, result.errorCode, result.errorMessage, 404)
247
+ }
248
+
249
+ ctx.status = 200
250
+ ctx.body = {
251
+ receivedMessages: result.receivedMessages
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Acknowledge Messages Controller
257
+ * POST /v1/projects/{project}/subscriptions/{subscription}:acknowledge
258
+ */
259
+ export const acknowledgeMessagesController = async ctx => {
260
+ const { project, subscription } = ctx.params
261
+ const { ackIds } = ctx.request.body
262
+
263
+ if (!ackIds || !Array.isArray(ackIds)) {
264
+ return sendError(ctx, 'INVALID_ARGUMENT', 'ackIds must be an array', 400)
265
+ }
266
+
267
+ const result = AcknowledgeMessages({
268
+ projectId: project,
269
+ subscriptionId: subscription,
270
+ ackIds
271
+ })
272
+
273
+ if (!result.isSuccessful) {
274
+ return sendError(ctx, result.errorCode, result.errorMessage, 404)
275
+ }
276
+
277
+ ctx.status = 200
278
+ ctx.body = {}
279
+ }
@@ -0,0 +1,162 @@
1
+ import { Application } from '../../configs/Application.js'
2
+ import { Logger } from '../../singletons/Logger.js'
3
+ import { SqliteStore } from '../../singletons/SqliteStore.js'
4
+ import { PUBSUB_TOPIC_REGISTRY } from '../../db/Tables.js'
5
+ import { shadowSubscriptionManager } from './ShadowSubscriptionManager.js'
6
+
7
+ const { pubsub } = Application
8
+ const PUBSUB_API = `http://${pubsub.emulatorHost}:${pubsub.emulatorPort}`
9
+ const PROJECT_ID = pubsub.projectId
10
+
11
+ // Default topics that should always exist
12
+ const DEFAULT_TOPICS = [
13
+ 'systemOneMinuteTick'
14
+ ]
15
+
16
+ /**
17
+ * Creates a topic and its shadow subscription in the Pub/Sub emulator
18
+ */
19
+ const createTopicWithShadow = async (topicName) => {
20
+ const fullTopicName = `projects/${PROJECT_ID}/topics/${topicName}`
21
+ const shadowSubName = `${topicName}-devtools-shadow`
22
+ const fullShadowSubName = `projects/${PROJECT_ID}/subscriptions/${shadowSubName}`
23
+
24
+ try {
25
+ // Create topic
26
+ const topicResponse = await fetch(`${PUBSUB_API}/v1/projects/${PROJECT_ID}/topics/${topicName}`, {
27
+ method: 'PUT',
28
+ headers: { 'Content-Type': 'application/json' },
29
+ body: JSON.stringify({})
30
+ })
31
+
32
+ const topicCreated = topicResponse.status === 200
33
+ const topicExists = topicResponse.status === 409
34
+
35
+ // Create shadow subscription for message capture
36
+ const subResponse = await fetch(`${PUBSUB_API}/v1/projects/${PROJECT_ID}/subscriptions/${shadowSubName}`, {
37
+ method: 'PUT',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify({ topic: fullTopicName })
40
+ })
41
+
42
+ const subCreated = subResponse.status === 200
43
+ const subExists = subResponse.status === 409
44
+
45
+ // Register with shadow subscription manager so poller captures messages
46
+ shadowSubscriptionManager.shadowSubscriptions.add(fullShadowSubName)
47
+ shadowSubscriptionManager.shadowToTopic.set(fullShadowSubName, fullTopicName)
48
+
49
+ return {
50
+ topic: topicCreated ? 'created' : (topicExists ? 'exists' : 'error'),
51
+ subscription: subCreated ? 'created' : (subExists ? 'exists' : 'error')
52
+ }
53
+ } catch (error) {
54
+ Logger.log({
55
+ level: 'warn',
56
+ message: `Failed to create default topic: ${topicName}`,
57
+ data: { error: error.message }
58
+ })
59
+ return { topic: 'error', subscription: 'error' }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Restore all topics and subscriptions from the persistent registry.
65
+ * Called on startup to auto-recreate topics after emulator restart.
66
+ */
67
+ const restoreRegisteredTopics = async () => {
68
+ try {
69
+ const entries = SqliteStore.db.prepare(`SELECT * FROM ${PUBSUB_TOPIC_REGISTRY}`).all()
70
+ if (entries.length === 0) return []
71
+ // Deduplicate unique topic names
72
+ const uniqueTopics = [...new Set(entries.map(e => e.topic_name))]
73
+ const results = []
74
+ // Create all unique topics with shadow subscriptions
75
+ for (const topicName of uniqueTopics) {
76
+ const result = await createTopicWithShadow(topicName)
77
+ results.push({ topicName, ...result, source: 'registry' })
78
+ }
79
+ // Create project-specific subscriptions
80
+ const subsEntries = entries.filter(e => e.subscription_name)
81
+ for (const entry of subsEntries) {
82
+ const fullTopicName = `projects/${PROJECT_ID}/topics/${entry.topic_name}`
83
+ try {
84
+ const subResponse = await fetch(`${PUBSUB_API}/v1/projects/${PROJECT_ID}/subscriptions/${entry.subscription_name}`, {
85
+ method: 'PUT',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body: JSON.stringify({ topic: fullTopicName })
88
+ })
89
+ const subCreated = subResponse.status === 200
90
+ const subExists = subResponse.status === 409
91
+ results.push({
92
+ topicName: entry.topic_name,
93
+ subscription: entry.subscription_name,
94
+ result: subCreated ? 'created' : (subExists ? 'exists' : 'error'),
95
+ source: 'registry'
96
+ })
97
+ } catch (error) {
98
+ results.push({
99
+ topicName: entry.topic_name,
100
+ subscription: entry.subscription_name,
101
+ result: 'error',
102
+ source: 'registry'
103
+ })
104
+ }
105
+ }
106
+ const createdTopics = results.filter(r => r.topic === 'created' && !r.subscription).length
107
+ const createdSubs = results.filter(r => r.result === 'created' && r.subscription).length
108
+ Logger.log({
109
+ level: 'info',
110
+ message: `Restored ${uniqueTopics.length} registered topic(s) from registry`,
111
+ data: {
112
+ totalEntries: entries.length,
113
+ uniqueTopics: uniqueTopics.length,
114
+ createdTopics,
115
+ createdSubs
116
+ }
117
+ })
118
+ return results
119
+ } catch (error) {
120
+ Logger.log({
121
+ level: 'warn',
122
+ message: 'Failed to restore registered topics from registry',
123
+ data: { error: error.message }
124
+ })
125
+ return []
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Initialize default topics on startup
131
+ */
132
+ export const initializeDefaultTopics = async () => {
133
+ // Wait a moment for the emulator to be fully ready
134
+ await new Promise(resolve => setTimeout(resolve, 1000))
135
+
136
+ const results = []
137
+
138
+ for (const topicName of DEFAULT_TOPICS) {
139
+ const result = await createTopicWithShadow(topicName)
140
+ results.push({ topicName, ...result })
141
+ }
142
+
143
+ const created = results.filter(r => r.topic === 'created').length
144
+ const existing = results.filter(r => r.topic === 'exists').length
145
+
146
+ Logger.log({
147
+ level: 'info',
148
+ message: 'Default Pub/Sub topics initialized',
149
+ data: {
150
+ topics: DEFAULT_TOPICS,
151
+ created,
152
+ existing,
153
+ results
154
+ }
155
+ })
156
+
157
+ // Restore registered topics from persistent registry
158
+ const registryResults = await restoreRegisteredTopics()
159
+ results.push(...registryResults)
160
+
161
+ return results
162
+ }