@pikku/core 0.12.13 → 0.12.15

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 (150) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/errors/errors.d.ts +18 -0
  3. package/dist/errors/errors.js +33 -0
  4. package/dist/function/function-runner.d.ts +1 -1
  5. package/dist/function/function-runner.js +10 -5
  6. package/dist/function/functions.types.js +1 -0
  7. package/dist/function/index.d.ts +2 -1
  8. package/dist/function/index.js +1 -0
  9. package/dist/handle-error.d.ts +2 -2
  10. package/dist/handle-error.js +8 -8
  11. package/dist/index.d.ts +1 -2
  12. package/dist/index.js +1 -2
  13. package/dist/middleware/index.d.ts +2 -0
  14. package/dist/middleware/index.js +2 -0
  15. package/dist/middleware/remote-auth.js +5 -2
  16. package/dist/permissions.d.ts +13 -1
  17. package/dist/permissions.js +51 -0
  18. package/dist/pikku-state.d.ts +0 -1
  19. package/dist/pikku-state.js +2 -4
  20. package/dist/remote.d.ts +10 -0
  21. package/dist/remote.js +32 -0
  22. package/dist/schema.js +2 -0
  23. package/dist/services/credential-service.d.ts +11 -0
  24. package/dist/services/credential-wire-service.d.ts +14 -3
  25. package/dist/services/credential-wire-service.js +49 -6
  26. package/dist/services/deployment-service.d.ts +12 -1
  27. package/dist/services/in-memory-queue-service.d.ts +7 -0
  28. package/dist/services/in-memory-queue-service.js +28 -0
  29. package/dist/services/index.d.ts +4 -1
  30. package/dist/services/index.js +2 -1
  31. package/dist/services/local-credential-service.d.ts +2 -0
  32. package/dist/services/local-credential-service.js +20 -0
  33. package/dist/services/logger-console.d.ts +26 -40
  34. package/dist/services/logger-console.js +102 -46
  35. package/dist/services/logger.d.ts +5 -0
  36. package/dist/services/meta-service.d.ts +175 -0
  37. package/dist/services/meta-service.js +310 -0
  38. package/dist/services/pikku-user-id.d.ts +3 -0
  39. package/dist/services/pikku-user-id.js +16 -0
  40. package/dist/services/typed-credential-service.d.ts +2 -0
  41. package/dist/services/typed-credential-service.js +6 -0
  42. package/dist/services/workflow-service.d.ts +6 -1
  43. package/dist/testing/service-tests.js +0 -8
  44. package/dist/types/core.types.d.ts +14 -2
  45. package/dist/types/state.types.d.ts +9 -2
  46. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +21 -0
  47. package/dist/wirings/ai-agent/ai-agent-prepare.js +90 -21
  48. package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
  49. package/dist/wirings/ai-agent/ai-agent-runner.js +24 -2
  50. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +2 -1
  51. package/dist/wirings/ai-agent/ai-agent-stream.js +61 -3
  52. package/dist/wirings/ai-agent/ai-agent.types.d.ts +25 -4
  53. package/dist/wirings/ai-agent/index.d.ts +1 -1
  54. package/dist/wirings/ai-agent/index.js +1 -1
  55. package/dist/wirings/channel/channel-runner.js +3 -3
  56. package/dist/wirings/cli/cli-runner.js +4 -3
  57. package/dist/wirings/cli/command-parser.js +7 -3
  58. package/dist/wirings/http/http-runner.d.ts +1 -1
  59. package/dist/wirings/http/http-runner.js +23 -15
  60. package/dist/wirings/http/http.types.d.ts +2 -0
  61. package/dist/wirings/mcp/mcp-runner.js +5 -3
  62. package/dist/wirings/queue/queue-runner.d.ts +3 -1
  63. package/dist/wirings/queue/queue-runner.js +8 -3
  64. package/dist/wirings/queue/queue.types.d.ts +6 -0
  65. package/dist/wirings/rpc/rpc-runner.js +57 -90
  66. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
  67. package/dist/wirings/scheduler/scheduler-runner.js +9 -5
  68. package/dist/wirings/trigger/trigger-runner.js +4 -2
  69. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +2 -0
  70. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
  71. package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
  72. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
  73. package/dist/wirings/workflow/graph/graph-runner.js +18 -4
  74. package/dist/wirings/workflow/index.d.ts +4 -2
  75. package/dist/wirings/workflow/index.js +4 -2
  76. package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
  77. package/dist/wirings/workflow/pikku-workflow-service.js +218 -24
  78. package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
  79. package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
  80. package/dist/wirings/workflow/workflow.types.d.ts +19 -1
  81. package/package.json +4 -1
  82. package/src/errors/errors.ts +44 -0
  83. package/src/function/function-runner.ts +24 -13
  84. package/src/function/functions.types.ts +1 -0
  85. package/src/function/index.ts +10 -0
  86. package/src/handle-error.test.ts +2 -2
  87. package/src/handle-error.ts +8 -8
  88. package/src/index.ts +1 -2
  89. package/src/middleware/index.ts +2 -0
  90. package/src/middleware/remote-auth.test.ts +4 -4
  91. package/src/middleware/remote-auth.ts +7 -2
  92. package/src/permissions.ts +70 -0
  93. package/src/pikku-state.test.ts +0 -26
  94. package/src/pikku-state.ts +2 -5
  95. package/src/remote.ts +46 -0
  96. package/src/schema.ts +1 -0
  97. package/src/services/credential-service.ts +13 -0
  98. package/src/services/credential-wire-service.test.ts +174 -0
  99. package/src/services/credential-wire-service.ts +52 -8
  100. package/src/services/deployment-service.ts +17 -1
  101. package/src/services/in-memory-queue-service.ts +46 -0
  102. package/src/services/index.ts +21 -1
  103. package/src/services/local-credential-service.ts +22 -0
  104. package/src/services/logger-console.ts +127 -47
  105. package/src/services/logger.ts +6 -0
  106. package/src/services/meta-service.ts +523 -0
  107. package/src/services/pikku-user-id.test.ts +62 -0
  108. package/src/services/pikku-user-id.ts +17 -0
  109. package/src/services/typed-credential-service.ts +8 -0
  110. package/src/services/workflow-service.ts +8 -0
  111. package/src/testing/service-tests.ts +0 -10
  112. package/src/types/core.types.ts +16 -2
  113. package/src/types/state.types.ts +7 -2
  114. package/src/wirings/ai-agent/ai-agent-prepare.ts +122 -41
  115. package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
  116. package/src/wirings/ai-agent/ai-agent-runner.ts +22 -3
  117. package/src/wirings/ai-agent/ai-agent-stream.ts +115 -3
  118. package/src/wirings/ai-agent/ai-agent.types.ts +27 -4
  119. package/src/wirings/ai-agent/index.ts +1 -0
  120. package/src/wirings/channel/channel-handler.ts +0 -1
  121. package/src/wirings/channel/channel-runner.ts +4 -5
  122. package/src/wirings/cli/cli-runner.test.ts +8 -7
  123. package/src/wirings/cli/cli-runner.ts +5 -4
  124. package/src/wirings/cli/command-parser.ts +8 -3
  125. package/src/wirings/http/http-runner.ts +27 -18
  126. package/src/wirings/http/http.types.ts +2 -0
  127. package/src/wirings/mcp/mcp-runner.ts +7 -10
  128. package/src/wirings/queue/queue-runner.test.ts +5 -11
  129. package/src/wirings/queue/queue-runner.ts +12 -4
  130. package/src/wirings/queue/queue.types.ts +6 -0
  131. package/src/wirings/rpc/rpc-runner.ts +91 -118
  132. package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
  133. package/src/wirings/scheduler/scheduler-runner.ts +13 -5
  134. package/src/wirings/trigger/trigger-runner.test.ts +10 -11
  135. package/src/wirings/trigger/trigger-runner.ts +6 -4
  136. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +2 -0
  137. package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
  138. package/src/wirings/workflow/graph/graph-runner.ts +19 -4
  139. package/src/wirings/workflow/index.ts +17 -6
  140. package/src/wirings/workflow/pikku-workflow-service.ts +286 -24
  141. package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
  142. package/src/wirings/workflow/workflow.types.ts +18 -1
  143. package/tsconfig.tsbuildinfo +1 -1
  144. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
  145. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
  146. package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
  147. package/dist/wirings/workflow/workflow-helpers.js +0 -38
  148. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
  149. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  150. package/src/wirings/workflow/workflow-helpers.ts +0 -79
