@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,474 @@
1
+ import { initializeApp, getApps, deleteApp } from 'firebase-admin/app'
2
+ import { getFirestore } from 'firebase-admin/firestore'
3
+ import { SqliteStore } from '../../singletons/SqliteStore.js'
4
+ import { Application } from '../../configs/Application.js'
5
+ import { FIRESTORE_METADATA } from '../../db/Tables.js'
6
+
7
+ const FIRESTORE_API = `http://${Application.firestore.emulatorHost}`
8
+ const DEFAULT_PROJECT_ID = Application.firestore.projectId
9
+ const DATABASE = '(default)'
10
+
11
+ // Set emulator host environment variable for Firebase Admin SDK
12
+ process.env.FIRESTORE_EMULATOR_HOST = Application.firestore.emulatorHost
13
+
14
+ // Cache Firebase app instances by projectId
15
+ const appCache = new Map()
16
+
17
+ function getFirestoreDb (projectId) {
18
+ const pid = projectId || DEFAULT_PROJECT_ID
19
+ if (appCache.has(pid)) {
20
+ return appCache.get(pid)
21
+ }
22
+ const appName = `firestore-${pid}`
23
+ const existingApp = getApps().find(a => a.name === appName)
24
+ const app = existingApp || initializeApp({ projectId: pid }, appName)
25
+ const db = getFirestore(app)
26
+ appCache.set(pid, db)
27
+ return db
28
+ }
29
+
30
+ function resolveProjectId (params) {
31
+ return params.projectId || DEFAULT_PROJECT_ID
32
+ }
33
+
34
+ export const Logic = {
35
+ listProjects (params) {
36
+ const { traceId } = params
37
+ const projectIds = Application.firestore.projectIds
38
+ return {
39
+ projects: projectIds,
40
+ defaultProjectId: DEFAULT_PROJECT_ID,
41
+ total: projectIds.length,
42
+ traceId
43
+ }
44
+ },
45
+
46
+ async listCollections (params) {
47
+ const { traceId } = params
48
+ const projectId = resolveProjectId(params)
49
+ try {
50
+ const db = getFirestoreDb(projectId)
51
+ const collectionsSnapshot = await db.listCollections()
52
+ const collections = collectionsSnapshot
53
+ .map(col => col.id)
54
+ .filter(name => !name.startsWith('_'))
55
+ .sort()
56
+ return {
57
+ collections,
58
+ total: collections.length,
59
+ traceId
60
+ }
61
+ } catch (error) {
62
+ try {
63
+ const tracked = SqliteStore.list(FIRESTORE_METADATA)
64
+ const collections = tracked.data
65
+ .filter(row => row.collectionName)
66
+ .map(row => row.collectionName)
67
+ .sort()
68
+ return {
69
+ collections,
70
+ total: collections.length,
71
+ traceId
72
+ }
73
+ } catch (fallbackError) {
74
+ return {
75
+ status: 'error',
76
+ message: error.message,
77
+ traceId
78
+ }
79
+ }
80
+ }
81
+ },
82
+
83
+ async listDocuments (params) {
84
+ const { collectionPath, page = {}, traceId } = params
85
+ const projectId = resolveProjectId(params)
86
+ try {
87
+ const pageSize = page.limit || 50
88
+ const pageToken = page.offset ? btoa(String(page.offset)) : undefined
89
+ const url = new URL(`${FIRESTORE_API}/v1/projects/${projectId}/databases/${DATABASE}/documents/${collectionPath}`)
90
+ url.searchParams.set('pageSize', pageSize)
91
+ if (pageToken) url.searchParams.set('pageToken', pageToken)
92
+ const response = await fetch(url)
93
+ if (!response.ok) {
94
+ if (response.status === 404) {
95
+ return { documents: [], total: 0, traceId }
96
+ }
97
+ throw new Error(`Firestore API error: ${response.statusText}`)
98
+ }
99
+ const data = await response.json()
100
+ return {
101
+ documents: data.documents || [],
102
+ total: (data.documents || []).length,
103
+ nextPageToken: data.nextPageToken,
104
+ traceId
105
+ }
106
+ } catch (error) {
107
+ return {
108
+ status: 'error',
109
+ message: error.message,
110
+ traceId
111
+ }
112
+ }
113
+ },
114
+
115
+ async getDocument (params) {
116
+ const { collectionPath, documentId, traceId } = params
117
+ const projectId = resolveProjectId(params)
118
+ try {
119
+ const response = await fetch(`${FIRESTORE_API}/v1/projects/${projectId}/databases/${DATABASE}/documents/${collectionPath}/${documentId}`)
120
+ if (!response.ok) {
121
+ throw new Error(`Firestore API error: ${response.statusText}`)
122
+ }
123
+ const document = await response.json()
124
+ return {
125
+ document,
126
+ traceId
127
+ }
128
+ } catch (error) {
129
+ return {
130
+ status: 'error',
131
+ message: error.message,
132
+ traceId
133
+ }
134
+ }
135
+ },
136
+
137
+ async createDocument (params) {
138
+ const { collectionPath, documentId, fields, traceId } = params
139
+ const projectId = resolveProjectId(params)
140
+ try {
141
+ const url = new URL(`${FIRESTORE_API}/v1/projects/${projectId}/databases/${DATABASE}/documents/${collectionPath}`)
142
+ if (documentId) {
143
+ url.searchParams.set('documentId', documentId)
144
+ }
145
+ const response = await fetch(url, {
146
+ method: 'POST',
147
+ headers: { 'Content-Type': 'application/json' },
148
+ body: JSON.stringify({ fields })
149
+ })
150
+ if (!response.ok) {
151
+ throw new Error(`Firestore API error: ${response.statusText}`)
152
+ }
153
+ const document = await response.json()
154
+ trackCollection(collectionPath)
155
+ return {
156
+ document,
157
+ traceId
158
+ }
159
+ } catch (error) {
160
+ return {
161
+ status: 'error',
162
+ message: error.message,
163
+ traceId
164
+ }
165
+ }
166
+ },
167
+
168
+ async updateDocument (params) {
169
+ const { collectionPath, documentId, fields, traceId } = params
170
+ const projectId = resolveProjectId(params)
171
+ try {
172
+ const response = await fetch(`${FIRESTORE_API}/v1/projects/${projectId}/databases/${DATABASE}/documents/${collectionPath}/${documentId}`, {
173
+ method: 'PATCH',
174
+ headers: { 'Content-Type': 'application/json' },
175
+ body: JSON.stringify({ fields })
176
+ })
177
+ if (!response.ok) {
178
+ throw new Error(`Firestore API error: ${response.statusText}`)
179
+ }
180
+ const document = await response.json()
181
+ return {
182
+ document,
183
+ traceId
184
+ }
185
+ } catch (error) {
186
+ return {
187
+ status: 'error',
188
+ message: error.message,
189
+ traceId
190
+ }
191
+ }
192
+ },
193
+
194
+ async deleteDocument (params) {
195
+ const { collectionPath, documentId, traceId } = params
196
+ const projectId = resolveProjectId(params)
197
+ try {
198
+ const response = await fetch(`${FIRESTORE_API}/v1/projects/${projectId}/databases/${DATABASE}/documents/${collectionPath}/${documentId}`, {
199
+ method: 'DELETE'
200
+ })
201
+ if (!response.ok) {
202
+ throw new Error(`Firestore API error: ${response.statusText}`)
203
+ }
204
+ return {
205
+ success: true,
206
+ traceId
207
+ }
208
+ } catch (error) {
209
+ return {
210
+ status: 'error',
211
+ message: error.message,
212
+ traceId
213
+ }
214
+ }
215
+ },
216
+
217
+ async deleteByQuery (params) {
218
+ const { collectionPath, where, traceId } = params
219
+ const projectId = resolveProjectId(params)
220
+ try {
221
+ const queryResult = await this.executeQuery({
222
+ collectionPath,
223
+ where: where ? [where] : [],
224
+ limit: 10000,
225
+ projectId,
226
+ traceId
227
+ })
228
+ if (queryResult.status === 'error') {
229
+ return queryResult
230
+ }
231
+ const documents = queryResult.documents || []
232
+ const deletePromises = documents.map(doc => {
233
+ const docId = doc.name.split('/').pop()
234
+ return this.deleteDocument({ collectionPath, projectId, documentId: docId, traceId })
235
+ })
236
+ await Promise.all(deletePromises)
237
+ return {
238
+ success: true,
239
+ deletedCount: documents.length,
240
+ traceId
241
+ }
242
+ } catch (error) {
243
+ return {
244
+ status: 'error',
245
+ message: error.message,
246
+ traceId
247
+ }
248
+ }
249
+ },
250
+
251
+ async deleteByPrefix (params) {
252
+ const { collectionPath, prefix, traceId } = params
253
+ const projectId = resolveProjectId(params)
254
+ try {
255
+ const listResult = await this.listDocuments({
256
+ collectionPath,
257
+ projectId,
258
+ page: { limit: 10000 },
259
+ traceId
260
+ })
261
+ if (listResult.status === 'error') {
262
+ return listResult
263
+ }
264
+ const documents = listResult.documents || []
265
+ const matchingDocs = documents.filter(doc => {
266
+ const docId = doc.name.split('/').pop()
267
+ return docId.startsWith(prefix)
268
+ })
269
+ const deletePromises = matchingDocs.map(doc => {
270
+ const docId = doc.name.split('/').pop()
271
+ return this.deleteDocument({ collectionPath, projectId, documentId: docId, traceId })
272
+ })
273
+ await Promise.all(deletePromises)
274
+ return {
275
+ success: true,
276
+ deletedCount: matchingDocs.length,
277
+ traceId
278
+ }
279
+ } catch (error) {
280
+ return {
281
+ status: 'error',
282
+ message: error.message,
283
+ traceId
284
+ }
285
+ }
286
+ },
287
+
288
+ async deleteBatch (params) {
289
+ const { collectionPath, documentIds = [], traceId } = params
290
+ const projectId = resolveProjectId(params)
291
+ try {
292
+ const results = await Promise.allSettled(
293
+ documentIds.map(docId =>
294
+ this.deleteDocument({ collectionPath, projectId, documentId: docId, traceId })
295
+ )
296
+ )
297
+ const succeeded = results.filter(r => r.status === 'fulfilled').length
298
+ const failedIds = documentIds.filter((_, idx) => results[idx].status === 'rejected')
299
+ return {
300
+ success: true,
301
+ deletedCount: succeeded,
302
+ failedIds,
303
+ traceId
304
+ }
305
+ } catch (error) {
306
+ return {
307
+ status: 'error',
308
+ message: error.message,
309
+ traceId
310
+ }
311
+ }
312
+ },
313
+
314
+ async executeQuery (params) {
315
+ const { collectionPath, where = [], orderBy, limit = 50, traceId } = params
316
+ const projectId = resolveProjectId(params)
317
+ try {
318
+ const structuredQuery = {
319
+ from: [{ collectionId: collectionPath }]
320
+ }
321
+ if (where.length > 0) {
322
+ structuredQuery.where = buildWhereFilter(where)
323
+ }
324
+ if (orderBy) {
325
+ structuredQuery.orderBy = [{
326
+ field: { fieldPath: orderBy.field },
327
+ direction: orderBy.direction || 'ASCENDING'
328
+ }]
329
+ }
330
+ if (limit) {
331
+ structuredQuery.limit = limit
332
+ }
333
+ const response = await fetch(`${FIRESTORE_API}/v1/projects/${projectId}/databases/${DATABASE}/documents:runQuery`, {
334
+ method: 'POST',
335
+ headers: { 'Content-Type': 'application/json' },
336
+ body: JSON.stringify({ structuredQuery })
337
+ })
338
+ if (!response.ok) {
339
+ throw new Error(`Firestore API error: ${response.statusText}`)
340
+ }
341
+ const results = await response.json()
342
+ const documents = results.filter(r => r.document).map(r => r.document)
343
+ return {
344
+ documents,
345
+ total: documents.length,
346
+ traceId
347
+ }
348
+ } catch (error) {
349
+ return {
350
+ status: 'error',
351
+ message: error.message,
352
+ traceId
353
+ }
354
+ }
355
+ },
356
+
357
+ async clearCollection (params) {
358
+ const { collectionPath, projectId, traceId } = params
359
+ try {
360
+ const listResult = await this.listDocuments({ collectionPath, projectId, page: { limit: 1000 }, traceId })
361
+ if (listResult.status === 'error') {
362
+ return listResult
363
+ }
364
+ const documents = listResult.documents || []
365
+ const deletePromises = documents.map(doc => {
366
+ const docId = doc.name.split('/').pop()
367
+ return this.deleteDocument({ collectionPath, projectId, documentId: docId, traceId })
368
+ })
369
+ await Promise.all(deletePromises)
370
+ untrackCollection(collectionPath)
371
+ return {
372
+ success: true,
373
+ deletedCount: documents.length,
374
+ traceId
375
+ }
376
+ } catch (error) {
377
+ return {
378
+ status: 'error',
379
+ message: error.message,
380
+ traceId
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ function buildWhereFilter (whereClauses) {
387
+ if (whereClauses.length === 1) {
388
+ return {
389
+ fieldFilter: {
390
+ field: { fieldPath: whereClauses[0].field },
391
+ op: mapOperator(whereClauses[0].operator),
392
+ value: convertValue(whereClauses[0].value)
393
+ }
394
+ }
395
+ }
396
+ return {
397
+ compositeFilter: {
398
+ op: 'AND',
399
+ filters: whereClauses.map(clause => ({
400
+ fieldFilter: {
401
+ field: { fieldPath: clause.field },
402
+ op: mapOperator(clause.operator),
403
+ value: convertValue(clause.value)
404
+ }
405
+ }))
406
+ }
407
+ }
408
+ }
409
+
410
+ function mapOperator (op) {
411
+ const opMap = {
412
+ '==': 'EQUAL',
413
+ '<': 'LESS_THAN',
414
+ '<=': 'LESS_THAN_OR_EQUAL',
415
+ '>': 'GREATER_THAN',
416
+ '>=': 'GREATER_THAN_OR_EQUAL',
417
+ '!=': 'NOT_EQUAL',
418
+ 'array-contains': 'ARRAY_CONTAINS',
419
+ in: 'IN',
420
+ 'array-contains-any': 'ARRAY_CONTAINS_ANY'
421
+ }
422
+ return opMap[op] || 'EQUAL'
423
+ }
424
+
425
+ function convertValue (value) {
426
+ if (typeof value === 'string') {
427
+ return { stringValue: value }
428
+ }
429
+ if (typeof value === 'number') {
430
+ if (Number.isInteger(value)) {
431
+ return { integerValue: String(value) }
432
+ }
433
+ return { doubleValue: value }
434
+ }
435
+ if (typeof value === 'boolean') {
436
+ return { booleanValue: value }
437
+ }
438
+ if (value === null) {
439
+ return { nullValue: null }
440
+ }
441
+ return { stringValue: String(value) }
442
+ }
443
+
444
+ function trackCollection (collectionPath) {
445
+ try {
446
+ const existing = SqliteStore.findOne(FIRESTORE_METADATA, { collectionName: collectionPath })
447
+ if (!existing) {
448
+ SqliteStore.create(FIRESTORE_METADATA, {
449
+ collectionName: collectionPath,
450
+ documentCount: 1,
451
+ createdAt: Date.now(),
452
+ updatedAt: Date.now()
453
+ })
454
+ } else {
455
+ SqliteStore.update(FIRESTORE_METADATA, existing._id, {
456
+ documentCount: (existing.documentCount || 0) + 1,
457
+ updatedAt: Date.now()
458
+ })
459
+ }
460
+ } catch (error) {
461
+ // Ignore tracking errors
462
+ }
463
+ }
464
+
465
+ function untrackCollection (collectionPath) {
466
+ try {
467
+ const existing = SqliteStore.findOne(FIRESTORE_METADATA, { collectionName: collectionPath })
468
+ if (existing) {
469
+ SqliteStore.delete(FIRESTORE_METADATA, existing._id)
470
+ }
471
+ } catch (error) {
472
+ // Ignore tracking errors
473
+ }
474
+ }
@@ -0,0 +1,23 @@
1
+ import KoaRouter from 'koa-router'
2
+ import { Controllers } from './Controllers.js'
3
+
4
+ const Router = new KoaRouter()
5
+ const v1 = new KoaRouter({ prefix: '/v1/firestore' })
6
+
7
+ v1.post('/projects/list', Controllers.listProjects)
8
+ v1.post('/collections/list', Controllers.listCollections)
9
+ v1.post('/documents/list', Controllers.listDocuments)
10
+ v1.post('/documents/get', Controllers.getDocument)
11
+ v1.post('/documents/create', Controllers.createDocument)
12
+ v1.post('/documents/update', Controllers.updateDocument)
13
+ v1.post('/documents/delete', Controllers.deleteDocument)
14
+ v1.post('/documents/delete-by-query', Controllers.deleteByQuery)
15
+ v1.post('/documents/delete-by-prefix', Controllers.deleteByPrefix)
16
+ v1.post('/documents/delete-batch', Controllers.deleteBatch)
17
+ v1.post('/query/execute', Controllers.executeQuery)
18
+ v1.post('/collection/clear', Controllers.clearCollection)
19
+ v1.get('/changes/stream', Controllers.streamChanges)
20
+
21
+ Router.use(v1.routes())
22
+
23
+ export { Router }