@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,199 @@
1
+ import { Application } from '../../configs/Application.js'
2
+ import { Logger } from '../../singletons/Logger.js'
3
+
4
+ const { pubsub } = Application
5
+ const SHADOW_SUFFIX = '-devtools-shadow'
6
+
7
+ /**
8
+ * Manages shadow subscriptions on the official Pub/Sub emulator.
9
+ * Creates shadow subscriptions for each user subscription to intercept messages.
10
+ */
11
+ export class ShadowSubscriptionManager {
12
+ constructor () {
13
+ this.baseUrl = `http://${pubsub.emulatorHost}:${pubsub.emulatorPort}`
14
+ this.projectId = pubsub.projectId
15
+ this.checkIntervalMs = pubsub.shadowSubscriptionCheckIntervalMs
16
+ this.knownSubscriptions = new Set()
17
+ this.shadowSubscriptions = new Set()
18
+ this.shadowToTopic = new Map()
19
+ this.intervalId = null
20
+ this.isRunning = false
21
+ }
22
+
23
+ async request (method, path, body = null) {
24
+ const url = `${this.baseUrl}${path}`
25
+ const options = {
26
+ method,
27
+ headers: { 'Content-Type': 'application/json' }
28
+ }
29
+ if (body) {
30
+ options.body = JSON.stringify(body)
31
+ }
32
+ const response = await fetch(url, options)
33
+ const text = await response.text()
34
+ if (!text) return { success: response.ok, status: response.status }
35
+ try {
36
+ return JSON.parse(text)
37
+ } catch {
38
+ return { success: response.ok, status: response.status, text }
39
+ }
40
+ }
41
+
42
+ async listSubscriptions () {
43
+ try {
44
+ const result = await this.request('GET', `/v1/projects/${this.projectId}/subscriptions`)
45
+ const subscriptions = result.subscriptions || []
46
+ return subscriptions
47
+ } catch (error) {
48
+ Logger.log({
49
+ level: 'error',
50
+ message: 'Failed to list subscriptions from emulator',
51
+ data: { error: error.message, baseUrl: this.baseUrl }
52
+ })
53
+ return []
54
+ }
55
+ }
56
+
57
+ async listTopics () {
58
+ try {
59
+ const result = await this.request('GET', `/v1/projects/${this.projectId}/topics`)
60
+ return result.topics || []
61
+ } catch (error) {
62
+ Logger.log({
63
+ level: 'error',
64
+ message: 'Failed to list topics from emulator',
65
+ data: { error: error.message }
66
+ })
67
+ return []
68
+ }
69
+ }
70
+
71
+ async createShadowSubscription (topicName, originalSubscriptionName) {
72
+ const shadowSubscriptionId = this.getShadowSubscriptionId(originalSubscriptionName)
73
+ const shadowSubscriptionName = `projects/${this.projectId}/subscriptions/${shadowSubscriptionId}`
74
+ if (this.shadowSubscriptions.has(shadowSubscriptionName)) {
75
+ return null
76
+ }
77
+ try {
78
+ const result = await this.request(
79
+ 'PUT',
80
+ `/v1/projects/${this.projectId}/subscriptions/${shadowSubscriptionId}`,
81
+ {
82
+ topic: topicName,
83
+ ackDeadlineSeconds: 60
84
+ }
85
+ )
86
+ if (result.name || result.success) {
87
+ this.shadowSubscriptions.add(shadowSubscriptionName)
88
+ this.shadowToTopic.set(shadowSubscriptionName, topicName)
89
+ Logger.log({
90
+ level: 'info',
91
+ message: 'Shadow subscription created',
92
+ data: { shadowSubscriptionName, forSubscription: originalSubscriptionName, topic: topicName }
93
+ })
94
+ return shadowSubscriptionName
95
+ }
96
+ if (result.error?.code === 409 || result.error?.status === 'ALREADY_EXISTS') {
97
+ this.shadowSubscriptions.add(shadowSubscriptionName)
98
+ this.shadowToTopic.set(shadowSubscriptionName, topicName)
99
+ return shadowSubscriptionName
100
+ }
101
+ Logger.log({
102
+ level: 'warn',
103
+ message: 'Failed to create shadow subscription',
104
+ data: { result, originalSubscriptionName }
105
+ })
106
+ return null
107
+ } catch (error) {
108
+ Logger.log({
109
+ level: 'error',
110
+ message: 'Error creating shadow subscription',
111
+ data: { error: error.message, originalSubscriptionName }
112
+ })
113
+ return null
114
+ }
115
+ }
116
+
117
+ getShadowSubscriptionId (originalSubscriptionName) {
118
+ const parts = originalSubscriptionName.split('/')
119
+ const subscriptionId = parts[parts.length - 1]
120
+ return `${subscriptionId}${SHADOW_SUFFIX}`
121
+ }
122
+
123
+ isShadowSubscription (subscriptionName) {
124
+ return subscriptionName.includes(SHADOW_SUFFIX)
125
+ }
126
+
127
+ async checkForNewSubscriptions () {
128
+ const subscriptions = await this.listSubscriptions()
129
+ if (subscriptions.length > 0) {
130
+ Logger.log({
131
+ level: 'info',
132
+ message: 'Checking subscriptions',
133
+ data: { count: subscriptions.length, shadowCount: this.shadowSubscriptions.size }
134
+ })
135
+ }
136
+ for (const sub of subscriptions) {
137
+ const subName = sub.name
138
+ if (this.isShadowSubscription(subName)) {
139
+ this.shadowSubscriptions.add(subName)
140
+ // Store topic mapping for discovered shadow subscriptions
141
+ if (sub.topic && !this.shadowToTopic.has(subName)) {
142
+ this.shadowToTopic.set(subName, sub.topic)
143
+ }
144
+ continue
145
+ }
146
+ if (!this.knownSubscriptions.has(subName)) {
147
+ this.knownSubscriptions.add(subName)
148
+ await this.createShadowSubscription(sub.topic, subName)
149
+ }
150
+ }
151
+ }
152
+
153
+ async start () {
154
+ if (this.isRunning) return
155
+ this.isRunning = true
156
+ Logger.log({
157
+ level: 'info',
158
+ message: 'Starting Shadow Subscription Manager',
159
+ data: {
160
+ emulatorHost: `${pubsub.emulatorHost}:${pubsub.emulatorPort}`,
161
+ checkIntervalMs: this.checkIntervalMs
162
+ }
163
+ })
164
+ await this.checkForNewSubscriptions()
165
+ this.intervalId = setInterval(async () => {
166
+ try {
167
+ await this.checkForNewSubscriptions()
168
+ } catch (error) {
169
+ Logger.log({
170
+ level: 'error',
171
+ message: 'Error in subscription check interval',
172
+ data: { error: error.message }
173
+ })
174
+ }
175
+ }, this.checkIntervalMs)
176
+ }
177
+
178
+ stop () {
179
+ if (this.intervalId) {
180
+ clearInterval(this.intervalId)
181
+ this.intervalId = null
182
+ }
183
+ this.isRunning = false
184
+ Logger.log({
185
+ level: 'info',
186
+ message: 'Shadow Subscription Manager stopped'
187
+ })
188
+ }
189
+
190
+ getShadowSubscriptions () {
191
+ return Array.from(this.shadowSubscriptions)
192
+ }
193
+
194
+ getTopicForShadow (shadowSubscriptionName) {
195
+ return this.shadowToTopic.get(shadowSubscriptionName) || null
196
+ }
197
+ }
198
+
199
+ export const shadowSubscriptionManager = new ShadowSubscriptionManager()
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Container Names Enum
3
+ * Normalized identifiers for all Docker containers in the platform
4
+ * Use these constants instead of hardcoding container names
5
+ */
6
+
7
+ export const ContainerNames = Object.freeze({
8
+ POSTGRES: 'postgres',
9
+ REDIS: 'redis',
10
+ REDIS_LOGS: 'redis-logs',
11
+ PUBSUB: 'pubsub',
12
+ FIRESTORE: 'firestore',
13
+ ELASTICSEARCH: 'elasticsearch',
14
+ KIBANA: 'kibana',
15
+ DEV_TOOLS_BACKEND: 'dev-tools-backend',
16
+ DEV_TOOLS_FRONTEND: 'dev-tools-frontend',
17
+ DEVICE_NATIVE: 'device-native',
18
+ DEVICE_SIMULATOR: 'device-simulator',
19
+ INTEGRATION_APALEO: 'integration-apaleo',
20
+ CORE_PMS: 'core-pms',
21
+ CORE_KEY: 'core-key',
22
+ INTEGRATION_MEWS: 'integration-mews',
23
+ INTEGRATION_OPERA: 'integration-opera'
24
+ })
25
+
26
+ export const ContainerDisplayNames = Object.freeze({
27
+ [ContainerNames.POSTGRES]: 'PostgreSQL',
28
+ [ContainerNames.REDIS]: 'Redis',
29
+ [ContainerNames.REDIS_LOGS]: 'Redis (Logs)',
30
+ [ContainerNames.PUBSUB]: 'GCP Pub/Sub Emulator',
31
+ [ContainerNames.FIRESTORE]: 'Firestore Emulator',
32
+ [ContainerNames.ELASTICSEARCH]: 'Elasticsearch',
33
+ [ContainerNames.KIBANA]: 'Kibana',
34
+ [ContainerNames.DEV_TOOLS_BACKEND]: 'Dev Tools Backend',
35
+ [ContainerNames.DEV_TOOLS_FRONTEND]: 'Dev Tools Frontend',
36
+ [ContainerNames.DEVICE_NATIVE]: 'Device Native',
37
+ [ContainerNames.DEVICE_SIMULATOR]: 'Device Simulator',
38
+ [ContainerNames.INTEGRATION_APALEO]: 'Integration Apaleo',
39
+ [ContainerNames.CORE_PMS]: 'Core PMS',
40
+ [ContainerNames.CORE_KEY]: 'Core Key',
41
+ [ContainerNames.INTEGRATION_MEWS]: 'Integration Mews',
42
+ [ContainerNames.INTEGRATION_OPERA]: 'Integration Opera'
43
+ })
44
+
45
+ export const ContainerDescriptions = Object.freeze({
46
+ [ContainerNames.POSTGRES]: 'PostgreSQL database for application data',
47
+ [ContainerNames.REDIS]: 'Redis cache and data store',
48
+ [ContainerNames.REDIS_LOGS]: 'Redis instance dedicated to log storage (256MB LRU)',
49
+ [ContainerNames.PUBSUB]: 'Google Cloud Pub/Sub emulation for messaging',
50
+ [ContainerNames.FIRESTORE]: 'Google Cloud Firestore NoSQL database emulation',
51
+ [ContainerNames.ELASTICSEARCH]: 'Elasticsearch search and analytics engine',
52
+ [ContainerNames.KIBANA]: 'Kibana visualization dashboard for Elasticsearch',
53
+ [ContainerNames.DEV_TOOLS_BACKEND]: 'Dev Tools API server and emulators',
54
+ [ContainerNames.DEV_TOOLS_FRONTEND]: 'Dev Tools web UI (nginx)',
55
+ [ContainerNames.DEVICE_NATIVE]: 'Device native communication service',
56
+ [ContainerNames.DEVICE_SIMULATOR]: 'Device simulator for testing',
57
+ [ContainerNames.INTEGRATION_APALEO]: 'Apaleo PMS integration',
58
+ [ContainerNames.CORE_PMS]: 'Core PMS service',
59
+ [ContainerNames.CORE_KEY]: 'Core key management service',
60
+ [ContainerNames.INTEGRATION_MEWS]: 'Mews PMS integration',
61
+ [ContainerNames.INTEGRATION_OPERA]: 'Opera PMS integration'
62
+ })
63
+
64
+ export const ContainerPorts = Object.freeze({
65
+ [ContainerNames.POSTGRES]: 5432,
66
+ [ContainerNames.REDIS]: 6379,
67
+ [ContainerNames.REDIS_LOGS]: 6380,
68
+ [ContainerNames.PUBSUB]: 8085,
69
+ [ContainerNames.FIRESTORE]: 8080,
70
+ [ContainerNames.ELASTICSEARCH]: 9200,
71
+ [ContainerNames.KIBANA]: 5601,
72
+ [ContainerNames.DEV_TOOLS_BACKEND]: 9000,
73
+ [ContainerNames.DEV_TOOLS_FRONTEND]: 9001,
74
+ [ContainerNames.DEVICE_NATIVE]: 3000,
75
+ [ContainerNames.DEVICE_SIMULATOR]: 3001,
76
+ [ContainerNames.INTEGRATION_APALEO]: 3002,
77
+ [ContainerNames.CORE_PMS]: 3003,
78
+ [ContainerNames.CORE_KEY]: 3004,
79
+ [ContainerNames.INTEGRATION_MEWS]: 3005,
80
+ [ContainerNames.INTEGRATION_OPERA]: 3006
81
+ })
82
+
83
+ // Default service configuration (which services are on by default)
84
+ export const ContainerDefaults = Object.freeze({
85
+ [ContainerNames.POSTGRES]: false,
86
+ [ContainerNames.REDIS]: true,
87
+ [ContainerNames.REDIS_LOGS]: false,
88
+ [ContainerNames.PUBSUB]: true,
89
+ [ContainerNames.FIRESTORE]: false,
90
+ [ContainerNames.ELASTICSEARCH]: false,
91
+ [ContainerNames.KIBANA]: false
92
+ })
93
+
94
+ /**
95
+ * Get all container names as array
96
+ */
97
+ export const getAllContainerNames = () => {
98
+ return Object.values(ContainerNames)
99
+ }
100
+
101
+ /**
102
+ * Validate container name
103
+ */
104
+ export const isValidContainerName = (name) => {
105
+ return getAllContainerNames().includes(name)
106
+ }
@@ -0,0 +1,28 @@
1
+ export const ErrorReason = Object.freeze({
2
+ // General errors
3
+ validationFailed: 'validationFailed',
4
+ resourceNotFound: 'resourceNotFound',
5
+ resourceAlreadyExists: 'resourceAlreadyExists',
6
+ collectionNotFound: 'collectionNotFound',
7
+ // Pub/Sub errors
8
+ topicNotFound: 'topicNotFound',
9
+ topicAlreadyExists: 'topicAlreadyExists',
10
+ subscriptionNotFound: 'subscriptionNotFound',
11
+ subscriptionAlreadyExists: 'subscriptionAlreadyExists',
12
+ messageNotFound: 'messageNotFound',
13
+ invalidMessageData: 'invalidMessageData',
14
+ // Logging errors
15
+ logEntryNotFound: 'logEntryNotFound',
16
+ invalidLogEntry: 'invalidLogEntry',
17
+ // Service errors
18
+ serviceNotFound: 'serviceNotFound',
19
+ serviceAlreadyRunning: 'serviceAlreadyRunning',
20
+ serviceNotRunning: 'serviceNotRunning',
21
+ serviceStartFailed: 'serviceStartFailed',
22
+ serviceStopFailed: 'serviceStopFailed',
23
+ // Storage errors
24
+ storageError: 'storageError',
25
+ flushFailed: 'flushFailed'
26
+ })
27
+
28
+ export const ErrorReasonValues = Object.values(ErrorReason)
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cloud Function Runtime Statuses
3
+ * Tracks the lifecycle state of a function process
4
+ */
5
+
6
+ export const FunctionStatuses = Object.freeze({
7
+ STOPPED: 'stopped',
8
+ STARTING: 'starting',
9
+ RUNNING: 'running',
10
+ ERROR: 'error'
11
+ })
12
+
13
+ export const FunctionStatusList = Object.values(FunctionStatuses)
14
+
15
+ export const isValidFunctionStatus = (status) => FunctionStatusList.includes(status)
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cloud Function Trigger Types
3
+ * Defines how a function is invoked
4
+ */
5
+
6
+ export const FunctionTriggerTypes = Object.freeze({
7
+ HTTP: 'http',
8
+ PUBSUB: 'pubsub',
9
+ FIRESTORE: 'firestore',
10
+ SCHEDULER: 'scheduler'
11
+ })
12
+
13
+ export const FunctionTriggerTypeList = Object.values(FunctionTriggerTypes)
14
+
15
+ export const isValidTriggerType = (type) => FunctionTriggerTypeList.includes(type)
@@ -0,0 +1,7 @@
1
+ export const GatewayState = Object.freeze({
2
+ STOPPED: 'stopped',
3
+ WAITING_FOR_DEPENDENCIES: 'waitingForDependencies',
4
+ STARTING: 'starting',
5
+ RUNNING: 'running',
6
+ ERROR: 'error'
7
+ })
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Service Names Enum
3
+ * Normalized identifiers for all services in the platform
4
+ * Use these constants instead of hardcoding service names
5
+ */
6
+
7
+ export const ServiceNames = Object.freeze({
8
+ PUBSUB: 'pubsub',
9
+ LOGGING: 'logging',
10
+ MQTT: 'mqtt',
11
+ FIRESTORE: 'firestore',
12
+ WEBUI: 'webui',
13
+ APP_GATEWAY: 'appGateway',
14
+ HTTP_PROXY: 'http',
15
+ WEBHOOK_PROXY: 'webhook',
16
+ CLOUD_FUNCTIONS: 'cloudFunctions',
17
+ // Docker container services (managed externally)
18
+ ELASTICSEARCH: 'elasticsearch',
19
+ KIBANA: 'kibana',
20
+ POSTGRES: 'postgres',
21
+ REDIS: 'redis'
22
+ })
23
+
24
+ export const ServiceDisplayNames = Object.freeze({
25
+ [ServiceNames.PUBSUB]: 'GCP Pub/Sub Emulator',
26
+ [ServiceNames.LOGGING]: 'Cloud Logging Emulator',
27
+ [ServiceNames.MQTT]: 'MQTT Broker',
28
+ [ServiceNames.FIRESTORE]: 'Cloud Firestore Emulator',
29
+ [ServiceNames.WEBUI]: 'Web UI & API Server',
30
+ [ServiceNames.APP_GATEWAY]: 'App Gateway',
31
+ [ServiceNames.HTTP_PROXY]: 'HTTP',
32
+ [ServiceNames.WEBHOOK_PROXY]: 'Webhook',
33
+ [ServiceNames.CLOUD_FUNCTIONS]: 'Cloud Functions',
34
+ [ServiceNames.ELASTICSEARCH]: 'Elasticsearch',
35
+ [ServiceNames.KIBANA]: 'Kibana',
36
+ [ServiceNames.POSTGRES]: 'PostgreSQL',
37
+ [ServiceNames.REDIS]: 'Redis'
38
+ })
39
+
40
+ export const ServiceDescriptions = Object.freeze({
41
+ [ServiceNames.PUBSUB]: 'Google Cloud Pub/Sub emulation for topic and subscription management',
42
+ [ServiceNames.LOGGING]: 'Google Cloud Logging emulation for log entry management',
43
+ [ServiceNames.MQTT]: 'MQTT message broker for device communication',
44
+ [ServiceNames.FIRESTORE]: 'Google Cloud Firestore emulation for NoSQL database',
45
+ [ServiceNames.WEBUI]: 'Management dashboard and REST API server',
46
+ [ServiceNames.APP_GATEWAY]: 'Virtual gateway for device-native to device-simulator communication',
47
+ [ServiceNames.HTTP_PROXY]: 'HTTP proxy for inter-service traffic monitoring',
48
+ [ServiceNames.WEBHOOK_PROXY]: 'Webhook proxy for external service callbacks',
49
+ [ServiceNames.CLOUD_FUNCTIONS]: 'Google Cloud Functions emulation for serverless function management',
50
+ [ServiceNames.ELASTICSEARCH]: 'Elasticsearch search and analytics engine',
51
+ [ServiceNames.KIBANA]: 'Kibana visualization dashboard for Elasticsearch',
52
+ [ServiceNames.POSTGRES]: 'PostgreSQL database for application data',
53
+ [ServiceNames.REDIS]: 'Redis cache and data store'
54
+ })
55
+
56
+ /**
57
+ * Get all service names as array
58
+ */
59
+ export const getAllServiceNames = () => {
60
+ return Object.values(ServiceNames)
61
+ }
62
+
63
+ /**
64
+ * Validate service name
65
+ */
66
+ export const isValidServiceName = (name) => {
67
+ return getAllServiceNames().includes(name)
68
+ }
@@ -0,0 +1,184 @@
1
+ // Periodic database maintenance job
2
+ // Trims unbounded tables, checkpoints WAL, and updates query planner stats
3
+
4
+ import { SqliteStore } from '../singletons/SqliteStore.js'
5
+ import { Application } from '../configs/Application.js'
6
+ import { Logger } from '../singletons/Logger.js'
7
+ import {
8
+ LOGGING_ENTRIES,
9
+ HTTP_TRAFFIC,
10
+ MQTT_MESSAGES,
11
+ PUBSUB_MESSAGES,
12
+ CLOUD_FUNCTION_INVOCATIONS
13
+ } from '../db/Tables.js'
14
+
15
+ const TABLES_TO_TRIM = [
16
+ { table: LOGGING_ENTRIES, column: 'created_at', configKey: 'loggingEntries' },
17
+ { table: HTTP_TRAFFIC, column: 'created_at', configKey: 'httpTraffic' },
18
+ { table: MQTT_MESSAGES, column: 'created_at', configKey: 'mqttMessages' },
19
+ { table: PUBSUB_MESSAGES, column: 'created_at', configKey: 'pubsubMessages' },
20
+ { table: CLOUD_FUNCTION_INVOCATIONS, column: 'created_at', configKey: 'functionInvocations' }
21
+ ]
22
+
23
+ /**
24
+ * Delete rows older than retention period from a table
25
+ */
26
+ const trimTable = (table, column, retentionDays) => {
27
+ const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString()
28
+ try {
29
+ const result = SqliteStore.db.prepare(
30
+ `DELETE FROM ${table} WHERE ${column} < ?`
31
+ ).run(cutoff)
32
+ return result.changes
33
+ } catch (error) {
34
+ Logger.log({
35
+ level: 'warn',
36
+ message: `Failed to trim ${table}`,
37
+ data: { error: error.message }
38
+ })
39
+ return 0
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Enforce max row count on logging_entries by deleting oldest rows
45
+ */
46
+ const enforceLoggingLimit = (maxEntries) => {
47
+ try {
48
+ const countResult = SqliteStore.db.prepare(
49
+ `SELECT COUNT(*) as count FROM ${LOGGING_ENTRIES}`
50
+ ).get()
51
+ const count = countResult.count
52
+ if (count <= maxEntries) return 0
53
+ const excess = count - maxEntries
54
+ const result = SqliteStore.db.prepare(`
55
+ DELETE FROM ${LOGGING_ENTRIES}
56
+ WHERE _id IN (
57
+ SELECT _id FROM ${LOGGING_ENTRIES}
58
+ ORDER BY created_at ASC
59
+ LIMIT ?
60
+ )
61
+ `).run(excess)
62
+ return result.changes
63
+ } catch (error) {
64
+ Logger.log({
65
+ level: 'warn',
66
+ message: 'Failed to enforce logging entry limit',
67
+ data: { error: error.message }
68
+ })
69
+ return 0
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Run full database maintenance cycle
75
+ */
76
+ export const runDatabaseMaintenance = () => {
77
+ const { retentionDays, maxLoggingEntries } = Application.dbMaintenance
78
+ const results = {}
79
+
80
+ // Trim tables by retention period
81
+ for (const { table, column, configKey } of TABLES_TO_TRIM) {
82
+ const days = retentionDays[configKey]
83
+ const deleted = trimTable(table, column, days)
84
+ if (deleted > 0) {
85
+ results[table] = { deleted, retentionDays: days }
86
+ }
87
+ }
88
+
89
+ // Enforce logging entry count limit
90
+ const loggingTrimmed = enforceLoggingLimit(maxLoggingEntries)
91
+ if (loggingTrimmed > 0) {
92
+ results[`${LOGGING_ENTRIES}_limit`] = { deleted: loggingTrimmed, maxEntries: maxLoggingEntries }
93
+ }
94
+
95
+ // WAL checkpoint
96
+ try {
97
+ SqliteStore.db.pragma('wal_checkpoint(PASSIVE)')
98
+ } catch (error) {
99
+ Logger.log({
100
+ level: 'warn',
101
+ message: 'WAL checkpoint failed',
102
+ data: { error: error.message }
103
+ })
104
+ }
105
+
106
+ // Update query planner stats
107
+ try {
108
+ SqliteStore.db.exec('ANALYZE')
109
+ } catch (error) {
110
+ Logger.log({
111
+ level: 'warn',
112
+ message: 'ANALYZE failed',
113
+ data: { error: error.message }
114
+ })
115
+ }
116
+
117
+ const totalDeleted = Object.values(results).reduce((sum, r) => sum + r.deleted, 0)
118
+ if (totalDeleted > 0) {
119
+ Logger.log({
120
+ level: 'info',
121
+ message: 'Database maintenance completed',
122
+ data: { totalDeleted, tables: results }
123
+ })
124
+ } else {
125
+ Logger.log({
126
+ level: 'debug',
127
+ message: 'Database maintenance completed - nothing to trim'
128
+ })
129
+ }
130
+
131
+ return results
132
+ }
133
+
134
+ /**
135
+ * Start the scheduled maintenance job
136
+ */
137
+ export const startMaintenanceJob = () => {
138
+ const { intervalHours } = Application.dbMaintenance
139
+ const intervalMs = intervalHours * 60 * 60 * 1000
140
+
141
+ Logger.log({
142
+ level: 'info',
143
+ message: 'Starting database maintenance job',
144
+ data: {
145
+ intervalHours,
146
+ retentionDays: Application.dbMaintenance.retentionDays,
147
+ maxLoggingEntries: Application.dbMaintenance.maxLoggingEntries
148
+ }
149
+ })
150
+
151
+ // Delay first run by 30 seconds to let server fully start
152
+ setTimeout(() => {
153
+ try {
154
+ runDatabaseMaintenance()
155
+ } catch (error) {
156
+ Logger.log({
157
+ level: 'error',
158
+ message: 'Initial database maintenance failed',
159
+ data: { error: error.message }
160
+ })
161
+ }
162
+ }, 30000)
163
+
164
+ return setInterval(() => {
165
+ try {
166
+ runDatabaseMaintenance()
167
+ } catch (error) {
168
+ Logger.log({
169
+ level: 'error',
170
+ message: 'Scheduled database maintenance failed',
171
+ data: { error: error.message }
172
+ })
173
+ }
174
+ }, intervalMs)
175
+ }
176
+
177
+ /**
178
+ * Stop the maintenance job
179
+ */
180
+ export const stopMaintenanceJob = (timer) => {
181
+ if (timer) {
182
+ clearInterval(timer)
183
+ }
184
+ }