@@ -0,0 +1,523 @@
1
+ import { readFile, readdir } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import type { JSONSchema7 } from 'json-schema'
4
+ import type { HTTPWiringsMeta } from '../wirings/http/http.types.js'
5
+ import type { ChannelsMeta } from '../wirings/channel/channel.types.js'
6
+ import type { ScheduledTasksMeta } from '../wirings/scheduler/scheduler.types.js'
7
+ import type { QueueWorkersMeta } from '../wirings/queue/queue.types.js'
8
+ import type { CLIMeta } from '../wirings/cli/cli.types.js'
9
+ import type {
10
+ MCPResourceMeta,
11
+ MCPToolMeta,
12
+ MCPPromptMeta,
13
+ } from '../wirings/mcp/mcp.types.js'
14
+ import type { WorkflowsMeta } from '../wirings/workflow/workflow.types.js'
15
+ import type {
16
+ TriggerMeta,
17
+ TriggerSourceMeta,
18
+ } from '../wirings/trigger/trigger.types.js'
19
+ import type { SecretDefinitionsMeta } from '../wirings/secret/secret.types.js'
20
+ import type { CredentialDefinitionsMeta } from '../wirings/credential/credential.types.js'
21
+ import type { VariableDefinitionsMeta } from '../wirings/variable/variable.types.js'
22
+ import type {
23
+ FunctionMeta,
24
+ FunctionsMeta,
25
+ MiddlewareMetadata,
26
+ PermissionMetadata,
27
+ } from '../types/core.types.js'
28
+ import type { AIAgentMeta } from '../wirings/ai-agent/ai-agent.types.js'
29
+
30
+ // Re-export core types for consumers
31
+ export type {
32
+ FunctionMeta,
33
+ FunctionsMeta,
34
+ MiddlewareMetadata as MiddlewareMeta,
35
+ PermissionMetadata as PermissionMeta,
36
+ }
37
+ export type AgentsMeta = AIAgentMeta
38
+ export type AgentMeta = AIAgentMeta[string]
39
+
40
+ export interface MCPMeta {
41
+ resources: MCPResourceMeta
42
+ tools: MCPToolMeta
43
+ prompts: MCPPromptMeta
44
+ }
45
+
46
+ export type RPCMetaRecord = Record<string, string>
47
+
48
+ export interface ServiceMeta {
49
+ name: string
50
+ summary: string
51
+ description: string
52
+ package: string
53
+ path: string
54
+ version: string
55
+ interface: string
56
+ expandedProperties: Record<string, string>
57
+ }
58
+
59
+ export type ServicesMetaRecord = Record<string, ServiceMeta>
60
+
61
+ export interface MiddlewareDefinitionMeta {
62
+ services: { optimized: boolean; services: string[] }
63
+ sourceFile: string
64
+ position: number
65
+ exportedName: string | null
66
+ factory?: boolean
67
+ name?: string
68
+ description?: string
69
+ package?: string
70
+ }
71
+
72
+ export interface MiddlewareInstanceMeta {
73
+ definitionId: string
74
+ sourceFile: string
75
+ position: number
76
+ isFactoryCall: boolean
77
+ }
78
+
79
+ export interface GroupMeta {
80
+ exportName: string | null
81
+ sourceFile: string
82
+ position: number
83
+ services: { optimized: boolean; services: string[] }
84
+ count: number
85
+ instanceIds: string[]
86
+ isFactory: boolean
87
+ }
88
+
89
+ export interface MiddlewareGroupsMeta {
90
+ definitions: Record<string, MiddlewareDefinitionMeta>
91
+ instances: Record<string, MiddlewareInstanceMeta>
92
+ httpGroups: Record<string, GroupMeta>
93
+ tagGroups: Record<string, GroupMeta>
94
+ }
95
+
96
+ export interface PermissionDefinitionMeta {
97
+ services: { optimized: boolean; services: string[] }
98
+ sourceFile: string
99
+ position: number
100
+ exportedName: string | null
101
+ factory?: boolean
102
+ name?: string
103
+ description?: string
104
+ }
105
+
106
+ export interface PermissionsGroupsMeta {
107
+ definitions: Record<string, PermissionDefinitionMeta>
108
+ httpGroups: Record<string, GroupMeta>
109
+ tagGroups: Record<string, GroupMeta>
110
+ }
111
+
112
+ /**
113
+ * Abstraction over .pikku metadata file access.
114
+ * All paths are relative to the .pikku root directory.
115
+ *
116
+ * Node.js uses LocalMetaService (filesystem).
117
+ * Cloudflare uses an R2/KV implementation.
118
+ */
119
+ export interface MetaService {
120
+ /** Base path for local filesystem implementations. Undefined for remote/non-local implementations. */
121
+ readonly basePath?: string
122
+ /** Read a file's content by relative path. Returns null if not found. */
123
+ readFile(relativePath: string): Promise<string | null>
124
+ /** List files in a directory by relative path. Returns empty array if not found. */
125
+ readDir(relativePath: string): Promise<string[]>
126
+
127
+ // -- Typed metadata accessors --
128
+
129
+ getHttpMeta(): Promise<HTTPWiringsMeta>
130
+ getChannelsMeta(): Promise<ChannelsMeta>
131
+ getSchedulerMeta(): Promise<ScheduledTasksMeta>
132
+ getQueueMeta(): Promise<QueueWorkersMeta>
133
+ getCliMeta(): Promise<CLIMeta>
134
+ getMcpMeta(): Promise<MCPMeta>
135
+ getRpcMeta(): Promise<RPCMetaRecord>
136
+ getWorkflowMeta(): Promise<WorkflowsMeta>
137
+ getTriggerMeta(): Promise<TriggerMeta>
138
+ getTriggerSourceMeta(): Promise<TriggerSourceMeta>
139
+ getFunctionsMeta(): Promise<FunctionsMeta>
140
+ getMiddlewareGroupsMeta(): Promise<MiddlewareGroupsMeta>
141
+ getPermissionsGroupsMeta(): Promise<PermissionsGroupsMeta>
142
+ getAgentsMeta(): Promise<AgentsMeta>
143
+ getSecretsMeta(): Promise<SecretDefinitionsMeta>
144
+ getCredentialsMeta(): Promise<CredentialDefinitionsMeta>
145
+ getVariablesMeta(): Promise<VariableDefinitionsMeta>
146
+ getServicesMeta(): Promise<ServicesMetaRecord>
147
+
148
+ getSchema(schemaName: string): Promise<JSONSchema7 | null>
149
+ getSchemas(schemaNames: string[]): Promise<Record<string, JSONSchema7 | null>>
150
+
151
+ clearCache(): void
152
+ }
153
+
154
+ /**
155
+ * Node.js filesystem implementation of MetaService.
156
+ * Reads .gen.json files from a local .pikku directory.
157
+ */
158
+ export class LocalMetaService implements MetaService {
159
+ public readonly basePath: string
160
+
161
+ private httpMetaCache: HTTPWiringsMeta | null = null
162
+ private channelsMetaCache: ChannelsMeta | null = null
163
+ private schedulerMetaCache: ScheduledTasksMeta | null = null
164
+ private queueMetaCache: QueueWorkersMeta | null = null
165
+ private cliMetaCache: CLIMeta | null = null
166
+ private mcpMetaCache: MCPMeta | null = null
167
+ private rpcMetaCache: RPCMetaRecord | null = null
168
+ private workflowMetaCache: WorkflowsMeta | null = null
169
+ private triggerMetaCache: TriggerMeta | null = null
170
+ private triggerSourceMetaCache: TriggerSourceMeta | null = null
171
+ private functionsMetaCache: FunctionsMeta | null = null
172
+ private servicesMetaCache: ServicesMetaRecord | null = null
173
+ private secretsMetaCache: SecretDefinitionsMeta | null = null
174
+ private credentialsMetaCache: CredentialDefinitionsMeta | null = null
175
+ private variablesMetaCache: VariableDefinitionsMeta | null = null
176
+ private middlewareGroupsMetaCache: MiddlewareGroupsMeta | null = null
177
+ private permissionsGroupsMetaCache: PermissionsGroupsMeta | null = null
178
+ private agentsMetaCache: AgentsMeta | null = null
179
+ private schemaCache = new Map<string, JSONSchema7>()
180
+
181
+ constructor(basePath: string) {
182
+ this.basePath = basePath
183
+ }
184
+
185
+ async readFile(relativePath: string): Promise<string | null> {
186
+ try {
187
+ return await readFile(join(this.basePath, relativePath), 'utf-8')
188
+ } catch {
189
+ return null
190
+ }
191
+ }
192
+
193
+ async readDir(relativePath: string): Promise<string[]> {
194
+ try {
195
+ return await readdir(join(this.basePath, relativePath))
196
+ } catch {
197
+ return []
198
+ }
199
+ }
200
+
201
+ clearCache(): void {
202
+ this.httpMetaCache = null
203
+ this.channelsMetaCache = null
204
+ this.schedulerMetaCache = null
205
+ this.queueMetaCache = null
206
+ this.cliMetaCache = null
207
+ this.mcpMetaCache = null
208
+ this.rpcMetaCache = null
209
+ this.workflowMetaCache = null
210
+ this.triggerMetaCache = null
211
+ this.triggerSourceMetaCache = null
212
+ this.functionsMetaCache = null
213
+ this.servicesMetaCache = null
214
+ this.secretsMetaCache = null
215
+ this.credentialsMetaCache = null
216
+ this.variablesMetaCache = null
217
+ this.middlewareGroupsMetaCache = null
218
+ this.permissionsGroupsMetaCache = null
219
+ this.agentsMetaCache = null
220
+ this.schemaCache.clear()
221
+ }
222
+
223
+ // -- Private helpers --
224
+
225
+ private async readMetaJson<T>(
226
+ dir: string,
227
+ baseName: string
228
+ ): Promise<string | null> {
229
+ const verbose = await this.readFile(`${dir}/${baseName}-verbose.gen.json`)
230
+ if (verbose) return verbose
231
+ const minimal = await this.readFile(`${dir}/${baseName}.gen.json`)
232
+ if (minimal) return minimal
233
+ return null
234
+ }
235
+
236
+ // -- Typed metadata accessors --
237
+
238
+ async getHttpMeta(): Promise<HTTPWiringsMeta> {
239
+ if (this.httpMetaCache) return this.httpMetaCache
240
+
241
+ const content = await this.readMetaJson('http', 'pikku-http-wirings-meta')
242
+ this.httpMetaCache = content
243
+ ? JSON.parse(content)
244
+ : {
245
+ get: {},
246
+ post: {},
247
+ put: {},
248
+ delete: {},
249
+ patch: {},
250
+ head: {},
251
+ options: {},
252
+ }
253
+ return this.httpMetaCache!
254
+ }
255
+
256
+ async getChannelsMeta(): Promise<ChannelsMeta> {
257
+ if (this.channelsMetaCache) return this.channelsMetaCache
258
+
259
+ const content = await this.readMetaJson('channel', 'pikku-channels-meta')
260
+ this.channelsMetaCache = content ? JSON.parse(content) : {}
261
+ return this.channelsMetaCache!
262
+ }
263
+
264
+ async getSchedulerMeta(): Promise<ScheduledTasksMeta> {
265
+ if (this.schedulerMetaCache) return this.schedulerMetaCache
266
+
267
+ const content = await this.readMetaJson(
268
+ 'scheduler',
269
+ 'pikku-schedulers-wirings-meta'
270
+ )
271
+ this.schedulerMetaCache = content ? JSON.parse(content) : {}
272
+ return this.schedulerMetaCache!
273
+ }
274
+
275
+ async getQueueMeta(): Promise<QueueWorkersMeta> {
276
+ if (this.queueMetaCache) return this.queueMetaCache
277
+
278
+ const content = await this.readMetaJson(
279
+ 'queue',
280
+ 'pikku-queue-workers-wirings-meta'
281
+ )
282
+ this.queueMetaCache = content ? JSON.parse(content) : {}
283
+ return this.queueMetaCache!
284
+ }
285
+
286
+ async getCliMeta(): Promise<CLIMeta> {
287
+ if (this.cliMetaCache) return this.cliMetaCache
288
+
289
+ const content = await this.readMetaJson('cli', 'pikku-cli-wirings-meta')
290
+ this.cliMetaCache = content
291
+ ? JSON.parse(content)
292
+ : { programs: {}, renderers: {} }
293
+ return this.cliMetaCache!
294
+ }
295
+
296
+ async getMcpMeta(): Promise<MCPMeta> {
297
+ if (this.mcpMetaCache) return this.mcpMetaCache
298
+
299
+ const content = await this.readMetaJson('mcp', 'pikku-mcp-wirings-meta')
300
+ if (content) {
301
+ const mcpData = JSON.parse(content)
302
+ this.mcpMetaCache = {
303
+ resources: mcpData.resourcesMeta || {},
304
+ tools: mcpData.toolsMeta || {},
305
+ prompts: mcpData.promptsMeta || {},
306
+ }
307
+ } else {
308
+ this.mcpMetaCache = { resources: {}, tools: {}, prompts: {} }
309
+ }
310
+ return this.mcpMetaCache!
311
+ }
312
+
313
+ async getRpcMeta(): Promise<RPCMetaRecord> {
314
+ if (this.rpcMetaCache) return this.rpcMetaCache
315
+
316
+ try {
317
+ const content = await this.readFile('rpc/pikku-rpc-wirings-meta.gen.json')
318
+ this.rpcMetaCache = content ? JSON.parse(content) : {}
319
+ return this.rpcMetaCache!
320
+ } catch (error) {
321
+ console.error('Error reading RPC wirings metadata:', error)
322
+ this.rpcMetaCache = {}
323
+ return this.rpcMetaCache
324
+ }
325
+ }
326
+
327
+ async getWorkflowMeta(): Promise<WorkflowsMeta> {
328
+ if (this.workflowMetaCache) return this.workflowMetaCache
329
+
330
+ try {
331
+ const files = await this.readDir('workflow/meta')
332
+ const jsonFiles = files.filter((f) => f.endsWith('.gen.json'))
333
+ const verboseFiles = jsonFiles.filter((f) => f.includes('-verbose'))
334
+ const minimalFiles = jsonFiles.filter((f) => !f.includes('-verbose'))
335
+ const verboseNames = new Set(
336
+ verboseFiles.map((f) => f.replace('-verbose.gen.json', ''))
337
+ )
338
+ const filesToRead = [
339
+ ...verboseFiles,
340
+ ...minimalFiles.filter(
341
+ (f) => !verboseNames.has(f.replace('.gen.json', ''))
342
+ ),
343
+ ]
344
+
345
+ const result: WorkflowsMeta = {}
346
+ await Promise.all(
347
+ filesToRead.map(async (file) => {
348
+ const content = await this.readFile(`workflow/meta/${file}`)
349
+ if (content) {
350
+ const meta = JSON.parse(content)
351
+ result[meta.name] = meta
352
+ }
353
+ })
354
+ )
355
+
356
+ this.workflowMetaCache = result
357
+ return this.workflowMetaCache
358
+ } catch (error) {
359
+ console.error('Error reading Workflow wirings metadata:', error)
360
+ this.workflowMetaCache = {}
361
+ return this.workflowMetaCache
362
+ }
363
+ }
364
+
365
+ async getTriggerMeta(): Promise<TriggerMeta> {
366
+ if (this.triggerMetaCache) return this.triggerMetaCache
367
+
368
+ const content = await this.readMetaJson(
369
+ 'trigger',
370
+ 'pikku-trigger-wirings-meta'
371
+ )
372
+ this.triggerMetaCache = content ? JSON.parse(content) : {}
373
+ return this.triggerMetaCache!
374
+ }
375
+
376
+ async getTriggerSourceMeta(): Promise<TriggerSourceMeta> {
377
+ if (this.triggerSourceMetaCache) return this.triggerSourceMetaCache
378
+
379
+ const content = await this.readMetaJson(
380
+ 'trigger',
381
+ 'pikku-trigger-sources-meta'
382
+ )
383
+ this.triggerSourceMetaCache = content ? JSON.parse(content) : {}
384
+ return this.triggerSourceMetaCache!
385
+ }
386
+
387
+ async getFunctionsMeta(): Promise<FunctionsMeta> {
388
+ if (this.functionsMetaCache) return this.functionsMetaCache
389
+
390
+ const content = await this.readMetaJson('function', 'pikku-functions-meta')
391
+ this.functionsMetaCache = content ? JSON.parse(content) : {}
392
+ return this.functionsMetaCache!
393
+ }
394
+
395
+ async getMiddlewareGroupsMeta(): Promise<MiddlewareGroupsMeta> {
396
+ if (this.middlewareGroupsMetaCache) return this.middlewareGroupsMetaCache
397
+
398
+ const content = await this.readMetaJson(
399
+ 'middleware',
400
+ 'pikku-middleware-groups-meta'
401
+ )
402
+ this.middlewareGroupsMetaCache = content
403
+ ? JSON.parse(content)
404
+ : { definitions: {}, instances: {}, httpGroups: {}, tagGroups: {} }
405
+ return this.middlewareGroupsMetaCache!
406
+ }
407
+
408
+ async getPermissionsGroupsMeta(): Promise<PermissionsGroupsMeta> {
409
+ if (this.permissionsGroupsMetaCache) return this.permissionsGroupsMetaCache
410
+
411
+ const content = await this.readMetaJson(
412
+ 'permissions',
413
+ 'pikku-permissions-groups-meta'
414
+ )
415
+ this.permissionsGroupsMetaCache = content
416
+ ? JSON.parse(content)
417
+ : { definitions: {}, httpGroups: {}, tagGroups: {} }
418
+ return this.permissionsGroupsMetaCache!
419
+ }
420
+
421
+ async getAgentsMeta(): Promise<AgentsMeta> {
422
+ if (this.agentsMetaCache) return this.agentsMetaCache
423
+
424
+ const content = await this.readMetaJson('agent', 'pikku-agent-wirings-meta')
425
+ if (content) {
426
+ const parsed = JSON.parse(content)
427
+ this.agentsMetaCache = parsed.agentsMeta || parsed
428
+ } else {
429
+ this.agentsMetaCache = {}
430
+ }
431
+ return this.agentsMetaCache!
432
+ }
433
+
434
+ async getSecretsMeta(): Promise<SecretDefinitionsMeta> {
435
+ if (this.secretsMetaCache) return this.secretsMetaCache
436
+
437
+ const content = await this.readFile('secrets/pikku-secrets-meta.gen.json')
438
+ this.secretsMetaCache = content ? JSON.parse(content) : {}
439
+ return this.secretsMetaCache!
440
+ }
441
+
442
+ async getCredentialsMeta(): Promise<CredentialDefinitionsMeta> {
443
+ if (this.credentialsMetaCache) return this.credentialsMetaCache
444
+
445
+ const content = await this.readFile(
446
+ 'credentials/pikku-credentials-meta.gen.json'
447
+ )
448
+ this.credentialsMetaCache = content ? JSON.parse(content) : {}
449
+ return this.credentialsMetaCache!
450
+ }
451
+
452
+ async getVariablesMeta(): Promise<VariableDefinitionsMeta> {
453
+ if (this.variablesMetaCache) return this.variablesMetaCache
454
+
455
+ const content = await this.readFile(
456
+ 'variables/pikku-variables-meta.gen.json'
457
+ )
458
+ this.variablesMetaCache = content ? JSON.parse(content) : {}
459
+ return this.variablesMetaCache!
460
+ }
461
+
462
+ async getServicesMeta(): Promise<ServicesMetaRecord> {
463
+ if (this.servicesMetaCache) return this.servicesMetaCache
464
+
465
+ try {
466
+ const files = await this.readDir('services')
467
+ const jsonFiles = files.filter((f) => f.endsWith('.gen.json'))
468
+
469
+ const result: ServicesMetaRecord = {}
470
+ await Promise.all(
471
+ jsonFiles.map(async (file) => {
472
+ const content = await this.readFile(`services/${file}`)
473
+ if (content) {
474
+ const meta: ServiceMeta = JSON.parse(content)
475
+ result[meta.name] = meta
476
+ }
477
+ })
478
+ )
479
+
480
+ this.servicesMetaCache = result
481
+ return this.servicesMetaCache
482
+ } catch (error) {
483
+ console.error('Error reading Services metadata:', error)
484
+ this.servicesMetaCache = {}
485
+ return this.servicesMetaCache
486
+ }
487
+ }
488
+
489
+ async getSchema(schemaName: string): Promise<JSONSchema7 | null> {
490
+ if (this.schemaCache.has(schemaName)) {
491
+ return this.schemaCache.get(schemaName)!
492
+ }
493
+
494
+ if (!/^[a-zA-Z0-9_\-\.]+$/.test(schemaName)) {
495
+ return null
496
+ }
497
+
498
+ try {
499
+ const content = await this.readFile(
500
+ `schemas/schemas/${schemaName}.schema.json`
501
+ )
502
+ if (!content) return null
503
+ const schema = JSON.parse(content) as JSONSchema7
504
+ this.schemaCache.set(schemaName, schema)
505
+ return schema
506
+ } catch (error) {
507
+ console.error(`Error reading schema ${schemaName}:`, error)
508
+ return null
509
+ }
510
+ }
511
+
512
+ async getSchemas(
513
+ schemaNames: string[]
514
+ ): Promise<Record<string, JSONSchema7 | null>> {
515
+ const results: Record<string, JSONSchema7 | null> = {}
516
+ await Promise.all(
517
+ schemaNames.map(async (name) => {
518
+ results[name] = await this.getSchema(name)
519
+ })
520
+ )
521
+ return results
522
+ }
523
+ }
@@ -0,0 +1,62 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { defaultPikkuUserIdResolver } from './pikku-user-id.js'
4
+ import type { PikkuWire } from '../types/core.types.js'
5
+
6
+ describe('defaultPikkuUserIdResolver', () => {
7
+ test('should return pikkuUserId from wire', () => {
8
+ const wire: PikkuWire = { pikkuUserId: 'user-1' }
9
+ assert.strictEqual(defaultPikkuUserIdResolver(wire), 'user-1')
10
+ })
11
+
12
+ test('should return userId from session', () => {
13
+ const wire: PikkuWire = { session: { userId: 'session-user' } as any }
14
+ assert.strictEqual(defaultPikkuUserIdResolver(wire), 'session-user')
15
+ })
16
+
17
+ test('should prefer pikkuUserId over session', () => {
18
+ const wire: PikkuWire = {
19
+ pikkuUserId: 'explicit',
20
+ session: { userId: 'session-user' } as any,
21
+ }
22
+ assert.strictEqual(defaultPikkuUserIdResolver(wire), 'explicit')
23
+ })
24
+
25
+ test('should return pikkuUserId from queue', () => {
26
+ const wire: PikkuWire = {
27
+ queue: {
28
+ queueName: 'test',
29
+ jobId: '1',
30
+ pikkuUserId: 'queue-user',
31
+ updateProgress: async () => {},
32
+ fail: async () => {},
33
+ discard: async () => {},
34
+ },
35
+ }
36
+ assert.strictEqual(defaultPikkuUserIdResolver(wire), 'queue-user')
37
+ })
38
+
39
+ test('should return pikkuUserId from workflow', () => {
40
+ const wire: PikkuWire = {
41
+ workflow: {
42
+ name: 'test',
43
+ runId: '1',
44
+ pikkuUserId: 'workflow-user',
45
+ getRun: async () => ({}) as any,
46
+ do: (() => {}) as any,
47
+ sleep: (() => {}) as any,
48
+ } as any,
49
+ }
50
+ assert.strictEqual(defaultPikkuUserIdResolver(wire), 'workflow-user')
51
+ })
52
+
53
+ test('should return undefined when no identity source', () => {
54
+ const wire: PikkuWire = {}
55
+ assert.strictEqual(defaultPikkuUserIdResolver(wire), undefined)
56
+ })
57
+
58
+ test('should ignore non-string session userId', () => {
59
+ const wire: PikkuWire = { session: { userId: 123 } as any }
60
+ assert.strictEqual(defaultPikkuUserIdResolver(wire), undefined)
61
+ })
62
+ })
@@ -0,0 +1,17 @@
1
+ import type { PikkuWire } from '../types/core.types.js'
2
+
3
+ export type PikkuUserIdResolver = (wire: PikkuWire) => string | undefined
4
+
5
+ export const defaultPikkuUserIdResolver: PikkuUserIdResolver = (wire) => {
6
+ // Explicit pikkuUserId on wire (set by earlier middleware or runner)
7
+ if (wire.pikkuUserId) return wire.pikkuUserId
8
+ // Session userId (from auth middleware — user-defined session shape)
9
+ const session = wire.session as Record<string, unknown> | undefined
10
+ if (session?.userId && typeof session.userId === 'string')
11
+ return session.userId
12
+ // Queue job: carried in job metadata
13
+ if (wire.queue?.pikkuUserId) return wire.queue.pikkuUserId
14
+ // Workflow: carried in run wire metadata
15
+ if (wire.workflow?.pikkuUserId) return wire.workflow.pikkuUserId
16
+ return undefined
17
+ }
@@ -52,6 +52,14 @@ export class TypedCredentialService<TMap = Record<string, unknown>>
52
52
  return this.credentials.getAll(userId)
