@pikku/core 0.12.3 → 0.12.4

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 (218) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/services/in-memory-workflow-service.d.ts +15 -2
  3. package/dist/services/in-memory-workflow-service.js +49 -0
  4. package/lcov.info +18891 -0
  5. package/package.json +1 -1
  6. package/src/crypto-utils.test.ts +0 -214
  7. package/src/crypto-utils.ts +0 -213
  8. package/src/errors/error-handler.ts +0 -62
  9. package/src/errors/error.test.ts +0 -195
  10. package/src/errors/errors.ts +0 -374
  11. package/src/errors/index.ts +0 -2
  12. package/src/factory-functions.test.ts +0 -109
  13. package/src/function/function-runner.test.ts +0 -536
  14. package/src/function/function-runner.ts +0 -365
  15. package/src/function/functions.types.ts +0 -304
  16. package/src/function/index.ts +0 -5
  17. package/src/handle-error.test.ts +0 -424
  18. package/src/handle-error.ts +0 -69
  19. package/src/index.ts +0 -118
  20. package/src/internal.ts +0 -6
  21. package/src/middleware/auth-apikey.test.ts +0 -363
  22. package/src/middleware/auth-apikey.ts +0 -47
  23. package/src/middleware/auth-bearer.test.ts +0 -450
  24. package/src/middleware/auth-bearer.ts +0 -72
  25. package/src/middleware/auth-cookie.test.ts +0 -528
  26. package/src/middleware/auth-cookie.ts +0 -80
  27. package/src/middleware/cors.test.ts +0 -424
  28. package/src/middleware/cors.ts +0 -104
  29. package/src/middleware/index.ts +0 -5
  30. package/src/middleware/remote-auth.test.ts +0 -488
  31. package/src/middleware/remote-auth.ts +0 -68
  32. package/src/middleware/timeout.ts +0 -15
  33. package/src/middleware-runner.test.ts +0 -418
  34. package/src/middleware-runner.ts +0 -234
  35. package/src/permissions.test.ts +0 -434
  36. package/src/permissions.ts +0 -319
  37. package/src/pikku-request.ts +0 -23
  38. package/src/pikku-response.ts +0 -5
  39. package/src/pikku-state.test.ts +0 -224
  40. package/src/pikku-state.ts +0 -216
  41. package/src/run-tests-script.test.ts +0 -49
  42. package/src/schema.test.ts +0 -249
  43. package/src/schema.ts +0 -151
  44. package/src/services/ai-agent-runner-service.ts +0 -41
  45. package/src/services/ai-run-state-service.ts +0 -20
  46. package/src/services/ai-storage-service.ts +0 -39
  47. package/src/services/content-service.ts +0 -76
  48. package/src/services/deployment-service.ts +0 -22
  49. package/src/services/gateway-service.ts +0 -20
  50. package/src/services/gopass-secrets.ts +0 -98
  51. package/src/services/in-memory-ai-run-state-service.ts +0 -73
  52. package/src/services/in-memory-trigger-service.ts +0 -64
  53. package/src/services/in-memory-workflow-service.test.ts +0 -351
  54. package/src/services/in-memory-workflow-service.ts +0 -438
  55. package/src/services/index.ts +0 -56
  56. package/src/services/jwt-service.ts +0 -30
  57. package/src/services/local-content.ts +0 -120
  58. package/src/services/local-gateway-service.ts +0 -62
  59. package/src/services/local-secrets.test.ts +0 -80
  60. package/src/services/local-secrets.ts +0 -94
  61. package/src/services/local-variables.test.ts +0 -61
  62. package/src/services/local-variables.ts +0 -39
  63. package/src/services/logger-console.test.ts +0 -118
  64. package/src/services/logger-console.ts +0 -98
  65. package/src/services/logger.ts +0 -57
  66. package/src/services/scheduler-service.ts +0 -86
  67. package/src/services/schema-service.ts +0 -33
  68. package/src/services/scoped-secret-service.test.ts +0 -74
  69. package/src/services/scoped-secret-service.ts +0 -41
  70. package/src/services/secret-service.ts +0 -38
  71. package/src/services/trigger-service.ts +0 -17
  72. package/src/services/typed-secret-service.test.ts +0 -93
  73. package/src/services/typed-secret-service.ts +0 -70
  74. package/src/services/typed-variables-service.test.ts +0 -73
  75. package/src/services/typed-variables-service.ts +0 -81
  76. package/src/services/user-session-service.test.ts +0 -113
  77. package/src/services/user-session-service.ts +0 -86
  78. package/src/services/variables-service.ts +0 -11
  79. package/src/services/workflow-service.ts +0 -95
  80. package/src/testing/index.ts +0 -2
  81. package/src/testing/service-tests.ts +0 -873
  82. package/src/time-utils.test.ts +0 -56
  83. package/src/time-utils.ts +0 -107
  84. package/src/types/core.types.ts +0 -478
  85. package/src/types/state.types.ts +0 -188
  86. package/src/utils/hash.test.ts +0 -68
  87. package/src/utils/hash.ts +0 -26
  88. package/src/utils.test.ts +0 -350
  89. package/src/utils.ts +0 -129
  90. package/src/version.test.ts +0 -80
  91. package/src/version.ts +0 -25
  92. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -469
  93. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +0 -152
  94. package/src/wirings/ai-agent/ai-agent-helpers.ts +0 -76
  95. package/src/wirings/ai-agent/ai-agent-memory.ts +0 -310
  96. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +0 -115
  97. package/src/wirings/ai-agent/ai-agent-model-config.ts +0 -43
  98. package/src/wirings/ai-agent/ai-agent-prepare.ts +0 -636
  99. package/src/wirings/ai-agent/ai-agent-registry.test.ts +0 -37
  100. package/src/wirings/ai-agent/ai-agent-registry.ts +0 -82
  101. package/src/wirings/ai-agent/ai-agent-runner.test.ts +0 -319
  102. package/src/wirings/ai-agent/ai-agent-runner.ts +0 -773
  103. package/src/wirings/ai-agent/ai-agent-stream.test.ts +0 -859
  104. package/src/wirings/ai-agent/ai-agent-stream.ts +0 -1153
  105. package/src/wirings/ai-agent/ai-agent.types.ts +0 -382
  106. package/src/wirings/ai-agent/index.ts +0 -37
  107. package/src/wirings/channel/channel-common.ts +0 -90
  108. package/src/wirings/channel/channel-handler.ts +0 -250
  109. package/src/wirings/channel/channel-middleware-runner.ts +0 -120
  110. package/src/wirings/channel/channel-runner.ts +0 -166
  111. package/src/wirings/channel/channel-store.ts +0 -29
  112. package/src/wirings/channel/channel.types.ts +0 -172
  113. package/src/wirings/channel/define-channel-routes.ts +0 -25
  114. package/src/wirings/channel/eventhub-service.ts +0 -34
  115. package/src/wirings/channel/eventhub-store.ts +0 -13
  116. package/src/wirings/channel/index.ts +0 -24
  117. package/src/wirings/channel/local/index.ts +0 -3
  118. package/src/wirings/channel/local/local-channel-handler.ts +0 -81
  119. package/src/wirings/channel/local/local-channel-runner.test.ts +0 -215
  120. package/src/wirings/channel/local/local-channel-runner.ts +0 -210
  121. package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -95
  122. package/src/wirings/channel/local/local-eventhub-service.ts +0 -101
  123. package/src/wirings/channel/log-channels.ts +0 -20
  124. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +0 -54
  125. package/src/wirings/channel/pikku-abstract-channel-handler.ts +0 -45
  126. package/src/wirings/channel/serverless/index.ts +0 -5
  127. package/src/wirings/channel/serverless/serverless-channel-runner.ts +0 -289
  128. package/src/wirings/cli/channel/cli-channel-runner.ts +0 -136
  129. package/src/wirings/cli/channel/index.ts +0 -1
  130. package/src/wirings/cli/cli-runner.test.ts +0 -403
  131. package/src/wirings/cli/cli-runner.ts +0 -529
  132. package/src/wirings/cli/cli.types.ts +0 -358
  133. package/src/wirings/cli/command-parser.test.ts +0 -445
  134. package/src/wirings/cli/command-parser.ts +0 -504
  135. package/src/wirings/cli/define-cli-commands.ts +0 -24
  136. package/src/wirings/cli/index.ts +0 -19
  137. package/src/wirings/gateway/gateway-runner.test.ts +0 -474
  138. package/src/wirings/gateway/gateway-runner.ts +0 -411
  139. package/src/wirings/gateway/gateway.types.ts +0 -149
  140. package/src/wirings/gateway/index.ts +0 -13
  141. package/src/wirings/http/http-routes.test.ts +0 -322
  142. package/src/wirings/http/http-routes.ts +0 -197
  143. package/src/wirings/http/http-runner.test.ts +0 -144
  144. package/src/wirings/http/http-runner.ts +0 -588
  145. package/src/wirings/http/http.types.ts +0 -369
  146. package/src/wirings/http/index.ts +0 -28
  147. package/src/wirings/http/log-http-routes.ts +0 -22
  148. package/src/wirings/http/pikku-fetch-http-request.test.ts +0 -237
  149. package/src/wirings/http/pikku-fetch-http-request.ts +0 -170
  150. package/src/wirings/http/pikku-fetch-http-response.test.ts +0 -82
  151. package/src/wirings/http/pikku-fetch-http-response.ts +0 -147
  152. package/src/wirings/http/routers/http-router.ts +0 -13
  153. package/src/wirings/http/routers/path-to-regex.test.ts +0 -319
  154. package/src/wirings/http/routers/path-to-regex.ts +0 -126
  155. package/src/wirings/http/web-request.test.ts +0 -236
  156. package/src/wirings/http/web-request.ts +0 -104
  157. package/src/wirings/mcp/index.ts +0 -27
  158. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +0 -389
  159. package/src/wirings/mcp/mcp-endpoint-registry.ts +0 -159
  160. package/src/wirings/mcp/mcp-runner.ts +0 -323
  161. package/src/wirings/mcp/mcp.types.ts +0 -216
  162. package/src/wirings/node/index.ts +0 -2
  163. package/src/wirings/node/node.types.ts +0 -23
  164. package/src/wirings/oauth2/index.ts +0 -3
  165. package/src/wirings/oauth2/oauth2-client.test.ts +0 -929
  166. package/src/wirings/oauth2/oauth2-client.ts +0 -335
  167. package/src/wirings/oauth2/oauth2.types.ts +0 -69
  168. package/src/wirings/oauth2/wire-oauth2-credential.ts +0 -23
  169. package/src/wirings/queue/index.ts +0 -31
  170. package/src/wirings/queue/queue-runner.test.ts +0 -710
  171. package/src/wirings/queue/queue-runner.ts +0 -192
  172. package/src/wirings/queue/queue.types.ts +0 -189
  173. package/src/wirings/queue/register-queue-helper.ts +0 -60
  174. package/src/wirings/queue/validate-worker-config.test.ts +0 -108
  175. package/src/wirings/queue/validate-worker-config.ts +0 -116
  176. package/src/wirings/rpc/index.ts +0 -4
  177. package/src/wirings/rpc/rpc-runner.ts +0 -460
  178. package/src/wirings/rpc/rpc-types.ts +0 -56
  179. package/src/wirings/rpc/wire-addon.ts +0 -21
  180. package/src/wirings/scheduler/index.ts +0 -11
  181. package/src/wirings/scheduler/log-schedulers.ts +0 -20
  182. package/src/wirings/scheduler/scheduler-runner.test.ts +0 -660
  183. package/src/wirings/scheduler/scheduler-runner.ts +0 -133
  184. package/src/wirings/scheduler/scheduler.types.ts +0 -53
  185. package/src/wirings/secret/index.ts +0 -9
  186. package/src/wirings/secret/secret.types.ts +0 -32
  187. package/src/wirings/secret/validate-secret-definitions.test.ts +0 -140
  188. package/src/wirings/secret/validate-secret-definitions.ts +0 -82
  189. package/src/wirings/trigger/index.ts +0 -10
  190. package/src/wirings/trigger/pikku-trigger-service.ts +0 -112
  191. package/src/wirings/trigger/trigger-runner.test.ts +0 -79
  192. package/src/wirings/trigger/trigger-runner.ts +0 -135
  193. package/src/wirings/trigger/trigger.types.ts +0 -178
  194. package/src/wirings/variable/index.ts +0 -8
  195. package/src/wirings/variable/validate-variable-definitions.test.ts +0 -91
  196. package/src/wirings/variable/validate-variable-definitions.ts +0 -69
  197. package/src/wirings/variable/variable.types.ts +0 -22
  198. package/src/wirings/workflow/dsl/index.ts +0 -31
  199. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +0 -320
  200. package/src/wirings/workflow/dsl/workflow-runner.ts +0 -28
  201. package/src/wirings/workflow/graph/graph-node.ts +0 -222
  202. package/src/wirings/workflow/graph/graph-runner.test.ts +0 -487
  203. package/src/wirings/workflow/graph/graph-runner.ts +0 -816
  204. package/src/wirings/workflow/graph/graph-validation.test.ts +0 -170
  205. package/src/wirings/workflow/graph/graph-validation.ts +0 -237
  206. package/src/wirings/workflow/graph/index.ts +0 -19
  207. package/src/wirings/workflow/graph/template.test.ts +0 -49
  208. package/src/wirings/workflow/graph/template.ts +0 -42
  209. package/src/wirings/workflow/graph/wire-workflow-graph.ts +0 -29
  210. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -104
  211. package/src/wirings/workflow/index.ts +0 -82
  212. package/src/wirings/workflow/pikku-workflow-service.test.ts +0 -200
  213. package/src/wirings/workflow/pikku-workflow-service.ts +0 -1176
  214. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  215. package/src/wirings/workflow/workflow-helpers.ts +0 -79
  216. package/src/wirings/workflow/workflow.types.ts +0 -274
  217. package/tsconfig.json +0 -14
  218. package/tsconfig.tsbuildinfo +0 -1
