@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,710 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { exec } from 'child_process'
|
|
5
|
+
import { promisify } from 'util'
|
|
6
|
+
import { SqliteStore } from '../../singletons/SqliteStore.js'
|
|
7
|
+
import { FunctionsService } from '../../singletons/FunctionsService.js'
|
|
8
|
+
import { FunctionTriggerDispatcher } from '../../singletons/FunctionTriggerDispatcher.js'
|
|
9
|
+
import { CLOUD_FUNCTIONS, CLOUD_FUNCTION_INVOCATIONS } from '../../db/Tables.js'
|
|
10
|
+
import { Application } from '../../configs/Application.js'
|
|
11
|
+
import { FunctionTriggerTypes, FunctionTriggerTypeList } from '../../enums/FunctionTriggerTypes.js'
|
|
12
|
+
import { FunctionStatuses } from '../../enums/FunctionStatuses.js'
|
|
13
|
+
|
|
14
|
+
const DEFAULT_SOURCE_DIR = path.resolve(Application.storage.dataDir || './data', 'functions')
|
|
15
|
+
const CONTAINER_PROJECT_DIR = '/app'
|
|
16
|
+
|
|
17
|
+
function resolveHostPath (containerPath) {
|
|
18
|
+
const hostProjectDir = Application.hostProjectDir
|
|
19
|
+
if (!hostProjectDir || !containerPath) return containerPath
|
|
20
|
+
if (containerPath.startsWith(CONTAINER_PROJECT_DIR + '/')) {
|
|
21
|
+
return path.join(hostProjectDir, containerPath.slice(CONTAINER_PROJECT_DIR.length))
|
|
22
|
+
}
|
|
23
|
+
return containerPath
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildFileTree (baseDir, currentDir) {
|
|
27
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true })
|
|
28
|
+
const result = []
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue
|
|
31
|
+
const relativePath = path.relative(baseDir, path.join(currentDir, entry.name))
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
result.push({
|
|
34
|
+
name: entry.name,
|
|
35
|
+
path: relativePath,
|
|
36
|
+
type: 'directory',
|
|
37
|
+
children: buildFileTree(baseDir, path.join(currentDir, entry.name))
|
|
38
|
+
})
|
|
39
|
+
} else {
|
|
40
|
+
const ext = path.extname(entry.name).slice(1)
|
|
41
|
+
result.push({
|
|
42
|
+
name: entry.name,
|
|
43
|
+
path: relativePath,
|
|
44
|
+
type: 'file',
|
|
45
|
+
language: getLanguageFromExt(ext)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Sort: directories first, then files, both alphabetically
|
|
50
|
+
result.sort((a, b) => {
|
|
51
|
+
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
|
|
52
|
+
return a.name.localeCompare(b.name)
|
|
53
|
+
})
|
|
54
|
+
return result
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getLanguageFromExt (ext) {
|
|
58
|
+
const map = {
|
|
59
|
+
js: 'javascript', mjs: 'javascript', cjs: 'javascript',
|
|
60
|
+
ts: 'typescript', tsx: 'typescript',
|
|
61
|
+
json: 'json',
|
|
62
|
+
md: 'markdown',
|
|
63
|
+
yaml: 'yaml', yml: 'yaml',
|
|
64
|
+
html: 'html',
|
|
65
|
+
css: 'css',
|
|
66
|
+
sh: 'shell', bash: 'shell',
|
|
67
|
+
env: 'plaintext', txt: 'plaintext'
|
|
68
|
+
}
|
|
69
|
+
return map[ext] || 'plaintext'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Ensure functions directory has a package.json with type=commonjs
|
|
74
|
+
* so that .js files can use `exports` / `module.exports` syntax
|
|
75
|
+
* (the main project has "type": "module" which would break CJS functions)
|
|
76
|
+
*/
|
|
77
|
+
function ensureFunctionsPackageJson (dir) {
|
|
78
|
+
const pkgPath = path.join(dir, 'package.json')
|
|
79
|
+
if (!fs.existsSync(pkgPath)) {
|
|
80
|
+
fs.writeFileSync(pkgPath, JSON.stringify({
|
|
81
|
+
name: 'cloud-functions',
|
|
82
|
+
version: '1.0.0',
|
|
83
|
+
type: 'commonjs',
|
|
84
|
+
private: true
|
|
85
|
+
}, null, 2))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveFunction (params) {
|
|
90
|
+
const { id, name, traceId } = params
|
|
91
|
+
if (id) {
|
|
92
|
+
const fn = SqliteStore.get(CLOUD_FUNCTIONS, id)
|
|
93
|
+
if (!fn) return { success: false, message: `Function not found with id: ${id}`, traceId }
|
|
94
|
+
return fn
|
|
95
|
+
}
|
|
96
|
+
if (name) {
|
|
97
|
+
const matches = SqliteStore.find(CLOUD_FUNCTIONS, { name })
|
|
98
|
+
if (matches.length === 0) return { success: false, message: `Function not found: ${name}`, traceId }
|
|
99
|
+
return matches[0]
|
|
100
|
+
}
|
|
101
|
+
return { success: false, message: 'id or name is required', traceId }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function mergeLiveStatus (fn) {
|
|
105
|
+
const statuses = FunctionsService.getAllStatuses()
|
|
106
|
+
const live = statuses.find(s => s.name === fn.name)
|
|
107
|
+
if (live) {
|
|
108
|
+
return { ...fn, status: live.status, port: live.port, pid: live.pid, uptime: live.uptime }
|
|
109
|
+
}
|
|
110
|
+
return fn
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const Logic = {
|
|
114
|
+
async create (params) {
|
|
115
|
+
const { name, source, sourcePath, entryPoint, triggerType, triggerConfig, signatureType, runtime, environmentVariables, timeoutSeconds, description, traceId } = params
|
|
116
|
+
if (!name) return { success: false, message: 'name is required', traceId }
|
|
117
|
+
if (!source && !sourcePath) return { success: false, message: 'source or sourcePath is required', traceId }
|
|
118
|
+
if (!entryPoint) return { success: false, message: 'entryPoint is required', traceId }
|
|
119
|
+
if (!triggerType) return { success: false, message: 'triggerType is required', traceId }
|
|
120
|
+
if (!FunctionTriggerTypeList.includes(triggerType)) {
|
|
121
|
+
return { success: false, message: `Invalid triggerType. Must be one of: ${FunctionTriggerTypeList.join(', ')}`, traceId }
|
|
122
|
+
}
|
|
123
|
+
const existing = SqliteStore.find(CLOUD_FUNCTIONS, { name })
|
|
124
|
+
if (existing.length > 0) {
|
|
125
|
+
return { success: false, message: `Function with name '${name}' already exists`, traceId }
|
|
126
|
+
}
|
|
127
|
+
let resolvedSourcePath = sourcePath
|
|
128
|
+
let resolvedSource = source
|
|
129
|
+
if (!sourcePath && source) {
|
|
130
|
+
const funcDir = path.join(DEFAULT_SOURCE_DIR, name)
|
|
131
|
+
if (!fs.existsSync(funcDir)) {
|
|
132
|
+
fs.mkdirSync(funcDir, { recursive: true })
|
|
133
|
+
}
|
|
134
|
+
ensureFunctionsPackageJson(funcDir)
|
|
135
|
+
resolvedSourcePath = funcDir
|
|
136
|
+
const sourceFile = path.join(funcDir, 'index.js')
|
|
137
|
+
fs.writeFileSync(sourceFile, source)
|
|
138
|
+
resolvedSource = 'index.js'
|
|
139
|
+
}
|
|
140
|
+
const id = uuid()
|
|
141
|
+
const record = {
|
|
142
|
+
id,
|
|
143
|
+
name,
|
|
144
|
+
source: resolvedSource,
|
|
145
|
+
sourcePath: resolvedSourcePath || null,
|
|
146
|
+
entryPoint,
|
|
147
|
+
triggerType,
|
|
148
|
+
triggerConfig: triggerConfig || {},
|
|
149
|
+
signatureType: signatureType || 'cloudevent',
|
|
150
|
+
runtime: runtime || 'nodejs20',
|
|
151
|
+
environmentVariables: environmentVariables || {},
|
|
152
|
+
timeoutSeconds: timeoutSeconds || 60,
|
|
153
|
+
description: description || '',
|
|
154
|
+
status: FunctionStatuses.STOPPED,
|
|
155
|
+
enabled: 1,
|
|
156
|
+
invocationCount: 0,
|
|
157
|
+
createdAt: new Date().toISOString(),
|
|
158
|
+
updatedAt: new Date().toISOString()
|
|
159
|
+
}
|
|
160
|
+
SqliteStore.create(CLOUD_FUNCTIONS, record)
|
|
161
|
+
try {
|
|
162
|
+
await FunctionsService.deployFunction(record)
|
|
163
|
+
FunctionTriggerDispatcher.registerFunction(record)
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// Function created in DB but failed to start - record error status
|
|
166
|
+
SqliteStore.update(CLOUD_FUNCTIONS, id, {
|
|
167
|
+
status: FunctionStatuses.ERROR,
|
|
168
|
+
errorMessage: error.message
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
const created = SqliteStore.get(CLOUD_FUNCTIONS, id)
|
|
172
|
+
return {
|
|
173
|
+
data: { function: mergeLiveStatus(created) },
|
|
174
|
+
message: 'Function created',
|
|
175
|
+
traceId
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
list (params) {
|
|
180
|
+
const { filter, limit, offset, traceId } = params
|
|
181
|
+
const options = {
|
|
182
|
+
orderBy: 'created_at DESC'
|
|
183
|
+
}
|
|
184
|
+
if (limit !== undefined) options.limit = limit
|
|
185
|
+
if (offset !== undefined) options.offset = offset
|
|
186
|
+
if (filter) {
|
|
187
|
+
const where = {}
|
|
188
|
+
if (filter.triggerType) where.triggerType = filter.triggerType
|
|
189
|
+
if (filter.status) where.status = filter.status
|
|
190
|
+
if (filter.enabled !== undefined) where.enabled = filter.enabled ? 1 : 0
|
|
191
|
+
if (Object.keys(where).length > 0) options.where = where
|
|
192
|
+
}
|
|
193
|
+
const { data, total } = SqliteStore.list(CLOUD_FUNCTIONS, options)
|
|
194
|
+
const functions = data.map(fn => mergeLiveStatus(fn))
|
|
195
|
+
return { data: { functions, total }, traceId }
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
details (params) {
|
|
199
|
+
const fn = resolveFunction(params)
|
|
200
|
+
if (fn.success === false) return fn
|
|
201
|
+
const fnData = mergeLiveStatus(fn)
|
|
202
|
+
fnData.hostSourcePath = resolveHostPath(fn.sourcePath)
|
|
203
|
+
return { data: { function: fnData }, traceId: params.traceId }
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
async update (params) {
|
|
207
|
+
const { id, name, traceId, ...updates } = params
|
|
208
|
+
const fn = resolveFunction({ id, name, traceId })
|
|
209
|
+
if (fn.success === false) return fn
|
|
210
|
+
const allowedFields = ['description', 'triggerConfig', 'enabled', 'environmentVariables', 'timeoutSeconds', 'entryPoint', 'signatureType']
|
|
211
|
+
const cleanUpdates = {}
|
|
212
|
+
for (const field of allowedFields) {
|
|
213
|
+
if (updates[field] !== undefined) cleanUpdates[field] = updates[field]
|
|
214
|
+
}
|
|
215
|
+
if (Object.keys(cleanUpdates).length === 0) {
|
|
216
|
+
return { success: false, message: 'No valid fields to update', traceId }
|
|
217
|
+
}
|
|
218
|
+
cleanUpdates.updatedAt = new Date().toISOString()
|
|
219
|
+
const triggerConfigChanged = cleanUpdates.triggerConfig !== undefined &&
|
|
220
|
+
JSON.stringify(cleanUpdates.triggerConfig) !== JSON.stringify(fn.triggerConfig)
|
|
221
|
+
const enabledChanged = cleanUpdates.enabled !== undefined && cleanUpdates.enabled !== fn.enabled
|
|
222
|
+
SqliteStore.update(CLOUD_FUNCTIONS, fn.id, cleanUpdates)
|
|
223
|
+
if (triggerConfigChanged) {
|
|
224
|
+
FunctionTriggerDispatcher.unregisterFunction(fn)
|
|
225
|
+
const updatedFn = SqliteStore.get(CLOUD_FUNCTIONS, fn.id)
|
|
226
|
+
FunctionTriggerDispatcher.registerFunction(updatedFn)
|
|
227
|
+
}
|
|
228
|
+
if (enabledChanged) {
|
|
229
|
+
if (cleanUpdates.enabled) {
|
|
230
|
+
try {
|
|
231
|
+
await FunctionsService.startFunction(fn.name)
|
|
232
|
+
const updatedFn = SqliteStore.get(CLOUD_FUNCTIONS, fn.id)
|
|
233
|
+
FunctionTriggerDispatcher.registerFunction(updatedFn)
|
|
234
|
+
} catch (error) {
|
|
235
|
+
SqliteStore.update(CLOUD_FUNCTIONS, fn.id, {
|
|
236
|
+
status: FunctionStatuses.ERROR,
|
|
237
|
+
errorMessage: error.message
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
try {
|
|
242
|
+
await FunctionsService.stopFunction(fn.name)
|
|
243
|
+
} catch {
|
|
244
|
+
// may not be running
|
|
245
|
+
}
|
|
246
|
+
FunctionTriggerDispatcher.unregisterFunction(fn)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const updated = SqliteStore.get(CLOUD_FUNCTIONS, fn.id)
|
|
250
|
+
return {
|
|
251
|
+
data: { function: mergeLiveStatus(updated) },
|
|
252
|
+
message: 'Function updated',
|
|
253
|
+
traceId
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
async delete (params) {
|
|
258
|
+
const fn = resolveFunction(params)
|
|
259
|
+
if (fn.success === false) return fn
|
|
260
|
+
try {
|
|
261
|
+
await FunctionsService.removeFunction(fn.name)
|
|
262
|
+
} catch {
|
|
263
|
+
// may not be running
|
|
264
|
+
}
|
|
265
|
+
FunctionTriggerDispatcher.unregisterFunction(fn)
|
|
266
|
+
// Delete invocations for this function
|
|
267
|
+
try {
|
|
268
|
+
const invocations = SqliteStore.find(CLOUD_FUNCTION_INVOCATIONS, { functionId: fn.id })
|
|
269
|
+
for (const inv of invocations) {
|
|
270
|
+
SqliteStore.delete(CLOUD_FUNCTION_INVOCATIONS, inv.id)
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
// Ignore cleanup errors
|
|
274
|
+
}
|
|
275
|
+
SqliteStore.delete(CLOUD_FUNCTIONS, fn.id)
|
|
276
|
+
return { message: `Function '${fn.name}' deleted`, traceId: params.traceId }
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async start (params) {
|
|
280
|
+
const fn = resolveFunction(params)
|
|
281
|
+
if (fn.success === false) return fn
|
|
282
|
+
try {
|
|
283
|
+
const status = await FunctionsService.startFunction(fn.name)
|
|
284
|
+
return {
|
|
285
|
+
data: { function: mergeLiveStatus(SqliteStore.get(CLOUD_FUNCTIONS, fn.id)), status },
|
|
286
|
+
message: `Function '${fn.name}' started`,
|
|
287
|
+
traceId: params.traceId
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return { success: false, message: `Failed to start function: ${error.message}`, traceId: params.traceId }
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
async stop (params) {
|
|
295
|
+
const fn = resolveFunction(params)
|
|
296
|
+
if (fn.success === false) return fn
|
|
297
|
+
try {
|
|
298
|
+
await FunctionsService.stopFunction(fn.name)
|
|
299
|
+
return {
|
|
300
|
+
data: { function: mergeLiveStatus(SqliteStore.get(CLOUD_FUNCTIONS, fn.id)) },
|
|
301
|
+
message: `Function '${fn.name}' stopped`,
|
|
302
|
+
traceId: params.traceId
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return { success: false, message: `Failed to stop function: ${error.message}`, traceId: params.traceId }
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
async restart (params) {
|
|
310
|
+
const fn = resolveFunction(params)
|
|
311
|
+
if (fn.success === false) return fn
|
|
312
|
+
try {
|
|
313
|
+
const status = await FunctionsService.restartFunction(fn.name)
|
|
314
|
+
return {
|
|
315
|
+
data: { function: mergeLiveStatus(SqliteStore.get(CLOUD_FUNCTIONS, fn.id)), status },
|
|
316
|
+
message: `Function '${fn.name}' restarted`,
|
|
317
|
+
traceId: params.traceId
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return { success: false, message: `Failed to restart function: ${error.message}`, traceId: params.traceId }
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
readSource (params) {
|
|
325
|
+
const fn = resolveFunction(params)
|
|
326
|
+
if (fn.success === false) return fn
|
|
327
|
+
if (!fn.sourcePath || !fn.source) {
|
|
328
|
+
return { success: false, message: 'Function has no source path configured', traceId: params.traceId }
|
|
329
|
+
}
|
|
330
|
+
const filePath = path.isAbsolute(fn.source)
|
|
331
|
+
? fn.source
|
|
332
|
+
: path.join(fn.sourcePath, fn.source)
|
|
333
|
+
try {
|
|
334
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
335
|
+
return {
|
|
336
|
+
data: { source: content, filePath },
|
|
337
|
+
traceId: params.traceId
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
return { success: false, message: `Failed to read source: ${error.message}`, traceId: params.traceId }
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
async updateSource (params) {
|
|
345
|
+
const { content, traceId } = params
|
|
346
|
+
const fn = resolveFunction(params)
|
|
347
|
+
if (fn.success === false) return fn
|
|
348
|
+
if (!content) return { success: false, message: 'content is required', traceId }
|
|
349
|
+
if (!fn.sourcePath || !fn.source) {
|
|
350
|
+
return { success: false, message: 'Function has no source path configured', traceId }
|
|
351
|
+
}
|
|
352
|
+
const filePath = path.isAbsolute(fn.source)
|
|
353
|
+
? fn.source
|
|
354
|
+
: path.join(fn.sourcePath, fn.source)
|
|
355
|
+
try {
|
|
356
|
+
fs.writeFileSync(filePath, content)
|
|
357
|
+
SqliteStore.update(CLOUD_FUNCTIONS, fn.id, { updatedAt: new Date().toISOString() })
|
|
358
|
+
} catch (error) {
|
|
359
|
+
return { success: false, message: `Failed to write source: ${error.message}`, traceId }
|
|
360
|
+
}
|
|
361
|
+
// Restart function to pick up changes
|
|
362
|
+
try {
|
|
363
|
+
await FunctionsService.restartFunction(fn.name)
|
|
364
|
+
} catch {
|
|
365
|
+
// Function may not be running
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
data: { filePath },
|
|
369
|
+
message: 'Source updated and function restarted',
|
|
370
|
+
traceId
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
readDependencies (params) {
|
|
375
|
+
const fn = resolveFunction(params)
|
|
376
|
+
if (fn.success === false) return fn
|
|
377
|
+
const dir = fn.sourcePath || DEFAULT_SOURCE_DIR
|
|
378
|
+
const pkgPath = path.join(dir, 'package.json')
|
|
379
|
+
try {
|
|
380
|
+
if (!fs.existsSync(pkgPath)) {
|
|
381
|
+
return {
|
|
382
|
+
data: {
|
|
383
|
+
packageJson: JSON.stringify({
|
|
384
|
+
name: 'cloud-functions',
|
|
385
|
+
version: '1.0.0',
|
|
386
|
+
type: 'commonjs',
|
|
387
|
+
private: true,
|
|
388
|
+
dependencies: {}
|
|
389
|
+
}, null, 2),
|
|
390
|
+
exists: false
|
|
391
|
+
},
|
|
392
|
+
traceId: params.traceId
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const content = fs.readFileSync(pkgPath, 'utf-8')
|
|
396
|
+
return { data: { packageJson: content, exists: true }, traceId: params.traceId }
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return { success: false, message: `Failed to read package.json: ${error.message}`, traceId: params.traceId }
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
async updateDependencies (params) {
|
|
403
|
+
const { packageJson, install, traceId } = params
|
|
404
|
+
const fn = resolveFunction(params)
|
|
405
|
+
if (fn.success === false) return fn
|
|
406
|
+
if (!packageJson) return { success: false, message: 'packageJson is required', traceId }
|
|
407
|
+
const dir = fn.sourcePath || DEFAULT_SOURCE_DIR
|
|
408
|
+
if (!fs.existsSync(dir)) {
|
|
409
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
410
|
+
}
|
|
411
|
+
const pkgPath = path.join(dir, 'package.json')
|
|
412
|
+
try {
|
|
413
|
+
JSON.parse(packageJson)
|
|
414
|
+
} catch {
|
|
415
|
+
return { success: false, message: 'packageJson must be valid JSON', traceId }
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
fs.writeFileSync(pkgPath, packageJson)
|
|
419
|
+
} catch (error) {
|
|
420
|
+
return { success: false, message: `Failed to write package.json: ${error.message}`, traceId }
|
|
421
|
+
}
|
|
422
|
+
let installResult = null
|
|
423
|
+
if (install !== false) {
|
|
424
|
+
try {
|
|
425
|
+
const execAsync = promisify(exec)
|
|
426
|
+
const { stdout, stderr } = await execAsync('npm install --production', {
|
|
427
|
+
cwd: dir,
|
|
428
|
+
timeout: 60000
|
|
429
|
+
})
|
|
430
|
+
installResult = { success: true, stdout: stdout.trim(), stderr: stderr.trim() }
|
|
431
|
+
} catch (error) {
|
|
432
|
+
installResult = { success: false, error: error.message }
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Restart function to pick up new dependencies
|
|
436
|
+
try {
|
|
437
|
+
await FunctionsService.restartFunction(fn.name)
|
|
438
|
+
} catch {
|
|
439
|
+
// Function may not be running
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
data: { filePath: pkgPath, installResult },
|
|
443
|
+
message: install !== false ? 'Dependencies updated and installed' : 'package.json updated',
|
|
444
|
+
traceId
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
listFiles (params) {
|
|
449
|
+
const fn = resolveFunction(params)
|
|
450
|
+
if (fn.success === false) return fn
|
|
451
|
+
const dir = fn.sourcePath || path.join(DEFAULT_SOURCE_DIR, fn.name)
|
|
452
|
+
try {
|
|
453
|
+
if (!fs.existsSync(dir)) {
|
|
454
|
+
return { data: { files: [], basePath: dir }, traceId: params.traceId }
|
|
455
|
+
}
|
|
456
|
+
const tree = buildFileTree(dir, dir)
|
|
457
|
+
return { data: { files: tree, basePath: dir }, traceId: params.traceId }
|
|
458
|
+
} catch (error) {
|
|
459
|
+
return { success: false, message: `Failed to list files: ${error.message}`, traceId: params.traceId }
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
readFile (params) {
|
|
464
|
+
const { filePath, traceId } = params
|
|
465
|
+
const fn = resolveFunction(params)
|
|
466
|
+
if (fn.success === false) return fn
|
|
467
|
+
const dir = fn.sourcePath || DEFAULT_SOURCE_DIR
|
|
468
|
+
// Security: prevent path traversal
|
|
469
|
+
const resolved = path.resolve(dir, filePath)
|
|
470
|
+
if (!resolved.startsWith(path.resolve(dir))) {
|
|
471
|
+
return { success: false, message: 'Invalid file path: path traversal not allowed', traceId }
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const content = fs.readFileSync(resolved, 'utf-8')
|
|
475
|
+
const ext = path.extname(filePath).slice(1)
|
|
476
|
+
return { data: { content, filePath, language: getLanguageFromExt(ext) }, traceId }
|
|
477
|
+
} catch (error) {
|
|
478
|
+
return { success: false, message: `Failed to read file: ${error.message}`, traceId }
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
async writeFile (params) {
|
|
483
|
+
const { filePath, content, traceId } = params
|
|
484
|
+
const fn = resolveFunction(params)
|
|
485
|
+
if (fn.success === false) return fn
|
|
486
|
+
const dir = fn.sourcePath || DEFAULT_SOURCE_DIR
|
|
487
|
+
// Security: prevent path traversal
|
|
488
|
+
const resolved = path.resolve(dir, filePath)
|
|
489
|
+
if (!resolved.startsWith(path.resolve(dir))) {
|
|
490
|
+
return { success: false, message: 'Invalid file path: path traversal not allowed', traceId }
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
// Ensure parent directory exists
|
|
494
|
+
const parentDir = path.dirname(resolved)
|
|
495
|
+
if (!fs.existsSync(parentDir)) {
|
|
496
|
+
fs.mkdirSync(parentDir, { recursive: true })
|
|
497
|
+
}
|
|
498
|
+
fs.writeFileSync(resolved, content)
|
|
499
|
+
return { data: { filePath }, message: 'File saved', traceId }
|
|
500
|
+
} catch (error) {
|
|
501
|
+
return { success: false, message: `Failed to write file: ${error.message}`, traceId }
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
deleteFile (params) {
|
|
506
|
+
const { filePath, traceId } = params
|
|
507
|
+
const fn = resolveFunction(params)
|
|
508
|
+
if (fn.success === false) return fn
|
|
509
|
+
const dir = fn.sourcePath || DEFAULT_SOURCE_DIR
|
|
510
|
+
const resolved = path.resolve(dir, filePath)
|
|
511
|
+
if (!resolved.startsWith(path.resolve(dir))) {
|
|
512
|
+
return { success: false, message: 'Invalid file path: path traversal not allowed', traceId }
|
|
513
|
+
}
|
|
514
|
+
// Prevent deleting the main source file or package.json
|
|
515
|
+
const basename = path.basename(resolved)
|
|
516
|
+
if (basename === 'package.json' || resolved === path.resolve(dir, fn.source)) {
|
|
517
|
+
return { success: false, message: 'Cannot delete the main source file or package.json', traceId }
|
|
518
|
+
}
|
|
519
|
+
try {
|
|
520
|
+
if (!fs.existsSync(resolved)) {
|
|
521
|
+
return { success: false, message: `File not found: ${filePath}`, traceId }
|
|
522
|
+
}
|
|
523
|
+
fs.unlinkSync(resolved)
|
|
524
|
+
// Clean up empty parent directories
|
|
525
|
+
const parentDir = path.dirname(resolved)
|
|
526
|
+
if (parentDir !== path.resolve(dir) && fs.readdirSync(parentDir).length === 0) {
|
|
527
|
+
fs.rmdirSync(parentDir)
|
|
528
|
+
}
|
|
529
|
+
return { data: { filePath }, message: 'File deleted', traceId }
|
|
530
|
+
} catch (error) {
|
|
531
|
+
return { success: false, message: `Failed to delete file: ${error.message}`, traceId }
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
async invoke (params) {
|
|
536
|
+
const { payload, traceId } = params
|
|
537
|
+
const fn = resolveFunction(params)
|
|
538
|
+
if (fn.success === false) return fn
|
|
539
|
+
const cloudEvent = FunctionTriggerDispatcher.buildManualCloudEvent(payload)
|
|
540
|
+
try {
|
|
541
|
+
const result = await FunctionTriggerDispatcher.invokeFunction(fn, cloudEvent, 'manual')
|
|
542
|
+
return {
|
|
543
|
+
data: { invocation: result },
|
|
544
|
+
message: `Function '${fn.name}' invoked`,
|
|
545
|
+
traceId
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
return { success: false, message: `Invocation failed: ${error.message}`, traceId }
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
async callHttpFunction (params) {
|
|
553
|
+
const { functionName, method, headers, body, path: subPath, query, traceId } = params
|
|
554
|
+
const matches = SqliteStore.find(CLOUD_FUNCTIONS, { name: functionName })
|
|
555
|
+
if (matches.length === 0) {
|
|
556
|
+
return { success: false, message: `Function not found: ${functionName}`, traceId }
|
|
557
|
+
}
|
|
558
|
+
const fn = matches[0]
|
|
559
|
+
if (fn.triggerType !== FunctionTriggerTypes.HTTP) {
|
|
560
|
+
return { success: false, message: `Function '${functionName}' is not an HTTP function`, traceId }
|
|
561
|
+
}
|
|
562
|
+
const endpointUrl = FunctionsService.getEndpointUrl(fn.name)
|
|
563
|
+
if (!endpointUrl) {
|
|
564
|
+
return { success: false, message: `Function '${functionName}' is not running`, traceId }
|
|
565
|
+
}
|
|
566
|
+
const targetUrl = new URL(endpointUrl)
|
|
567
|
+
if (subPath) targetUrl.pathname = `/${subPath}`
|
|
568
|
+
if (query) {
|
|
569
|
+
for (const [key, value] of Object.entries(query)) {
|
|
570
|
+
targetUrl.searchParams.set(key, value)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const startMs = Date.now()
|
|
574
|
+
const invocationId = uuid()
|
|
575
|
+
const startedAt = new Date().toISOString()
|
|
576
|
+
try {
|
|
577
|
+
const fetchOptions = {
|
|
578
|
+
method,
|
|
579
|
+
headers: {
|
|
580
|
+
'Content-Type': headers['content-type'] || 'application/json'
|
|
581
|
+
},
|
|
582
|
+
signal: AbortSignal.timeout((fn.timeoutSeconds || 60) * 1000)
|
|
583
|
+
}
|
|
584
|
+
if (method !== 'GET' && method !== 'HEAD' && body) {
|
|
585
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body)
|
|
586
|
+
}
|
|
587
|
+
const response = await fetch(targetUrl.toString(), fetchOptions)
|
|
588
|
+
const responseTimeMs = Date.now() - startMs
|
|
589
|
+
const responseBody = await response.text()
|
|
590
|
+
const responseHeaders = {}
|
|
591
|
+
response.headers.forEach((value, key) => {
|
|
592
|
+
responseHeaders[key] = value
|
|
593
|
+
})
|
|
594
|
+
// Record invocation
|
|
595
|
+
try {
|
|
596
|
+
SqliteStore.create(CLOUD_FUNCTION_INVOCATIONS, {
|
|
597
|
+
id: invocationId,
|
|
598
|
+
functionId: fn.id,
|
|
599
|
+
functionName: fn.name,
|
|
600
|
+
triggerType: FunctionTriggerTypes.HTTP,
|
|
601
|
+
triggerSource: `http:${method} ${subPath || '/'}`,
|
|
602
|
+
responseStatus: response.status,
|
|
603
|
+
responseBody,
|
|
604
|
+
responseTimeMs,
|
|
605
|
+
startedAt,
|
|
606
|
+
completedAt: new Date().toISOString()
|
|
607
|
+
})
|
|
608
|
+
SqliteStore.update(CLOUD_FUNCTIONS, fn.id, {
|
|
609
|
+
invocationCount: (fn.invocationCount || 0) + 1,
|
|
610
|
+
lastInvokedAt: new Date().toISOString(),
|
|
611
|
+
lastStatus: response.status
|
|
612
|
+
})
|
|
613
|
+
} catch {
|
|
614
|
+
// Ignore recording errors
|
|
615
|
+
}
|
|
616
|
+
FunctionsService.broadcastInvocation({
|
|
617
|
+
id: invocationId,
|
|
618
|
+
functionName: fn.name,
|
|
619
|
+
triggerType: FunctionTriggerTypes.HTTP,
|
|
620
|
+
responseStatus: response.status,
|
|
621
|
+
responseTimeMs,
|
|
622
|
+
completedAt: new Date().toISOString()
|
|
623
|
+
})
|
|
624
|
+
return {
|
|
625
|
+
status: response.status,
|
|
626
|
+
headers: responseHeaders,
|
|
627
|
+
body: responseBody
|
|
628
|
+
}
|
|
629
|
+
} catch (error) {
|
|
630
|
+
const responseTimeMs = Date.now() - startMs
|
|
631
|
+
try {
|
|
632
|
+
SqliteStore.create(CLOUD_FUNCTION_INVOCATIONS, {
|
|
633
|
+
id: invocationId,
|
|
634
|
+
functionId: fn.id,
|
|
635
|
+
functionName: fn.name,
|
|
636
|
+
triggerType: FunctionTriggerTypes.HTTP,
|
|
637
|
+
triggerSource: `http:${method} ${subPath || '/'}`,
|
|
638
|
+
error: error.message,
|
|
639
|
+
responseTimeMs,
|
|
640
|
+
startedAt,
|
|
641
|
+
completedAt: new Date().toISOString()
|
|
642
|
+
})
|
|
643
|
+
} catch {
|
|
644
|
+
// Ignore
|
|
645
|
+
}
|
|
646
|
+
return { success: false, message: `HTTP call failed: ${error.message}`, traceId }
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
listInvocations (params) {
|
|
651
|
+
const { functionId, functionName, triggerType, limit, offset, traceId } = params
|
|
652
|
+
const options = {
|
|
653
|
+
orderBy: 'started_at DESC',
|
|
654
|
+
limit: limit || 50,
|
|
655
|
+
offset: offset || 0
|
|
656
|
+
}
|
|
657
|
+
const where = {}
|
|
658
|
+
if (functionId) where.functionId = functionId
|
|
659
|
+
if (functionName) where.functionName = functionName
|
|
660
|
+
if (triggerType) where.triggerType = triggerType
|
|
661
|
+
if (Object.keys(where).length > 0) options.where = where
|
|
662
|
+
const { data, total } = SqliteStore.list(CLOUD_FUNCTION_INVOCATIONS, options)
|
|
663
|
+
return { data: { invocations: data, total }, traceId }
|
|
664
|
+
},
|
|
665
|
+
|
|
666
|
+
invocationDetails (params) {
|
|
667
|
+
const { id, traceId } = params
|
|
668
|
+
if (!id) return { success: false, message: 'id is required', traceId }
|
|
669
|
+
const invocation = SqliteStore.get(CLOUD_FUNCTION_INVOCATIONS, id)
|
|
670
|
+
if (!invocation) return { success: false, message: `Invocation not found: ${id}`, traceId }
|
|
671
|
+
return { data: { invocation }, traceId }
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
clearInvocations (params) {
|
|
675
|
+
const { functionId, traceId } = params
|
|
676
|
+
if (functionId) {
|
|
677
|
+
const invocations = SqliteStore.find(CLOUD_FUNCTION_INVOCATIONS, { functionId })
|
|
678
|
+
for (const inv of invocations) {
|
|
679
|
+
SqliteStore.delete(CLOUD_FUNCTION_INVOCATIONS, inv.id)
|
|
680
|
+
}
|
|
681
|
+
return { message: `Cleared ${invocations.length} invocations for function`, traceId }
|
|
682
|
+
}
|
|
683
|
+
SqliteStore.clear(CLOUD_FUNCTION_INVOCATIONS)
|
|
684
|
+
return { message: 'All invocations cleared', traceId }
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
stats (params) {
|
|
688
|
+
const { traceId } = params
|
|
689
|
+
const { data: allFunctions } = SqliteStore.list(CLOUD_FUNCTIONS, {})
|
|
690
|
+
const byTriggerType = {}
|
|
691
|
+
const byStatus = {}
|
|
692
|
+
for (const fn of allFunctions) {
|
|
693
|
+
byTriggerType[fn.triggerType] = (byTriggerType[fn.triggerType] || 0) + 1
|
|
694
|
+
const liveStatus = mergeLiveStatus(fn).status
|
|
695
|
+
byStatus[liveStatus] = (byStatus[liveStatus] || 0) + 1
|
|
696
|
+
}
|
|
697
|
+
const { total: totalInvocations } = SqliteStore.list(CLOUD_FUNCTION_INVOCATIONS, { limit: 0 })
|
|
698
|
+
const runtimeStatuses = FunctionsService.getAllStatuses()
|
|
699
|
+
return {
|
|
700
|
+
data: {
|
|
701
|
+
totalFunctions: allFunctions.length,
|
|
702
|
+
totalInvocations,
|
|
703
|
+
byTriggerType,
|
|
704
|
+
byStatus,
|
|
705
|
+
runningProcesses: runtimeStatuses.length
|
|
706
|
+
},
|
|
707
|
+
traceId
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|