53
53
  }
54
54
 
55
+ async getUsersWithCredential(name: string): Promise<string[]> {
56
+ return this.credentials.getUsersWithCredential(name)
57
+ }
58
+
59
+ async getAllUsers(): Promise<string[]> {
60
+ return this.credentials.getAllUsers()
61
+ }
62
+
55
63
  async getAllStatus(userId?: string): Promise<CredentialStatusInfo[]> {
56
64
  const results: CredentialStatusInfo[] = []
57
65
 
@@ -2,6 +2,7 @@ import type { SerializedError } from '../types/core.types.js'
2
2
  import type {
3
3
  WorkflowRun,
4
4
  WorkflowRunWire,
5
+ WorkflowRunStatus,
5
6
  StepState,
6
7
  WorkflowStatus,
7
8
  WorkflowVersionStatus,
@@ -21,6 +22,7 @@ export interface WorkflowService {
21
22
  wire: WorkflowRunWire
22
23
  ): Promise<string>
23
24
  getRun(id: string): Promise<WorkflowRun | null>
25
+ getRunStatus(id: string): Promise<WorkflowRunStatus | null>
24
26
  getRunHistory(runId: string): Promise<Array<StepState & { stepName: string }>>
25
27
  updateRunStatus(
26
28
  id: string,
@@ -40,6 +42,12 @@ export interface WorkflowService {
40
42
  rpcService: any,
41
43
  options?: { inline?: boolean; startNode?: string }
42
44
  ): Promise<{ runId: string }>
45
+ runToCompletion<I>(
46
+ name: string,
47
+ input: I,
48
+ rpcService: any,
49
+ options?: { pollIntervalMs?: number; wire?: WorkflowRunWire }
50
+ ): Promise<unknown>
43
51
  runWorkflowJob(runId: string, rpcService: any): Promise<void>
44
52
  orchestrateWorkflow(runId: string, rpcService: any): Promise<void>
45
53
  executeWorkflowSleepCompleted(runId: string, stepId: string): Promise<void>
@@ -735,16 +735,6 @@ export function defineServiceTests(config: ServiceTestConfig): void {
735
735
  endpoint: 'http://localhost:3000',
736
736
  functions: ['funcA', 'funcB'],
737
737
  })
738
-
739
- const infos = await service.findFunction('funcA')
740
- assert.ok(infos.length >= 1)
741
- assert.equal(infos[0]!.deploymentId, 'deploy-1')
742
- assert.equal(infos[0]!.endpoint, 'http://localhost:3000')
743
- })
744
-
745
- test('findFunction returns empty for unknown function', async () => {
746
- const infos = await service.findFunction('unknown-func')
747
- assert.deepEqual(infos, [])
748
738
  })
749
739
  })
750
740
  }