@@ -1,170 +0,0 @@
1
- import { describe, it } from 'node:test'
2
- import assert from 'node:assert/strict'
3
-
4
- import {
5
- computeEntryNodeIds,
6
- validateWorkflowWiring,
7
- generateMermaidDiagram,
8
- } from './graph-validation.js'
9
-
10
- describe('computeEntryNodeIds', () => {
11
- it('returns nodes not referenced by any other node', () => {
12
- const nodes = {
13
- start: { rpcName: 'a', next: 'middle' },
14
- middle: { rpcName: 'b', next: 'end' },
15
- end: { rpcName: 'c' },
16
- }
17
- assert.deepEqual(computeEntryNodeIds(nodes), ['start'])
18
- })
19
-
20
- it('returns multiple entry nodes for parallel starts', () => {
21
- const nodes = {
22
- a: { rpcName: 'x' },
23
- b: { rpcName: 'y' },
24
- c: { rpcName: 'z', next: 'a' },
25
- }
26
- const entries = computeEntryNodeIds(nodes)
27
- assert.ok(entries.includes('b'))
28
- assert.ok(entries.includes('c'))
29
- assert.ok(!entries.includes('a'))
30
- })
31
-
32
- it('returns empty for fully circular graph', () => {
33
- const nodes = {
34
- a: { rpcName: 'x', next: 'b' },
35
- b: { rpcName: 'y', next: 'a' },
36
- }
37
- assert.deepEqual(computeEntryNodeIds(nodes), [])
38
- })
39
-
40
- it('handles onError references', () => {
41
- const nodes = {
42
- start: { rpcName: 'a', next: 'ok', onError: 'err' },
43
- ok: { rpcName: 'b' },
44
- err: { rpcName: 'c' },
45
- }
46
- assert.deepEqual(computeEntryNodeIds(nodes), ['start'])
47
- })
48
-
49
- it('handles branching next as object', () => {
50
- const nodes = {
51
- start: { rpcName: 'a', next: { yes: 'b', no: 'c' } },
52
- b: { rpcName: 'x' },
53
- c: { rpcName: 'y' },
54
- }
55
- assert.deepEqual(computeEntryNodeIds(nodes), ['start'])
56
- })
57
-
58
- it('handles parallel next as array', () => {
59
- const nodes = {
60
- start: { rpcName: 'a', next: ['b', 'c'] },
61
- b: { rpcName: 'x' },
62
- c: { rpcName: 'y' },
63
- }
64
- assert.deepEqual(computeEntryNodeIds(nodes), ['start'])
65
- })
66
- })
67
-
68
- describe('validateWorkflowWiring', () => {
69
- const tools = ['toolA', 'toolB', 'toolC']
70
-
71
- it('returns no errors for a valid workflow', () => {
72
- const nodes = {
73
- step1: { rpcName: 'toolA', input: {}, next: 'step2' },
74
- step2: { rpcName: 'toolB', input: {} },
75
- }
76
- const errors = validateWorkflowWiring(nodes, tools)
77
- assert.deepEqual(errors, [])
78
- })
79
-
80
- it('errors on missing rpcName', () => {
81
- const nodes = {
82
- step1: { input: {} },
83
- }
84
- const errors = validateWorkflowWiring(nodes, tools)
85
- assert.ok(errors.some((e) => e.includes("missing 'rpcName'")))
86
- })
87
-
88
- it('errors on unknown tool', () => {
89
- const nodes = {
90
- step1: { rpcName: 'unknownTool', input: {} },
91
- }
92
- const errors = validateWorkflowWiring(nodes, tools)
93
- assert.ok(errors.some((e) => e.includes('unknown tool')))
94
- })
95
-
96
- it('errors on next referencing unknown node', () => {
97
- const nodes = {
98
- step1: { rpcName: 'toolA', next: 'nonexistent' },
99
- }
100
- const errors = validateWorkflowWiring(nodes, tools)
101
- assert.ok(errors.some((e) => e.includes("unknown node 'nonexistent'")))
102
- })
103
-
104
- it('errors on onError referencing unknown node', () => {
105
- const nodes = {
106
- step1: { rpcName: 'toolA', onError: 'missing' },
107
- }
108
- const errors = validateWorkflowWiring(nodes, tools)
109
- assert.ok(errors.some((e) => e.includes("unknown node 'missing'")))
110
- })
111
-
112
- it('errors on input referencing unknown node', () => {
113
- const nodes = {
114
- step1: {
115
- rpcName: 'toolA',
116
- input: { field: { $ref: 'missing', path: 'x' } },
117
- },
118
- }
119
- const errors = validateWorkflowWiring(nodes, tools)
120
- assert.ok(errors.some((e) => e.includes("unknown node 'missing'")))
121
- })
122
-
123
- it('allows trigger refs without errors', () => {
124
- const nodes = {
125
- step1: {
126
- rpcName: 'toolA',
127
- input: { field: { $ref: 'trigger', path: 'title' } },
128
- next: 'step2',
129
- },
130
- step2: { rpcName: 'toolB', input: {} },
131
- }
132
- const errors = validateWorkflowWiring(nodes, tools)
133
- assert.deepEqual(errors, [])
134
- })
135
-
136
- it('allows valid node-to-node refs', () => {
137
- const nodes = {
138
- step1: { rpcName: 'toolA', input: {}, next: 'step2' },
139
- step2: {
140
- rpcName: 'toolB',
141
- input: { data: { $ref: 'step1', path: 'result' } },
142
- },
143
- }
144
- const errors = validateWorkflowWiring(nodes, tools)
145
- assert.deepEqual(errors, [])
146
- })
147
- })
148
-
149
- describe('generateMermaidDiagram', () => {
150
- it('generates valid mermaid output', () => {
151
- const nodes = {
152
- start: { rpcName: 'toolA', next: 'end' },
153
- end: { rpcName: 'toolB' },
154
- }
155
- const result = generateMermaidDiagram('test-wf', nodes, ['start'])
156
- assert.ok(result.includes('graph TD'))
157
- assert.ok(result.includes('start([toolA])'))
158
- assert.ok(result.includes('end[toolB]'))
159
- assert.ok(result.includes('start --> end'))
160
- })
161
-
162
- it('shows error edges with dotted lines', () => {
163
- const nodes = {
164
- start: { rpcName: 'toolA', onError: 'handler' },
165
- handler: { rpcName: 'toolB' },
166
- }
167
- const result = generateMermaidDiagram('test-wf', nodes, ['start'])
168
- assert.ok(result.includes('start -.->|error| handler'))
169
- })
170
- })
@@ -1,237 +0,0 @@
1
- import { pikkuState } from '../../../pikku-state.js'
2
- import { resolveNamespace } from '../../rpc/rpc-runner.js'
3
-
4
- function normalizeTargets(value: unknown): string[] {
5
- if (!value) return []
6
- if (typeof value === 'string') return [value]
7
- if (Array.isArray(value)) return value.filter((v) => typeof v === 'string')
8
- if (typeof value === 'object') {
9
- const targets: string[] = []
10
- for (const v of Object.values(value as Record<string, unknown>)) {
11
- targets.push(...normalizeTargets(v))
12
- }
13
- return targets
14
- }
15
- return []
16
- }
17
-
18
- function collectRefs(value: unknown, refs: Set<string>): void {
19
- if (
20
- typeof value === 'object' &&
21
- value !== null &&
22
- '$ref' in value &&
23
- typeof (value as any).$ref === 'string'
24
- ) {
25
- refs.add((value as any).$ref)
26
- return
27
- }
28
- if (Array.isArray(value)) {
29
- for (const item of value) collectRefs(item, refs)
30
- return
31
- }
32
- if (typeof value === 'object' && value !== null) {
33
- for (const v of Object.values(value)) collectRefs(v, refs)
34
- }
35
- }
36
-
37
- function resolveToolMeta(toolName: string): {
38
- fnMeta: any
39
- schemas: Map<string, any>
40
- } | null {
41
- const resolved = toolName.includes(':') ? resolveNamespace(toolName) : null
42
-
43
- if (resolved) {
44
- const fnMeta = pikkuState(resolved.package, 'function', 'meta')[
45
- resolved.function
46
- ]
47
- const schemas = pikkuState(resolved.package, 'misc', 'schemas')
48
- return fnMeta ? { fnMeta, schemas } : null
49
- }
50
-
51
- const rpcMeta = pikkuState(null, 'rpc', 'meta')
52
- const pikkuFuncId = rpcMeta[toolName]
53
- if (!pikkuFuncId) return null
54
-
55
- const fnMeta = pikkuState(null, 'function', 'meta')[pikkuFuncId]
56
- const schemas = pikkuState(null, 'misc', 'schemas')
57
- return fnMeta ? { fnMeta, schemas } : null
58
- }
59
-
60
- export function computeEntryNodeIds(nodes: Record<string, any>): string[] {
61
- const referenced = new Set<string>()
62
- for (const node of Object.values(nodes)) {
63
- if (node.next) {
64
- for (const target of normalizeTargets(node.next)) {
65
- referenced.add(target)
66
- }
67
- }
68
- if (node.onError) {
69
- for (const target of normalizeTargets(node.onError)) {
70
- referenced.add(target)
71
- }
72
- }
73
- }
74
- return Object.keys(nodes).filter((id) => !referenced.has(id))
75
- }
76
-
77
- export function validateWorkflowWiring(
78
- nodes: Record<string, any>,
79
- toolNames: string[]
80
- ): string[] {
81
- const errors: string[] = []
82
- const nodeIds = new Set(Object.keys(nodes))
83
- const toolSet = new Set(toolNames)
84
-
85
- for (const [nodeId, node] of Object.entries(nodes)) {
86
- if (!node.rpcName) {
87
- errors.push(`Node '${nodeId}' is missing 'rpcName'`)
88
- continue
89
- }
90
-
91
- if (!toolSet.has(node.rpcName)) {
92
- errors.push(
93
- `Node '${nodeId}' references unknown tool '${node.rpcName}'. Available tools: ${toolNames.join(', ')}`
94
- )
95
- continue
96
- }
97
-
98
- const nextTargets = normalizeTargets(node.next)
99
- for (const target of nextTargets) {
100
- if (!nodeIds.has(target)) {
101
- errors.push(
102
- `Node '${nodeId}' routes to unknown node '${target}' in 'next'`
103
- )
104
- }
105
- }
106
-
107
- const errorTargets = normalizeTargets(node.onError)
108
- for (const target of errorTargets) {
109
- if (!nodeIds.has(target)) {
110
- errors.push(
111
- `Node '${nodeId}' routes to unknown node '${target}' in 'onError'`
112
- )
113
- }
114
- }
115
-
116
- if (!node.input) continue
117
-
118
- const refs = new Set<string>()
119
- collectRefs(node.input, refs)
120
-
121
- for (const ref of refs) {
122
- if (ref === 'trigger' || ref === '$item') {
123
- const targetMeta = resolveToolMeta(node.rpcName)
124
- if (targetMeta?.fnMeta?.inputSchemaName) {
125
- const targetSchema = targetMeta.schemas.get(
126
- targetMeta.fnMeta.inputSchemaName
127
- )
128
- if (targetSchema?.properties) {
129
- for (const [field, fieldValue] of Object.entries(
130
- node.input as Record<string, any>
131
- )) {
132
- if (
133
- typeof fieldValue === 'object' &&
134
- fieldValue !== null &&
135
- fieldValue.$ref === ref &&
136
- !fieldValue.path
137
- ) {
138
- const targetType = targetSchema.properties[field]?.type
139
- if (targetType && targetType !== 'object') {
140
- errors.push(
141
- `Node '${nodeId}' input field '${field}' expects type '${targetType}', but references the whole ${ref} object without a path. Use { $ref: "${ref}", path: "${field}" } to extract the field.`
142
- )
143
- }
144
- }
145
- }
146
- }
147
- }
148
- continue
149
- }
150
-
151
- if (!nodeIds.has(ref)) {
152
- errors.push(
153
- `Node '${nodeId}' references unknown node '${ref}' in input`
154
- )
155
- continue
156
- }
157
-
158
- const sourceNode = nodes[ref]
159
- if (!sourceNode?.rpcName) continue
160
-
161
- const sourceMeta = resolveToolMeta(sourceNode.rpcName)
162
- if (!sourceMeta?.fnMeta?.outputSchemaName) continue
163
-
164
- const sourceSchema = sourceMeta.schemas.get(
165
- sourceMeta.fnMeta.outputSchemaName
166
- )
167
- if (!sourceSchema?.properties) continue
168
-
169
- for (const [field, fieldValue] of Object.entries(
170
- node.input as Record<string, any>
171
- )) {
172
- if (
173
- typeof fieldValue === 'object' &&
174
- fieldValue !== null &&
175
- fieldValue.$ref === ref &&
176
- fieldValue.path
177
- ) {
178
- const pathRoot = fieldValue.path.split('.')[0]
179
- if (
180
- sourceSchema.properties &&
181
- !(pathRoot in sourceSchema.properties)
182
- ) {
183
- errors.push(
184
- `Node '${nodeId}' input field '${field}' references path '${fieldValue.path}' but node '${ref}' (${sourceNode.rpcName}) output has no property '${pathRoot}'`
185
- )
186
- continue
187
- }
188
-
189
- const targetMeta = resolveToolMeta(node.rpcName)
190
- if (!targetMeta?.fnMeta?.inputSchemaName) continue
191
- const targetSchema = targetMeta.schemas.get(
192
- targetMeta.fnMeta.inputSchemaName
193
- )
194
- if (!targetSchema?.properties?.[field]) continue
195
-
196
- const sourceType = sourceSchema.properties[pathRoot]?.type
197
- const targetType = targetSchema.properties[field].type
198
- if (sourceType && targetType && sourceType !== targetType) {
199
- errors.push(
200
- `Node '${nodeId}' input field '${field}' expects type '${targetType}', but node '${ref}' output field '${pathRoot}' is type '${sourceType}'`
201
- )
202
- }
203
- }
204
- }
205
- }
206
- }
207
-
208
- return errors
209
- }
210
-
211
- export function generateMermaidDiagram(
212
- workflowName: string,
213
- nodes: Record<string, any>,
214
- entryNodeIds: string[]
215
- ): string {
216
- const lines: string[] = ['graph TD']
217
-
218
- for (const [nodeId, node] of Object.entries(nodes)) {
219
- const label = node.rpcName || nodeId
220
- const isEntry = entryNodeIds.includes(nodeId)
221
- lines.push(
222
- ` ${nodeId}${isEntry ? '([' : '['}${label}${isEntry ? '])' : ']'}`
223
- )
224
-
225
- const nextTargets = normalizeTargets(node.next)
226
- for (const target of nextTargets) {
227
- lines.push(` ${nodeId} --> ${target}`)
228
- }
229
-
230
- const errorTargets = normalizeTargets(node.onError)
231
- for (const target of errorTargets) {
232
- lines.push(` ${nodeId} -.->|error| ${target}`)
233
- }
234
- }
235
-
236
- return lines.join('\n')
237
- }
@@ -1,19 +0,0 @@
1
- export {
2
- continueGraph,
3
- executeGraphStep,
4
- onGraphNodeComplete,
5
- runFromMeta,
6
- runWorkflowGraph,
7
- } from './graph-runner.js'
8
- export { template } from './template.js'
9
- export type { TemplateString } from './template.js'
10
- export {
11
- pikkuWorkflowGraph,
12
- type PikkuWorkflowGraphConfig,
13
- type PikkuWorkflowGraphResult,
14
- } from './wire-workflow-graph.js'
15
- export {
16
- validateWorkflowWiring,
17
- computeEntryNodeIds,
18
- generateMermaidDiagram,
19
- } from './graph-validation.js'
@@ -1,49 +0,0 @@
1
- import { describe, test } from 'node:test'
2
- import assert from 'node:assert'
3
- import { template } from './template.js'
4
-
5
- describe('template', () => {
6
- test('should parse a simple template with one placeholder', () => {
7
- const result = template('Hello $0!', [{ $ref: 'greeting' }])
8
- assert.deepStrictEqual(result.$template.parts, ['Hello ', '!'])
9
- assert.deepStrictEqual(result.$template.expressions, [{ $ref: 'greeting' }])
10
- })
11
-
12
- test('should parse template with multiple placeholders', () => {
13
- const result = template('$0 and $1', [{ $ref: 'a' }, { $ref: 'b' }])
14
- assert.deepStrictEqual(result.$template.parts, ['', ' and ', ''])
15
- assert.strictEqual(result.$template.expressions.length, 2)
16
- assert.strictEqual(result.$template.expressions[0].$ref, 'a')
17
- assert.strictEqual(result.$template.expressions[1].$ref, 'b')
18
- })
19
-
20
- test('should preserve path in references', () => {
21
- const result = template('Value: $0', [{ $ref: 'node1', path: 'data.name' }])
22
- assert.deepStrictEqual(result.$template.expressions[0], {
23
- $ref: 'node1',
24
- path: 'data.name',
25
- })
26
- })
27
-
28
- test('should handle template with no placeholders', () => {
29
- const result = template('No placeholders here', [])
30
- assert.deepStrictEqual(result.$template.parts, ['No placeholders here'])
31
- assert.deepStrictEqual(result.$template.expressions, [])
32
- })
33
-
34
- test('should handle out-of-range ref index as unknown', () => {
35
- const result = template('$5', [{ $ref: 'a' }])
36
- assert.deepStrictEqual(result.$template.expressions[0], { $ref: 'unknown' })
37
- })
38
-
39
- test('should handle adjacent placeholders', () => {
40
- const result = template('$0$1', [{ $ref: 'a' }, { $ref: 'b' }])
41
- assert.deepStrictEqual(result.$template.parts, ['', '', ''])
42
- assert.strictEqual(result.$template.expressions.length, 2)
43
- })
44
-
45
- test('should omit path when not provided', () => {
46
- const result = template('$0', [{ $ref: 'node' }])
47
- assert.strictEqual(result.$template.expressions[0].path, undefined)
48
- })
49
- })
@@ -1,42 +0,0 @@
1
- export type TemplateString = {
2
- $template: {
3
- parts: string[]
4
- expressions: Array<{ $ref: string; path?: string }>
5
- }
6
- } & { __brand: 'TemplateString' }
7
-
8
- export function template(
9
- templateStr: string,
10
- refs: Array<{ $ref: string; path?: string }>
11
- ): TemplateString {
12
- const parts: string[] = []
13
- const expressions: Array<{ $ref: string; path?: string }> = []
14
-
15
- const regex = /\$(\d+)/g
16
- let lastIndex = 0
17
- let match
18
-
19
- while ((match = regex.exec(templateStr)) !== null) {
20
- parts.push(templateStr.slice(lastIndex, match.index))
21
-
22
- const refIndex = parseInt(match[1]!, 10)
23
- const refValue = refs[refIndex]
24
- if (refValue) {
25
- const expr: { $ref: string; path?: string } = { $ref: refValue.$ref }
26
- if (refValue.path) {
27
- expr.path = refValue.path
28
- }
29
- expressions.push(expr)
30
- } else {
31
- expressions.push({ $ref: 'unknown' })
32
- }
33
-
34
- lastIndex = regex.lastIndex
35
- }
36
-
37
- parts.push(templateStr.slice(lastIndex))
38
-
39
- return {
40
- $template: { parts, expressions },
41
- } as TemplateString
42
- }
@@ -1,29 +0,0 @@
1
- export interface PikkuWorkflowGraphConfig<
2
- FuncMap extends Record<string, string>,
3
- T,
4
- > {
5
- disabled?: true
6
- name?: string
7
- description?: string
8
- tags?: string[]
9
- nodes: FuncMap
10
- config?: T
11
- }
12
-
13
- export interface PikkuWorkflowGraphResult {
14
- __type: 'pikkuWorkflowGraph'
15
- name?: string
16
- description?: string
17
- tags?: string[]
18
- }
19
-
20
- export function pikkuWorkflowGraph<
21
- const FuncMap extends Record<string, string>,
22
- >(config: PikkuWorkflowGraphConfig<FuncMap, any>): PikkuWorkflowGraphResult {
23
- return {
24
- __type: 'pikkuWorkflowGraph',
25
- name: config.name,
26
- description: config.description,
27
- tags: config.tags,
28
- }
29
- }
@@ -1,104 +0,0 @@
1
- /**
2
- * Ref value - internal representation after input callback is evaluated
3
- */
4
- export interface RefValue {
5
- __isRef: true
6
- nodeId: string
7
- path?: string
8
- }
9
-
10
- /**
11
- * Helper to create a ref value
12
- */
13
- export const createRef = (nodeId: string, path?: string): RefValue => ({
14
- __isRef: true,
15
- nodeId,
16
- path,
17
- })
18
-
19
- /**
20
- * Check if a value is a ref
21
- */
22
- export const isRef = (value: unknown): value is RefValue =>
23
- typeof value === 'object' &&
24
- value !== null &&
25
- '__isRef' in value &&
26
- (value as RefValue).__isRef === true
27
-
28
- /**
29
- * Input reference for fetching step results
30
- */
31
- export interface InputRef {
32
- nodeId: string
33
- path?: string
34
- }
35
-
36
- /**
37
- * Ref function type - provides type-safe references to other nodes' outputs
38
- * The actual typing is done at the pikkuWorkflowGraph level
39
- */
40
- export type RefFn<NodeIds extends string = string> = (
41
- nodeId: NodeIds,
42
- path?: string
43
- ) => RefValue
44
-
45
- /**
46
- * Next node configuration - fully serializable
47
- * - string: single next node
48
- * - string[]: parallel execution (all run concurrently)
49
- * - Record<string, string | string[]>: branching (function calls graph.branch('key'))
50
- */
51
- export type NextConfig<NodeIds extends string = string> =
52
- | NodeIds
53
- | NodeIds[]
54
- | Record<string, NodeIds | NodeIds[]>
55
-
56
- /**
57
- * Graph node configuration - references functions by RPC name
58
- */
59
- export interface GraphNodeConfig<NodeIds extends string = string> {
60
- /** RPC function name */
61
- func: string
62
- /** Input mapping callback - receives ref function, returns input object */
63
- input?: (ref: RefFn<NodeIds>) => Record<string, unknown>
64
- /** Next nodes - string, array, or record for branching */
65
- next?: NextConfig<NodeIds>
66
- /** Error routing - node(s) to execute on error */
67
- onError?: NodeIds | NodeIds[]
68
- }
69
-
70
- /**
71
- * Graph wire context - available to functions running in a workflow graph
72
- */
73
- export interface PikkuGraphWire {
74
- /** Workflow run ID */
75
- runId: string
76
- /** Graph name */
77
- graphName: string
78
- /** Current node ID */
79
- nodeId: string
80
- /**
81
- * Select which branch to take for Record-based next config.
82
- * Must be called if the node has a Record `next` configuration.
83
- * @param key - The branch key to take (must match a key in the next Record)
84
- */
85
- branch: (key: string) => void
86
- /**
87
- * Set a state variable value.
88
- * @param name - Variable name
89
- * @param value - Value to store
90
- */
91
- setState: (name: string, value: unknown) => Promise<void>
92
- /**
93
- * Get the entire state object.
94
- * @returns The state object with all variables
95
- */
96
- getState: () => Promise<Record<string, unknown>>
97
- }
98
-
99
- /**
100
- * Internal mutable state for graph wire (used by runner to capture branch)
101
- */
102
- export interface GraphWireState {
103
- branchKey?: string
104
- }