@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,427 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid'
|
|
2
|
+
import { SqliteStore } from '../../singletons/SqliteStore.js'
|
|
3
|
+
import { MessageBroadcaster } from '../../singletons/MessageBroadcaster.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract sender from message attributes
|
|
7
|
+
* @param {Object} attributes - Message attributes
|
|
8
|
+
* @returns {string|null} - Sender identifier
|
|
9
|
+
*/
|
|
10
|
+
const extractSender = attributes => {
|
|
11
|
+
if (!attributes) return null
|
|
12
|
+
return attributes.sender || attributes.source || attributes.userId || null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a message has meaningful data worth storing in history.
|
|
17
|
+
* Filters out empty payloads like {} or empty strings that show as
|
|
18
|
+
* unknown items in the UI.
|
|
19
|
+
* @param {string} data - Base64-encoded message data
|
|
20
|
+
* @returns {boolean} - true if the message has meaningful content
|
|
21
|
+
*/
|
|
22
|
+
const hasMeaningfulData = data => {
|
|
23
|
+
if (!data) return false
|
|
24
|
+
try {
|
|
25
|
+
const decoded = Buffer.from(data, 'base64').toString('utf-8')
|
|
26
|
+
const trimmed = decoded.trim()
|
|
27
|
+
if (!trimmed || trimmed === '{}' || trimmed === '""' || trimmed === 'null') return false
|
|
28
|
+
return true
|
|
29
|
+
} catch {
|
|
30
|
+
return !!data
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a new topic
|
|
36
|
+
*/
|
|
37
|
+
export const CreateTopic = params => {
|
|
38
|
+
const { projectId, topicId } = params
|
|
39
|
+
|
|
40
|
+
const topicName = `projects/${projectId}/topics/${topicId}`
|
|
41
|
+
|
|
42
|
+
// Check if topic already exists
|
|
43
|
+
const existingTopic = SqliteStore.get('pubsub_topics', topicName, 'name')
|
|
44
|
+
if (existingTopic) {
|
|
45
|
+
return {
|
|
46
|
+
isSuccessful: false,
|
|
47
|
+
errorCode: 'ALREADY_EXISTS',
|
|
48
|
+
errorMessage: 'Topic already exists'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const topic = {
|
|
53
|
+
name: topicName,
|
|
54
|
+
labels: {},
|
|
55
|
+
messageStoragePolicy: {},
|
|
56
|
+
kmsKeyName: '',
|
|
57
|
+
schemaSettings: null,
|
|
58
|
+
satisfiesPzs: false,
|
|
59
|
+
messageRetentionDuration: '604800s'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
SqliteStore.create('pubsub_topics', topic)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
isSuccessful: true,
|
|
66
|
+
topic
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get a topic
|
|
72
|
+
*/
|
|
73
|
+
export const GetTopic = params => {
|
|
74
|
+
const { projectId, topicId } = params
|
|
75
|
+
const topicName = `projects/${projectId}/topics/${topicId}`
|
|
76
|
+
|
|
77
|
+
const topic = SqliteStore.get('pubsub_topics', topicName, 'name')
|
|
78
|
+
|
|
79
|
+
if (!topic) {
|
|
80
|
+
return {
|
|
81
|
+
isSuccessful: false,
|
|
82
|
+
errorCode: 'NOT_FOUND',
|
|
83
|
+
errorMessage: 'Topic not found'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
isSuccessful: true,
|
|
89
|
+
topic
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* List topics in a project
|
|
95
|
+
*/
|
|
96
|
+
export const ListTopics = params => {
|
|
97
|
+
const { projectId, pageSize = 50, pageToken } = params
|
|
98
|
+
|
|
99
|
+
const result = SqliteStore.list('pubsub_topics', {
|
|
100
|
+
filter: topic => topic.name.startsWith(`projects/${projectId}/topics/`),
|
|
101
|
+
limit: pageSize,
|
|
102
|
+
offset: pageToken ? parseInt(pageToken, 10) : 0
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
isSuccessful: true,
|
|
107
|
+
topics: result.data,
|
|
108
|
+
nextPageToken: result.data.length === pageSize ? String(result.data.length) : undefined
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Delete a topic
|
|
114
|
+
*/
|
|
115
|
+
export const DeleteTopic = params => {
|
|
116
|
+
const { projectId, topicId } = params
|
|
117
|
+
const topicName = `projects/${projectId}/topics/${topicId}`
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
SqliteStore.delete('pubsub_topics', topicName, 'name')
|
|
121
|
+
|
|
122
|
+
// Also delete all subscriptions for this topic
|
|
123
|
+
const subscriptions = SqliteStore.find('pubsub_subscriptions', sub => sub.topic === topicName)
|
|
124
|
+
subscriptions.forEach(sub => {
|
|
125
|
+
SqliteStore.delete('pubsub_subscriptions', sub.name, 'name')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
isSuccessful: true
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return {
|
|
133
|
+
isSuccessful: false,
|
|
134
|
+
errorCode: 'NOT_FOUND',
|
|
135
|
+
errorMessage: 'Topic not found'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Publish messages to a topic
|
|
142
|
+
* Auto-creates the topic if it doesn't exist (dev-tools convenience feature)
|
|
143
|
+
*/
|
|
144
|
+
export const PublishMessages = params => {
|
|
145
|
+
const { projectId, topicId, messages } = params
|
|
146
|
+
const topicName = `projects/${projectId}/topics/${topicId}`
|
|
147
|
+
|
|
148
|
+
// Auto-create topic if it doesn't exist (dev-tools convenience feature)
|
|
149
|
+
const existingTopic = SqliteStore.get('pubsub_topics', topicName, 'name')
|
|
150
|
+
if (!existingTopic) {
|
|
151
|
+
const topic = {
|
|
152
|
+
name: topicName,
|
|
153
|
+
labels: {},
|
|
154
|
+
messageStoragePolicy: {},
|
|
155
|
+
kmsKeyName: '',
|
|
156
|
+
schemaSettings: null,
|
|
157
|
+
satisfiesPzs: false,
|
|
158
|
+
messageRetentionDuration: '604800s'
|
|
159
|
+
}
|
|
160
|
+
SqliteStore.create('pubsub_topics', topic)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const messageIds = []
|
|
164
|
+
const publishTime = new Date().toISOString()
|
|
165
|
+
|
|
166
|
+
// Store each message
|
|
167
|
+
for (const message of messages) {
|
|
168
|
+
const messageId = uuid()
|
|
169
|
+
const storedMessage = {
|
|
170
|
+
id: messageId,
|
|
171
|
+
data: message.data,
|
|
172
|
+
attributes: message.attributes || {},
|
|
173
|
+
orderingKey: message.orderingKey || '',
|
|
174
|
+
publishTime,
|
|
175
|
+
topic: topicName,
|
|
176
|
+
ackId: null,
|
|
177
|
+
deliveryAttempt: 0
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Store in ephemeral messages table (for pull/ack)
|
|
181
|
+
SqliteStore.create('pubsub_messages', storedMessage)
|
|
182
|
+
|
|
183
|
+
// Only store messages with meaningful data in history
|
|
184
|
+
if (hasMeaningfulData(message.data)) {
|
|
185
|
+
const historyRecord = {
|
|
186
|
+
id: uuid(),
|
|
187
|
+
message_id: messageId,
|
|
188
|
+
data: message.data,
|
|
189
|
+
attributes: message.attributes || {},
|
|
190
|
+
ordering_key: message.orderingKey || '',
|
|
191
|
+
publish_time: publishTime,
|
|
192
|
+
topic: topicName,
|
|
193
|
+
sender: extractSender(message.attributes),
|
|
194
|
+
view_count: 0,
|
|
195
|
+
last_viewed_at: null
|
|
196
|
+
}
|
|
197
|
+
SqliteStore.create('pubsub_message_history', historyRecord)
|
|
198
|
+
|
|
199
|
+
// Broadcast to SSE clients
|
|
200
|
+
MessageBroadcaster.broadcast({
|
|
201
|
+
messageId,
|
|
202
|
+
topic: topicName,
|
|
203
|
+
data: message.data,
|
|
204
|
+
attributes: message.attributes || {},
|
|
205
|
+
publishTime,
|
|
206
|
+
sender: extractSender(message.attributes)
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
messageIds.push(messageId)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
isSuccessful: true,
|
|
215
|
+
messageIds
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Create a subscription
|
|
221
|
+
*/
|
|
222
|
+
export const CreateSubscription = params => {
|
|
223
|
+
const { projectId, subscriptionId, topic, ackDeadlineSeconds = 10, pushConfig = {} } = params
|
|
224
|
+
|
|
225
|
+
const subscriptionName = `projects/${projectId}/subscriptions/${subscriptionId}`
|
|
226
|
+
|
|
227
|
+
// Check if subscription already exists
|
|
228
|
+
const existingSubscription = SqliteStore.get('pubsub_subscriptions', subscriptionName, 'name')
|
|
229
|
+
if (existingSubscription) {
|
|
230
|
+
return {
|
|
231
|
+
isSuccessful: false,
|
|
232
|
+
errorCode: 'ALREADY_EXISTS',
|
|
233
|
+
errorMessage: 'Subscription already exists'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validate that topic exists
|
|
238
|
+
const topicExists = SqliteStore.get('pubsub_topics', topic, 'name')
|
|
239
|
+
if (!topicExists) {
|
|
240
|
+
return {
|
|
241
|
+
isSuccessful: false,
|
|
242
|
+
errorCode: 'NOT_FOUND',
|
|
243
|
+
errorMessage: 'Topic not found'
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const subscription = {
|
|
248
|
+
name: subscriptionName,
|
|
249
|
+
topic,
|
|
250
|
+
pushConfig,
|
|
251
|
+
ackDeadlineSeconds,
|
|
252
|
+
retainAckedMessages: false,
|
|
253
|
+
messageRetentionDuration: '604800s',
|
|
254
|
+
labels: {},
|
|
255
|
+
enableMessageOrdering: false,
|
|
256
|
+
expirationPolicy: {
|
|
257
|
+
ttl: '2678400s'
|
|
258
|
+
},
|
|
259
|
+
filter: '',
|
|
260
|
+
deadLetterPolicy: null,
|
|
261
|
+
retryPolicy: null,
|
|
262
|
+
detached: false,
|
|
263
|
+
enableExactlyOnceDelivery: false,
|
|
264
|
+
topicMessageRetentionDuration: '604800s',
|
|
265
|
+
state: 'ACTIVE'
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
SqliteStore.create('pubsub_subscriptions', subscription)
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
isSuccessful: true,
|
|
272
|
+
subscription
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get a subscription
|
|
278
|
+
*/
|
|
279
|
+
export const GetSubscription = params => {
|
|
280
|
+
const { projectId, subscriptionId } = params
|
|
281
|
+
const subscriptionName = `projects/${projectId}/subscriptions/${subscriptionId}`
|
|
282
|
+
|
|
283
|
+
const subscription = SqliteStore.get('pubsub_subscriptions', subscriptionName, 'name')
|
|
284
|
+
|
|
285
|
+
if (!subscription) {
|
|
286
|
+
return {
|
|
287
|
+
isSuccessful: false,
|
|
288
|
+
errorCode: 'NOT_FOUND',
|
|
289
|
+
errorMessage: 'Subscription not found'
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
isSuccessful: true,
|
|
295
|
+
subscription
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* List subscriptions
|
|
301
|
+
*/
|
|
302
|
+
export const ListSubscriptions = params => {
|
|
303
|
+
const { projectId, pageSize = 50, pageToken } = params
|
|
304
|
+
|
|
305
|
+
const result = SqliteStore.list('pubsub_subscriptions', {
|
|
306
|
+
filter: sub => sub.name.startsWith(`projects/${projectId}/subscriptions/`),
|
|
307
|
+
limit: pageSize,
|
|
308
|
+
offset: pageToken ? parseInt(pageToken, 10) : 0
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
isSuccessful: true,
|
|
313
|
+
subscriptions: result.data,
|
|
314
|
+
nextPageToken: result.data.length === pageSize ? String(result.data.length) : undefined
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Delete a subscription
|
|
320
|
+
*/
|
|
321
|
+
export const DeleteSubscription = params => {
|
|
322
|
+
const { projectId, subscriptionId } = params
|
|
323
|
+
const subscriptionName = `projects/${projectId}/subscriptions/${subscriptionId}`
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
SqliteStore.delete('pubsub_subscriptions', subscriptionName, 'name')
|
|
327
|
+
return {
|
|
328
|
+
isSuccessful: true
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
return {
|
|
332
|
+
isSuccessful: false,
|
|
333
|
+
errorCode: 'NOT_FOUND',
|
|
334
|
+
errorMessage: 'Subscription not found'
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Pull messages from a subscription
|
|
341
|
+
*/
|
|
342
|
+
export const PullMessages = params => {
|
|
343
|
+
const { projectId, subscriptionId, maxMessages = 100, returnImmediately = false } = params
|
|
344
|
+
const subscriptionName = `projects/${projectId}/subscriptions/${subscriptionId}`
|
|
345
|
+
|
|
346
|
+
// Check if subscription exists
|
|
347
|
+
const subscription = SqliteStore.get('pubsub_subscriptions', subscriptionName, 'name')
|
|
348
|
+
if (!subscription) {
|
|
349
|
+
return {
|
|
350
|
+
isSuccessful: false,
|
|
351
|
+
errorCode: 'NOT_FOUND',
|
|
352
|
+
errorMessage: 'Subscription not found'
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Get messages for this subscription's topic that haven't been acknowledged
|
|
357
|
+
const messages = SqliteStore.find('pubsub_messages', msg =>
|
|
358
|
+
msg.topic === subscription.topic && !msg.ackId
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
// Limit to maxMessages
|
|
362
|
+
const messagesToReturn = messages.slice(0, maxMessages)
|
|
363
|
+
|
|
364
|
+
// Generate ack IDs and mark messages as pending acknowledgment
|
|
365
|
+
const receivedMessages = messagesToReturn.map(msg => {
|
|
366
|
+
const ackId = uuid()
|
|
367
|
+
|
|
368
|
+
// Update message with ackId
|
|
369
|
+
SqliteStore.update('pubsub_messages', msg.id, {
|
|
370
|
+
ackId,
|
|
371
|
+
deliveryAttempt: msg.deliveryAttempt + 1,
|
|
372
|
+
lastDeliveryTime: new Date().toISOString()
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
ackId,
|
|
377
|
+
message: {
|
|
378
|
+
data: msg.data,
|
|
379
|
+
attributes: msg.attributes,
|
|
380
|
+
messageId: msg.id,
|
|
381
|
+
publishTime: msg.publishTime,
|
|
382
|
+
orderingKey: msg.orderingKey
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
isSuccessful: true,
|
|
389
|
+
receivedMessages
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Acknowledge messages
|
|
395
|
+
*/
|
|
396
|
+
export const AcknowledgeMessages = params => {
|
|
397
|
+
const { projectId, subscriptionId, ackIds } = params
|
|
398
|
+
const subscriptionName = `projects/${projectId}/subscriptions/${subscriptionId}`
|
|
399
|
+
|
|
400
|
+
// Check if subscription exists
|
|
401
|
+
const subscription = SqliteStore.get('pubsub_subscriptions', subscriptionName, 'name')
|
|
402
|
+
if (!subscription) {
|
|
403
|
+
return {
|
|
404
|
+
isSuccessful: false,
|
|
405
|
+
errorCode: 'NOT_FOUND',
|
|
406
|
+
errorMessage: 'Subscription not found'
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Delete messages with matching ackIds
|
|
411
|
+
const allMessages = SqliteStore.find('pubsub_messages', () => true)
|
|
412
|
+
|
|
413
|
+
for (const ackId of ackIds) {
|
|
414
|
+
const messageToAck = allMessages.find(msg => msg.ackId === ackId)
|
|
415
|
+
if (messageToAck) {
|
|
416
|
+
try {
|
|
417
|
+
SqliteStore.delete('pubsub_messages', messageToAck.id)
|
|
418
|
+
} catch (error) {
|
|
419
|
+
// Message might have already been deleted, ignore
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
isSuccessful: true
|
|
426
|
+
}
|
|
427
|
+
}
|