@gokiteam/goki-dev 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +478 -0
- package/bin/goki-dev.js +452 -0
- package/bin/mcp-server.js +16 -0
- package/bin/secrets-cli.js +302 -0
- package/cli/ComposeOverrideGenerator.js +226 -0
- package/cli/ComposeParser.js +73 -0
- package/cli/ConfigGenerator.js +304 -0
- package/cli/ConfigManager.js +46 -0
- package/cli/DatabaseManager.js +94 -0
- package/cli/DevToolsChecker.js +21 -0
- package/cli/DevToolsDir.js +66 -0
- package/cli/DevToolsManager.js +451 -0
- package/cli/DockerManager.js +138 -0
- package/cli/FunctionManager.js +95 -0
- package/cli/HttpProxyRewriter.js +91 -0
- package/cli/Logger.js +10 -0
- package/cli/McpConfigManager.js +123 -0
- package/cli/NgrokManager.js +431 -0
- package/cli/ProjectCLI.js +2322 -0
- package/cli/PubSubManager.js +129 -0
- package/cli/SnapshotManager.js +88 -0
- package/cli/UiFormatter.js +292 -0
- package/cli/WebhookUrlRewriter.js +32 -0
- package/cli/secrets/BiometricAuth.js +125 -0
- package/cli/secrets/SecretInjector.js +47 -0
- package/cli/secrets/SecretsConfig.js +141 -0
- package/cli/secrets/SecretsDoctor.js +384 -0
- package/cli/secrets/SecretsManager.js +255 -0
- package/client/dist/client.d.ts +332 -0
- package/client/dist/client.js +507 -0
- package/client/dist/helpers.d.ts +62 -0
- package/client/dist/helpers.js +122 -0
- package/client/dist/index.d.ts +59 -0
- package/client/dist/index.js +78 -0
- package/client/dist/package.json +1 -0
- package/client/dist/types.d.ts +280 -0
- package/client/dist/types.js +7 -0
- package/config.development +46 -0
- package/config.test +18 -0
- package/guidelines/CodingStyleGuideline.md +148 -0
- package/guidelines/CommentingGuideline.md +10 -0
- package/guidelines/HttpApiImplementationGuideline.md +137 -0
- package/guidelines/NamingGuideline.md +182 -0
- package/package.json +138 -0
- package/patterns/api/[collectionName]/Controllers.md +62 -0
- package/patterns/api/[collectionName]/Logic.md +154 -0
- package/patterns/api/[collectionName]/Permissions.md +81 -0
- package/patterns/api/[collectionName]/Router.md +83 -0
- package/patterns/api/[collectionName]/Schemas.md +197 -0
- package/patterns/configs/Patterns.md +7 -0
- package/patterns/enums/Patterns.md +24 -0
- package/patterns/errorHandling/Patterns.md +185 -0
- package/patterns/testing/Patterns.md +232 -0
- package/src/Server.js +238 -0
- package/src/api/dashboard/Controllers.js +9 -0
- package/src/api/dashboard/Logic.js +76 -0
- package/src/api/dashboard/Router.js +11 -0
- package/src/api/dashboard/Schemas.js +47 -0
- package/src/api/data/Controllers.js +26 -0
- package/src/api/data/Logic.js +188 -0
- package/src/api/data/Router.js +16 -0
- package/src/api/docker/Controllers.js +33 -0
- package/src/api/docker/Logic.js +268 -0
- package/src/api/docker/Router.js +15 -0
- package/src/api/docker/Schemas.js +80 -0
- package/src/api/docs/Controllers.js +15 -0
- package/src/api/docs/Logic.js +85 -0
- package/src/api/docs/Router.js +12 -0
- package/src/api/export/Controllers.js +30 -0
- package/src/api/export/Logic.js +143 -0
- package/src/api/export/Router.js +18 -0
- package/src/api/export/Schemas.js +104 -0
- package/src/api/firestore/Controllers.js +152 -0
- package/src/api/firestore/Logic.js +474 -0
- package/src/api/firestore/Router.js +23 -0
- package/src/api/functions/Controllers.js +261 -0
- package/src/api/functions/Logic.js +710 -0
- package/src/api/functions/Router.js +50 -0
- package/src/api/functions/Schemas.js +193 -0
- package/src/api/gateway/Controllers.js +72 -0
- package/src/api/gateway/Logic.js +74 -0
- package/src/api/gateway/Router.js +10 -0
- package/src/api/gateway/Schemas.js +19 -0
- package/src/api/health/Controllers.js +14 -0
- package/src/api/health/Logic.js +24 -0
- package/src/api/health/Router.js +12 -0
- package/src/api/httpTraffic/Controllers.js +29 -0
- package/src/api/httpTraffic/Logic.js +33 -0
- package/src/api/httpTraffic/Router.js +9 -0
- package/src/api/httpTraffic/Schemas.js +23 -0
- package/src/api/logging/Controllers.js +80 -0
- package/src/api/logging/Logic.js +461 -0
- package/src/api/logging/Router.js +24 -0
- package/src/api/logging/Schemas.js +43 -0
- package/src/api/mqtt/Controllers.js +17 -0
- package/src/api/mqtt/Logic.js +66 -0
- package/src/api/mqtt/Router.js +12 -0
- package/src/api/postgres/Controllers.js +97 -0
- package/src/api/postgres/Logic.js +221 -0
- package/src/api/postgres/Router.js +21 -0
- package/src/api/pubsub/Controllers.js +236 -0
- package/src/api/pubsub/Logic.js +732 -0
- package/src/api/pubsub/Router.js +41 -0
- package/src/api/pubsub/Schemas.js +355 -0
- package/src/api/redis/Controllers.js +63 -0
- package/src/api/redis/Logic.js +239 -0
- package/src/api/redis/Router.js +21 -0
- package/src/api/scheduler/Controllers.js +27 -0
- package/src/api/scheduler/Logic.js +49 -0
- package/src/api/scheduler/Router.js +16 -0
- package/src/api/services/Controllers.js +26 -0
- package/src/api/services/Logic.js +205 -0
- package/src/api/services/Router.js +14 -0
- package/src/api/services/Schemas.js +66 -0
- package/src/api/snapshots/Controllers.js +37 -0
- package/src/api/snapshots/Logic.js +797 -0
- package/src/api/snapshots/Router.js +15 -0
- package/src/api/snapshots/Schemas.js +23 -0
- package/src/api/webhooks/Controllers.js +49 -0
- package/src/api/webhooks/Logic.js +137 -0
- package/src/api/webhooks/Router.js +12 -0
- package/src/api/webhooks/Schemas.js +31 -0
- package/src/configs/Application.js +147 -0
- package/src/configs/Default.js +13 -0
- package/src/consumers/BlackboxLogsConsumer.js +235 -0
- package/src/consumers/DockerLogsConsumer.js +687 -0
- package/src/db/Tables.js +66 -0
- package/src/db/schemas/firestore.js +18 -0
- package/src/db/schemas/functions.js +65 -0
- package/src/db/schemas/httpTraffic.js +43 -0
- package/src/db/schemas/logging.js +74 -0
- package/src/db/schemas/migrations.js +64 -0
- package/src/db/schemas/mqtt.js +56 -0
- package/src/db/schemas/pubsub.js +90 -0
- package/src/db/schemas/pubsubRegistry.js +22 -0
- package/src/db/schemas/webhooks.js +28 -0
- package/src/emulation/awsiot/Controllers.js +91 -0
- package/src/emulation/awsiot/Logic.js +70 -0
- package/src/emulation/awsiot/Router.js +19 -0
- package/src/emulation/awsiot/Server.js +100 -0
- package/src/emulation/firestore/Server.js +136 -0
- package/src/emulation/logging/Controllers.js +212 -0
- package/src/emulation/logging/Logic.js +416 -0
- package/src/emulation/logging/Router.js +36 -0
- package/src/emulation/logging/Schemas.js +82 -0
- package/src/emulation/logging/Server.js +108 -0
- package/src/emulation/pubsub/Controllers.js +279 -0
- package/src/emulation/pubsub/DefaultTopics.js +162 -0
- package/src/emulation/pubsub/Logic.js +427 -0
- package/src/emulation/pubsub/README.md +309 -0
- package/src/emulation/pubsub/Router.js +33 -0
- package/src/emulation/pubsub/Server.js +104 -0
- package/src/emulation/pubsub/ShadowPoller.js +276 -0
- package/src/emulation/pubsub/ShadowSubscriptionManager.js +199 -0
- package/src/enums/ContainerNames.js +106 -0
- package/src/enums/ErrorReason.js +28 -0
- package/src/enums/FunctionStatuses.js +15 -0
- package/src/enums/FunctionTriggerTypes.js +15 -0
- package/src/enums/GatewayState.js +7 -0
- package/src/enums/ServiceNames.js +68 -0
- package/src/jobs/DatabaseMaintenance.js +184 -0
- package/src/jobs/MessageHistoryCleanup.js +152 -0
- package/src/mcp/ApiClient.js +25 -0
- package/src/mcp/Server.js +52 -0
- package/src/mcp/prompts/debugging.js +104 -0
- package/src/mcp/resources/platform.js +118 -0
- package/src/mcp/tools/data.js +84 -0
- package/src/mcp/tools/docker.js +166 -0
- package/src/mcp/tools/firestore.js +162 -0
- package/src/mcp/tools/functions.js +380 -0
- package/src/mcp/tools/httpTraffic.js +69 -0
- package/src/mcp/tools/logging.js +174 -0
- package/src/mcp/tools/mqtt.js +37 -0
- package/src/mcp/tools/postgres.js +130 -0
- package/src/mcp/tools/pubsub.js +316 -0
- package/src/mcp/tools/redis.js +146 -0
- package/src/mcp/tools/services.js +169 -0
- package/src/mcp/tools/snapshots.js +88 -0
- package/src/mcp/tools/webhooks.js +115 -0
- package/src/middleware/DevProxy.js +67 -0
- package/src/middleware/ErrorCatcher.js +35 -0
- package/src/middleware/HttpProxy.js +215 -0
- package/src/middleware/Reply.js +24 -0
- package/src/middleware/TraceId.js +9 -0
- package/src/middleware/WebhookProxy.js +234 -0
- package/src/protocols/mqtt/Broker.js +92 -0
- package/src/protocols/mqtt/Handlers.js +175 -0
- package/src/protocols/mqtt/PubSubBridge.js +162 -0
- package/src/protocols/mqtt/Server.js +116 -0
- package/src/runtime/FunctionRunner.js +179 -0
- package/src/services/AppGatewayService.js +582 -0
- package/src/singletons/FirestoreBroadcaster.js +367 -0
- package/src/singletons/FunctionTriggerDispatcher.js +456 -0
- package/src/singletons/FunctionsService.js +418 -0
- package/src/singletons/HttpProxy.js +224 -0
- package/src/singletons/LogBroadcaster.js +159 -0
- package/src/singletons/Logger.js +49 -0
- package/src/singletons/MemoryJsonStore.js +175 -0
- package/src/singletons/MessageBroadcaster.js +190 -0
- package/src/singletons/PostgresBroadcaster.js +367 -0
- package/src/singletons/PostgresClient.js +180 -0
- package/src/singletons/RedisClient.js +184 -0
- package/src/singletons/SqliteStore.js +480 -0
- package/src/singletons/TickService.js +151 -0
- package/src/singletons/WebhookProxy.js +223 -0
|
@@ -0,0 +1,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
|
+
}
|