@pikku/core 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/crypto-utils.d.ts +2 -0
  3. package/dist/crypto-utils.js +78 -0
  4. package/dist/errors/error-handler.js +3 -3
  5. package/dist/errors/errors.d.ts +8 -0
  6. package/dist/errors/errors.js +16 -0
  7. package/dist/errors/index.d.ts +0 -1
  8. package/dist/errors/index.js +0 -1
  9. package/dist/function/function-runner.d.ts +10 -3
  10. package/dist/function/function-runner.js +136 -31
  11. package/dist/function/functions.types.d.ts +35 -12
  12. package/dist/function/functions.types.js +16 -6
  13. package/dist/function/index.d.ts +2 -2
  14. package/dist/function/index.js +1 -2
  15. package/dist/handle-error.js +1 -10
  16. package/dist/index.d.ts +30 -24
  17. package/dist/index.js +19 -27
  18. package/dist/middleware/auth-apikey.d.ts +1 -1
  19. package/dist/middleware/auth-apikey.js +3 -3
  20. package/dist/middleware/auth-bearer.d.ts +1 -1
  21. package/dist/middleware/auth-bearer.js +3 -6
  22. package/dist/middleware/auth-cookie.d.ts +1 -1
  23. package/dist/middleware/auth-cookie.js +6 -8
  24. package/dist/middleware/cors.d.ts +46 -0
  25. package/dist/middleware/cors.js +73 -0
  26. package/dist/middleware/index.d.ts +5 -3
  27. package/dist/middleware/index.js +5 -3
  28. package/dist/middleware/remote-auth.d.ts +5 -0
  29. package/dist/middleware/remote-auth.js +55 -0
  30. package/dist/middleware/timeout.d.ts +1 -1
  31. package/dist/middleware-runner.d.ts +3 -14
  32. package/dist/middleware-runner.js +14 -16
  33. package/dist/permissions.d.ts +3 -86
  34. package/dist/permissions.js +20 -80
  35. package/dist/pikku-state.d.ts +33 -104
  36. package/dist/pikku-state.js +150 -66
  37. package/dist/schema.d.ts +8 -6
  38. package/dist/schema.js +31 -18
  39. package/dist/services/ai-agent-runner-service.d.ts +24 -0
  40. package/dist/services/ai-agent-runner-service.js +1 -0
  41. package/dist/services/ai-run-state-service.d.ts +13 -0
  42. package/dist/services/ai-run-state-service.js +1 -0
  43. package/dist/services/ai-storage-service.d.ts +18 -0
  44. package/dist/services/ai-storage-service.js +1 -0
  45. package/dist/services/deployment-service.d.ts +19 -0
  46. package/dist/services/deployment-service.js +1 -0
  47. package/dist/services/gopass-secrets.d.ts +47 -0
  48. package/dist/services/gopass-secrets.js +94 -0
  49. package/dist/services/in-memory-trigger-service.d.ts +23 -0
  50. package/dist/services/in-memory-trigger-service.js +50 -0
  51. package/dist/services/in-memory-workflow-service.d.ts +59 -0
  52. package/dist/services/in-memory-workflow-service.js +287 -0
  53. package/dist/services/index.d.ts +25 -11
  54. package/dist/services/index.js +10 -13
  55. package/dist/services/local-secrets.d.ts +23 -0
  56. package/dist/services/local-secrets.js +45 -0
  57. package/dist/services/local-variables.d.ts +5 -0
  58. package/dist/services/local-variables.js +18 -0
  59. package/dist/services/scheduler-service.d.ts +21 -1
  60. package/dist/services/scheduler-service.js +14 -0
  61. package/dist/services/schema-service.d.ts +6 -0
  62. package/dist/services/scoped-secret-service.d.ts +16 -0
  63. package/dist/services/scoped-secret-service.js +35 -0
  64. package/dist/services/secret-service.d.ts +25 -4
  65. package/dist/services/trigger-service.d.ts +23 -0
  66. package/dist/services/trigger-service.js +1 -0
  67. package/dist/services/typed-secret-service.d.ts +30 -0
  68. package/dist/services/typed-secret-service.js +40 -0
  69. package/dist/services/typed-variables-service.d.ts +27 -0
  70. package/dist/services/typed-variables-service.js +46 -0
  71. package/dist/services/user-session-service.d.ts +13 -0
  72. package/dist/services/user-session-service.js +17 -0
  73. package/dist/services/variables-service.d.ts +5 -0
  74. package/dist/services/workflow-service.d.ts +43 -0
  75. package/dist/services/workflow-service.js +1 -0
  76. package/dist/types/core.types.d.ts +54 -10
  77. package/dist/types/core.types.js +7 -0
  78. package/dist/types/state.types.d.ts +127 -0
  79. package/dist/types/state.types.js +1 -0
  80. package/dist/utils.d.ts +10 -1
  81. package/dist/utils.js +60 -3
  82. package/dist/version.d.ts +6 -0
  83. package/dist/version.js +19 -0
  84. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +29 -0
  85. package/dist/wirings/ai-agent/ai-agent-memory.js +199 -0
  86. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +9 -0
  87. package/dist/wirings/ai-agent/ai-agent-model-config.js +36 -0
  88. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +60 -0
  89. package/dist/wirings/ai-agent/ai-agent-prepare.js +312 -0
  90. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +15 -0
  91. package/dist/wirings/ai-agent/ai-agent-registry.js +47 -0
  92. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +3 -0
  93. package/dist/wirings/ai-agent/ai-agent-runner.js +96 -0
  94. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +11 -0
  95. package/dist/wirings/ai-agent/ai-agent-stream.js +503 -0
  96. package/dist/wirings/ai-agent/ai-agent.types.d.ts +254 -0
  97. package/dist/wirings/ai-agent/ai-agent.types.js +1 -0
  98. package/dist/wirings/ai-agent/index.d.ts +5 -0
  99. package/dist/wirings/ai-agent/index.js +4 -0
  100. package/dist/wirings/channel/channel-common.d.ts +7 -4
  101. package/dist/wirings/channel/channel-common.js +15 -8
  102. package/dist/wirings/channel/channel-handler.js +13 -4
  103. package/dist/wirings/channel/channel-middleware-runner.d.ts +9 -0
  104. package/dist/wirings/channel/channel-middleware-runner.js +73 -0
  105. package/dist/wirings/channel/channel-runner.js +12 -12
  106. package/dist/wirings/channel/channel.types.d.ts +5 -1
  107. package/dist/wirings/channel/define-channel-routes.d.ts +21 -0
  108. package/dist/wirings/channel/define-channel-routes.js +23 -0
  109. package/dist/wirings/channel/index.d.ts +10 -8
  110. package/dist/wirings/channel/index.js +7 -8
  111. package/dist/wirings/channel/local/index.d.ts +3 -3
  112. package/dist/wirings/channel/local/index.js +3 -3
  113. package/dist/wirings/channel/local/local-channel-runner.js +18 -6
  114. package/dist/wirings/channel/log-channels.js +1 -1
  115. package/dist/wirings/channel/serverless/index.d.ts +1 -1
  116. package/dist/wirings/channel/serverless/index.js +1 -1
  117. package/dist/wirings/channel/serverless/serverless-channel-runner.js +17 -6
  118. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  119. package/dist/wirings/cli/channel/index.d.ts +1 -1
  120. package/dist/wirings/cli/channel/index.js +1 -1
  121. package/dist/wirings/cli/cli-runner.d.ts +1 -1
  122. package/dist/wirings/cli/cli-runner.js +22 -18
  123. package/dist/wirings/cli/cli.types.d.ts +7 -6
  124. package/dist/wirings/cli/define-cli-commands.d.ts +19 -0
  125. package/dist/wirings/cli/define-cli-commands.js +20 -0
  126. package/dist/wirings/cli/index.d.ts +2 -3
  127. package/dist/wirings/cli/index.js +1 -3
  128. package/dist/wirings/http/http-routes.d.ts +69 -0
  129. package/dist/wirings/http/http-routes.js +139 -0
  130. package/dist/wirings/http/http-runner.d.ts +2 -2
  131. package/dist/wirings/http/http-runner.js +62 -27
  132. package/dist/wirings/http/http.types.d.ts +77 -24
  133. package/dist/wirings/http/index.d.ts +5 -4
  134. package/dist/wirings/http/index.js +4 -3
  135. package/dist/wirings/http/log-http-routes.js +1 -1
  136. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  137. package/dist/wirings/mcp/index.d.ts +4 -3
  138. package/dist/wirings/mcp/index.js +3 -3
  139. package/dist/wirings/mcp/mcp-runner.d.ts +1 -3
  140. package/dist/wirings/mcp/mcp-runner.js +42 -56
  141. package/dist/wirings/mcp/mcp.types.d.ts +3 -3
  142. package/dist/wirings/node/index.d.ts +2 -0
  143. package/dist/wirings/node/index.js +1 -0
  144. package/dist/wirings/node/node.types.d.ts +20 -0
  145. package/dist/wirings/node/node.types.js +1 -0
  146. package/dist/wirings/oauth2/index.d.ts +3 -0
  147. package/dist/wirings/oauth2/index.js +2 -0
  148. package/dist/wirings/oauth2/oauth2-client.d.ts +47 -0
  149. package/dist/wirings/oauth2/oauth2-client.js +267 -0
  150. package/dist/wirings/oauth2/oauth2.types.d.ts +65 -0
  151. package/dist/wirings/oauth2/oauth2.types.js +4 -0
  152. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +20 -0
  153. package/dist/wirings/oauth2/wire-oauth2-credential.js +21 -0
  154. package/dist/wirings/queue/index.d.ts +2 -2
  155. package/dist/wirings/queue/index.js +1 -1
  156. package/dist/wirings/queue/queue-runner.d.ts +7 -0
  157. package/dist/wirings/queue/queue-runner.js +23 -12
  158. package/dist/wirings/queue/queue.types.d.ts +8 -2
  159. package/dist/wirings/rpc/index.d.ts +1 -1
  160. package/dist/wirings/rpc/index.js +1 -1
  161. package/dist/wirings/rpc/rpc-runner.d.ts +40 -13
  162. package/dist/wirings/rpc/rpc-runner.js +191 -25
  163. package/dist/wirings/rpc/rpc-types.d.ts +15 -4
  164. package/dist/wirings/scheduler/index.d.ts +3 -3
  165. package/dist/wirings/scheduler/index.js +2 -3
  166. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  167. package/dist/wirings/scheduler/scheduler-runner.d.ts +5 -0
  168. package/dist/wirings/scheduler/scheduler-runner.js +28 -10
  169. package/dist/wirings/secret/index.d.ts +3 -0
  170. package/dist/wirings/secret/index.js +2 -0
  171. package/dist/wirings/secret/secret.types.d.ts +27 -0
  172. package/dist/wirings/secret/secret.types.js +1 -0
  173. package/dist/wirings/secret/validate-secret-definitions.d.ts +6 -0
  174. package/dist/wirings/secret/validate-secret-definitions.js +55 -0
  175. package/dist/wirings/trigger/index.d.ts +3 -0
  176. package/dist/wirings/trigger/index.js +2 -0
  177. package/dist/wirings/trigger/pikku-trigger-service.d.ts +26 -0
  178. package/dist/wirings/trigger/pikku-trigger-service.js +75 -0
  179. package/dist/wirings/trigger/trigger-runner.d.ts +42 -0
  180. package/dist/wirings/trigger/trigger-runner.js +85 -0
  181. package/dist/wirings/trigger/trigger.types.d.ts +130 -0
  182. package/dist/wirings/trigger/trigger.types.js +40 -0
  183. package/dist/wirings/variable/index.d.ts +3 -0
  184. package/dist/wirings/variable/index.js +2 -0
  185. package/dist/wirings/variable/validate-variable-definitions.d.ts +6 -0
  186. package/dist/wirings/variable/validate-variable-definitions.js +45 -0
  187. package/dist/wirings/variable/variable.types.d.ts +18 -0
  188. package/dist/wirings/variable/variable.types.js +1 -0
  189. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  190. package/dist/wirings/workflow/dsl/index.js +4 -0
  191. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +292 -0
  192. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  193. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  194. package/dist/wirings/workflow/dsl/workflow-runner.js +23 -0
  195. package/dist/wirings/workflow/graph/graph-node.d.ts +117 -0
  196. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  197. package/dist/wirings/workflow/graph/graph-runner.d.ts +9 -0
  198. package/dist/wirings/workflow/graph/graph-runner.js +540 -0
  199. package/dist/wirings/workflow/graph/index.d.ts +4 -0
  200. package/dist/wirings/workflow/graph/index.js +3 -0
  201. package/dist/wirings/workflow/graph/template.d.ts +15 -0
  202. package/dist/wirings/workflow/graph/template.js +27 -0
  203. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +15 -0
  204. package/dist/wirings/workflow/graph/wire-workflow-graph.js +8 -0
  205. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +82 -0
  206. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  207. package/dist/wirings/workflow/index.d.ts +7 -12
  208. package/dist/wirings/workflow/index.js +8 -11
  209. package/dist/wirings/workflow/pikku-workflow-service.d.ts +114 -10
  210. package/dist/wirings/workflow/pikku-workflow-service.js +386 -115
  211. package/dist/wirings/workflow/workflow-helpers.d.ts +36 -0
  212. package/dist/wirings/workflow/workflow-helpers.js +38 -0
  213. package/dist/wirings/workflow/workflow-utils.d.ts +1 -0
  214. package/dist/wirings/workflow/workflow-utils.js +1 -0
  215. package/dist/wirings/workflow/workflow.types.d.ts +83 -214
  216. package/package.json +13 -6
  217. package/run-tests.sh +4 -1
  218. package/src/crypto-utils.ts +99 -0
  219. package/src/errors/error-handler.ts +3 -3
  220. package/src/errors/errors.ts +17 -0
  221. package/src/errors/index.ts +0 -1
  222. package/src/factory-functions.test.ts +42 -2
  223. package/src/function/function-runner.test.ts +5 -12
  224. package/src/function/function-runner.ts +195 -40
  225. package/src/function/functions.types.ts +66 -14
  226. package/src/function/index.ts +5 -2
  227. package/src/handle-error.ts +2 -10
  228. package/src/index.ts +106 -27
  229. package/src/middleware/auth-apikey.test.ts +15 -12
  230. package/src/middleware/auth-apikey.ts +3 -3
  231. package/src/middleware/auth-bearer.test.ts +17 -14
  232. package/src/middleware/auth-bearer.ts +25 -27
  233. package/src/middleware/auth-cookie.test.ts +16 -13
  234. package/src/middleware/auth-cookie.ts +6 -8
  235. package/src/middleware/cors.ts +96 -0
  236. package/src/middleware/index.ts +5 -3
  237. package/src/middleware/remote-auth.ts +68 -0
  238. package/src/middleware-runner.test.ts +122 -0
  239. package/src/middleware-runner.ts +24 -17
  240. package/src/permissions.test.ts +102 -86
  241. package/src/permissions.ts +32 -93
  242. package/src/pikku-state.ts +168 -201
  243. package/src/run-tests-script.test.ts +49 -0
  244. package/src/schema.ts +50 -18
  245. package/src/services/ai-agent-runner-service.ts +29 -0
  246. package/src/services/ai-run-state-service.ts +20 -0
  247. package/src/services/ai-storage-service.ts +39 -0
  248. package/src/services/deployment-service.ts +22 -0
  249. package/src/services/gopass-secrets.ts +95 -0
  250. package/src/services/in-memory-trigger-service.ts +68 -0
  251. package/src/services/in-memory-workflow-service.ts +395 -0
  252. package/src/services/index.ts +49 -14
  253. package/src/services/local-secrets.ts +51 -0
  254. package/src/services/local-variables.ts +22 -0
  255. package/src/services/scheduler-service.ts +29 -1
  256. package/src/services/schema-service.ts +7 -0
  257. package/src/services/scoped-secret-service.ts +41 -0
  258. package/src/services/secret-service.ts +25 -4
  259. package/src/services/trigger-service.ts +38 -0
  260. package/src/services/typed-secret-service.ts +70 -0
  261. package/src/services/typed-variables-service.ts +81 -0
  262. package/src/services/user-session-service.ts +23 -0
  263. package/src/services/variables-service.ts +5 -0
  264. package/src/services/workflow-service.ts +94 -0
  265. package/src/types/core.types.ts +82 -8
  266. package/src/types/state.types.ts +169 -0
  267. package/src/utils.ts +74 -5
  268. package/src/version.test.ts +80 -0
  269. package/src/version.ts +25 -0
  270. package/src/wirings/ai-agent/ai-agent-memory.ts +300 -0
  271. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  272. package/src/wirings/ai-agent/ai-agent-prepare.ts +470 -0
  273. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  274. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  275. package/src/wirings/ai-agent/ai-agent-runner.test.ts +75 -0
  276. package/src/wirings/ai-agent/ai-agent-runner.ts +137 -0
  277. package/src/wirings/ai-agent/ai-agent-stream.test.ts +246 -0
  278. package/src/wirings/ai-agent/ai-agent-stream.ts +774 -0
  279. package/src/wirings/ai-agent/ai-agent.types.ts +273 -0
  280. package/src/wirings/ai-agent/index.ts +28 -0
  281. package/src/wirings/channel/channel-common.ts +32 -19
  282. package/src/wirings/channel/channel-handler.ts +18 -5
  283. package/src/wirings/channel/channel-middleware-runner.ts +119 -0
  284. package/src/wirings/channel/channel-runner.ts +14 -15
  285. package/src/wirings/channel/channel.types.ts +17 -1
  286. package/src/wirings/channel/define-channel-routes.ts +25 -0
  287. package/src/wirings/channel/index.ts +23 -8
  288. package/src/wirings/channel/local/index.ts +3 -3
  289. package/src/wirings/channel/local/local-channel-runner.test.ts +47 -2
  290. package/src/wirings/channel/local/local-channel-runner.ts +22 -6
  291. package/src/wirings/channel/log-channels.ts +1 -1
  292. package/src/wirings/channel/serverless/index.ts +5 -1
  293. package/src/wirings/channel/serverless/serverless-channel-runner.ts +22 -6
  294. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  295. package/src/wirings/cli/channel/index.ts +1 -1
  296. package/src/wirings/cli/cli-runner.test.ts +28 -28
  297. package/src/wirings/cli/cli-runner.ts +27 -19
  298. package/src/wirings/cli/cli.types.ts +12 -11
  299. package/src/wirings/cli/command-parser.test.ts +8 -8
  300. package/src/wirings/cli/define-cli-commands.ts +24 -0
  301. package/src/wirings/cli/index.ts +10 -4
  302. package/src/wirings/http/http-routes.test.ts +322 -0
  303. package/src/wirings/http/http-routes.ts +198 -0
  304. package/src/wirings/http/http-runner.test.ts +5 -5
  305. package/src/wirings/http/http-runner.ts +80 -29
  306. package/src/wirings/http/http.types.ts +121 -23
  307. package/src/wirings/http/index.ts +16 -4
  308. package/src/wirings/http/log-http-routes.ts +1 -1
  309. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  310. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  311. package/src/wirings/mcp/index.ts +27 -3
  312. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +3 -3
  313. package/src/wirings/mcp/mcp-runner.ts +56 -67
  314. package/src/wirings/mcp/mcp.types.ts +3 -3
  315. package/src/wirings/node/index.ts +2 -0
  316. package/src/wirings/node/node.types.ts +23 -0
  317. package/src/wirings/oauth2/index.ts +3 -0
  318. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  319. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  320. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  321. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  322. package/src/wirings/queue/index.ts +14 -2
  323. package/src/wirings/queue/queue-runner.test.ts +115 -73
  324. package/src/wirings/queue/queue-runner.ts +39 -13
  325. package/src/wirings/queue/queue.types.ts +12 -2
  326. package/src/wirings/rpc/index.ts +1 -1
  327. package/src/wirings/rpc/rpc-runner.ts +270 -34
  328. package/src/wirings/rpc/rpc-types.ts +21 -4
  329. package/src/wirings/scheduler/index.ts +7 -4
  330. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  331. package/src/wirings/scheduler/scheduler-runner.test.ts +97 -53
  332. package/src/wirings/scheduler/scheduler-runner.ts +50 -10
  333. package/src/wirings/secret/index.ts +9 -0
  334. package/src/wirings/secret/secret.types.ts +32 -0
  335. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  336. package/src/wirings/trigger/index.ts +10 -0
  337. package/src/wirings/trigger/pikku-trigger-service.ts +134 -0
  338. package/src/wirings/trigger/trigger-runner.ts +138 -0
  339. package/src/wirings/trigger/trigger.types.ts +178 -0
  340. package/src/wirings/variable/index.ts +8 -0
  341. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  342. package/src/wirings/variable/variable.types.ts +22 -0
  343. package/src/wirings/workflow/dsl/index.ts +31 -0
  344. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
  345. package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
  346. package/src/wirings/workflow/graph/graph-node.ts +222 -0
  347. package/src/wirings/workflow/graph/graph-runner.test.ts +495 -0
  348. package/src/wirings/workflow/graph/graph-runner.ts +816 -0
  349. package/src/wirings/workflow/graph/index.ts +14 -0
  350. package/src/wirings/workflow/graph/template.ts +42 -0
  351. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  352. package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
  353. package/src/wirings/workflow/index.ts +64 -26
  354. package/src/wirings/workflow/pikku-workflow-service.test.ts +212 -0
  355. package/src/wirings/workflow/pikku-workflow-service.ts +571 -136
  356. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  357. package/src/wirings/workflow/workflow-utils.ts +0 -0
  358. package/src/wirings/workflow/workflow.types.ts +123 -279
  359. package/tsconfig.tsbuildinfo +1 -1
  360. package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
  361. package/dist/wirings/workflow/workflow-runner.js +0 -65
  362. package/src/function/function-runner.ts.bak +0 -188
  363. package/src/wirings/workflow/workflow-runner.ts +0 -72
@@ -1,4 +1,4 @@
1
- import { NotFoundError } from '../../errors/errors.js'
1
+ import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js'
2
2
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
3
3
  import { pikkuState } from '../../pikku-state.js'
4
4
  import { CorePikkuMiddleware, CoreUserSession } from '../../types/core.types.js'
@@ -20,7 +20,10 @@ import type {
20
20
  CreateSingletonServices,
21
21
  } from '../../types/core.types.js'
22
22
  import { PikkuChannel } from '../channel/channel.types.js'
23
- import { PikkuSessionService } from '../../services/user-session-service.js'
23
+ import {
24
+ PikkuSessionService,
25
+ createMiddlewareSessionWireProps,
26
+ } from '../../services/user-session-service.js'
24
27
  import { LocalVariablesService } from '../../services/local-variables.js'
25
28
  import { generateCommandHelp, parseCLIArguments } from './command-parser.js'
26
29
 
@@ -57,17 +60,17 @@ export const wireCLI = <
57
60
  cli: CoreCLI<Commands, GlobalOptions, PikkuMiddleware, GlobalOutput>
58
61
  ) => {
59
62
  // Get the existing metadata that was generated during inspection
60
- const cliMeta = pikkuState('cli', 'meta') || {}
63
+ const cliMeta = pikkuState(null, 'cli', 'meta') || {}
61
64
 
62
65
  if (!cliMeta.programs?.[cli.program]) {
63
- throw new Error(
64
- `CLI metadata not found for program '${cli.program}'. Did you run 'pikku all'?`
66
+ throw new PikkuMissingMetaError(
67
+ `CLI metadata not found for program '${cli.program}'`
65
68
  )
66
69
  }
67
70
 
68
71
  // Get existing programs state and add this program
69
72
  const programs: Record<string, CLIProgramState> =
70
- pikkuState('cli', 'programs') || {}
73
+ pikkuState(null, 'cli', 'programs') || {}
71
74
  programs[cli.program] = {
72
75
  defaultRenderer: (cli.render ||
73
76
  defaultJSONRenderer) as CorePikkuCLIRender<any>,
@@ -75,7 +78,7 @@ export const wireCLI = <
75
78
  renderers: {},
76
79
  tags: cli.tags,
77
80
  }
78
- pikkuState('cli', 'programs', programs)
81
+ pikkuState(null, 'cli', 'programs', programs)
79
82
 
80
83
  // Register all command functions recursively
81
84
  registerCLICommands(
@@ -130,7 +133,7 @@ function registerCLICommands(
130
133
  program: string
131
134
  ) {
132
135
  // Get the CLI metadata to find actual function names
133
- const cliMeta = pikkuState('cli', 'meta').programs[program]
136
+ const cliMeta = pikkuState(null, 'cli', 'meta').programs[program]
134
137
 
135
138
  for (const [name, command] of Object.entries(commands)) {
136
139
  const fullPath = [...path, name]
@@ -141,7 +144,7 @@ function registerCLICommands(
141
144
  for (let i = 1; i < fullPath.length; i++) {
142
145
  currentMeta = currentMeta?.subcommands?.[fullPath[i]]
143
146
  }
144
- const funcName = currentMeta?.pikkuFuncName
147
+ const funcName = currentMeta?.pikkuFuncId
145
148
 
146
149
  // Skip if no function name (could be a command group)
147
150
  if (!funcName) {
@@ -166,6 +169,7 @@ function registerCLICommands(
166
169
 
167
170
  // Store the options and middleware in program state for use during execution
168
171
  const programs: Record<string, CLIProgramState> = pikkuState(
172
+ null,
169
173
  'cli',
170
174
  'programs'
171
175
  )
@@ -208,10 +212,10 @@ function pluckCLIData(
208
212
  funcName: string,
209
213
  availableOptions: Record<string, CLIOption>
210
214
  ): Record<string, any> {
211
- const funcMeta = pikkuState('function', 'meta')[funcName]
215
+ const funcMeta = pikkuState(null, 'function', 'meta')[funcName]
212
216
  const schemaName = funcMeta?.inputSchemaName
213
217
  const schema = schemaName
214
- ? pikkuState('misc', 'schemas').get(schemaName)
218
+ ? pikkuState(null, 'misc', 'schemas').get(schemaName)
215
219
  : null
216
220
 
217
221
  if (schema && schema.properties) {
@@ -249,7 +253,7 @@ export async function runCLICommand({
249
253
  createWireServices?: CreateWireServices
250
254
  }): Promise<any> {
251
255
  // Get the command metadata to find the function name
252
- const cliMeta = pikkuState('cli', 'meta')
256
+ const cliMeta = pikkuState(null, 'cli', 'meta')
253
257
  const programMeta = cliMeta.programs?.[program]
254
258
  if (!programMeta) {
255
259
  throw new NotFoundError(`Program not found: ${program}`)
@@ -271,11 +275,11 @@ export async function runCLICommand({
271
275
  currentCommand = currentCommand.subcommands[commandPath[i]]
272
276
  }
273
277
 
274
- const funcName = currentCommand.pikkuFuncName
278
+ const funcName = currentCommand.pikkuFuncId
275
279
 
276
280
  // Get program-specific data
277
281
  const programs: Record<string, CLIProgramState> =
278
- pikkuState('cli', 'programs') || {}
282
+ pikkuState(null, 'cli', 'programs') || {}
279
283
  const programData = programs[program]
280
284
 
281
285
  // Combine program middleware + command middleware from the hierarchy
@@ -331,7 +335,7 @@ export async function runCLICommand({
331
335
  data: pluckedData,
332
336
  channel,
333
337
  },
334
- session: userSession,
338
+ ...createMiddlewareSessionWireProps(userSession),
335
339
  }
336
340
 
337
341
  try {
@@ -346,10 +350,12 @@ export async function runCLICommand({
346
350
  wirePermissions: undefined,
347
351
  tags: programData?.tags,
348
352
  wire,
353
+ sessionService: userSession,
349
354
  })
350
355
 
351
356
  // Apply renderer one final time with the final output (if renderer exists)
352
- if (renderer) {
357
+ // Skip if result is undefined (void functions handle their own output)
358
+ if (renderer && result !== undefined) {
353
359
  await Promise.resolve(
354
360
  renderer(singletonServices, result, userSession.get())
355
361
  )
@@ -394,7 +400,7 @@ export async function executeCLI({
394
400
  }: {
395
401
  programName: string
396
402
  args?: string[]
397
- createConfig: CreateConfig<any, any>
403
+ createConfig?: CreateConfig<any, any>
398
404
  createSingletonServices: CreateSingletonServices<any, any>
399
405
  createWireServices?: CreateWireServices<any, any, any>
400
406
  }): Promise<void> {
@@ -406,7 +412,7 @@ export async function executeCLI({
406
412
 
407
413
  try {
408
414
  // Get CLI metadata from state
409
- const allCLIMeta = pikkuState('cli', 'meta') as unknown as
415
+ const allCLIMeta = pikkuState(null, 'cli', 'meta') as unknown as
410
416
  | CLIMeta
411
417
  | undefined
412
418
  if (!allCLIMeta) {
@@ -469,7 +475,9 @@ export async function executeCLI({
469
475
  const data = { ...parsed.positionals, ...parsed.options }
470
476
 
471
477
  // Create config (pass data in case it needs to use any parsed options)
472
- const config = await createConfig(new LocalVariablesService(), data)
478
+ const config = createConfig
479
+ ? await createConfig(new LocalVariablesService(), data)
480
+ : ({} as any)
473
481
 
474
482
  // Create services with config
475
483
  const singletonServices = await createSingletonServices(config)
@@ -69,7 +69,7 @@ export interface CLIProgramState {
69
69
  */
70
70
  export interface CLICommandMeta {
71
71
  parameters?: string
72
- pikkuFuncName: string
72
+ pikkuFuncId: string
73
73
  positionals: CLIPositional[]
74
74
  options: Record<string, CLIOption>
75
75
  renderName?: string
@@ -131,8 +131,8 @@ export type CorePikkuCLIRender<
131
131
  export type ExtractFunctionInput<Func> =
132
132
  Func extends CorePikkuFunctionConfig<infer FuncType, any, any>
133
133
  ? FuncType extends
134
- | CorePikkuFunction<infer Input, any, any, any>
135
- | CorePikkuFunctionSessionless<infer Input, any, any, any>
134
+ | CorePikkuFunction<infer Input, any, any, any, any>
135
+ | CorePikkuFunctionSessionless<infer Input, any, any, any, any>
136
136
  ? Input
137
137
  : never
138
138
  : never
@@ -198,8 +198,8 @@ export type ValidateParameters<Params extends string, Input> =
198
198
  export type ExtractFunctionOutput<Func> =
199
199
  Func extends CorePikkuFunctionConfig<infer FuncType, any, any>
200
200
  ? FuncType extends
201
- | CorePikkuFunction<any, infer Output, any, any>
202
- | CorePikkuFunctionSessionless<any, infer Output, any, any>
201
+ | CorePikkuFunction<any, infer Output, any, any, any>
202
+ | CorePikkuFunctionSessionless<any, infer Output, any, any, any>
203
203
  ? Output
204
204
  : never
205
205
  : never
@@ -222,6 +222,7 @@ export type CoreCLICommandConfig<
222
222
  > = {
223
223
  parameters?: ValidateParameters<Params, ExtractFunctionInput<FuncConfig>>
224
224
  func?: FuncConfig
225
+ title?: string
225
226
  description?: string
226
227
  render?: PikkuCLIRender
227
228
  options?: {
@@ -248,8 +249,8 @@ export interface CoreCLICommand<
248
249
  In,
249
250
  Out,
250
251
  PikkuFunctionConfig extends CorePikkuFunctionConfig<
251
- | CorePikkuFunction<In, Out, any, any>
252
- | CorePikkuFunctionSessionless<In, Out, any, any>
252
+ | CorePikkuFunction<In, Out, any, any, any>
253
+ | CorePikkuFunctionSessionless<In, Out, any, any, any>
253
254
  >,
254
255
  PikkuPermission extends CorePikkuPermission<any, any, any>,
255
256
  PikkuMiddleware extends CorePikkuMiddleware,
@@ -281,8 +282,8 @@ export type CLICommandShorthand<
281
282
  In,
282
283
  Out,
283
284
  PikkuFunctionConfig extends CorePikkuFunctionConfig<
284
- | CorePikkuFunction<In, Out, any, any>
285
- | CorePikkuFunctionSessionless<In, Out, any, any>
285
+ | CorePikkuFunction<In, Out, any, any, any>
286
+ | CorePikkuFunctionSessionless<In, Out, any, any, any>
286
287
  >,
287
288
  > = PikkuFunctionConfig
288
289
 
@@ -293,8 +294,8 @@ export type CLICommandDefinition<
293
294
  In,
294
295
  Out,
295
296
  PikkuFunctionConfig extends CorePikkuFunctionConfig<
296
- | CorePikkuFunction<In, Out, any, any>
297
- | CorePikkuFunctionSessionless<In, Out, any, any>
297
+ | CorePikkuFunction<In, Out, any, any, any>
298
+ | CorePikkuFunctionSessionless<In, Out, any, any, any>
298
299
  >,
299
300
  PikkuPermission extends CorePikkuPermission<any, any, any>,
300
301
  PikkuMiddleware extends CorePikkuMiddleware,
@@ -17,7 +17,7 @@ const testMeta: CLIMeta = {
17
17
  commands: {
18
18
  greet: {
19
19
  parameters: '<name>',
20
- pikkuFuncName: 'greetFunc',
20
+ pikkuFuncId: 'greetFunc',
21
21
  positionals: [{ name: 'name', required: true }],
22
22
  options: {
23
23
  loud: {
@@ -28,13 +28,13 @@ const testMeta: CLIMeta = {
28
28
  },
29
29
  },
30
30
  user: {
31
- pikkuFuncName: '',
31
+ pikkuFuncId: '',
32
32
  positionals: [],
33
33
  options: {},
34
34
  subcommands: {
35
35
  create: {
36
36
  parameters: '<name> <email>',
37
- pikkuFuncName: 'createUserFunc',
37
+ pikkuFuncId: 'createUserFunc',
38
38
  positionals: [
39
39
  { name: 'name', required: true },
40
40
  { name: 'email', required: true },
@@ -50,7 +50,7 @@ const testMeta: CLIMeta = {
50
50
  },
51
51
  delete: {
52
52
  parameters: '<id>',
53
- pikkuFuncName: 'deleteUserFunc',
53
+ pikkuFuncId: 'deleteUserFunc',
54
54
  positionals: [{ name: 'id', required: true }],
55
55
  options: {
56
56
  force: {
@@ -64,13 +64,13 @@ const testMeta: CLIMeta = {
64
64
  },
65
65
  files: {
66
66
  parameters: '<paths...>',
67
- pikkuFuncName: 'filesFunc',
67
+ pikkuFuncId: 'filesFunc',
68
68
  positionals: [{ name: 'paths', required: true, variadic: true }],
69
69
  options: {},
70
70
  },
71
71
  optional: {
72
72
  parameters: '[name]',
73
- pikkuFuncName: 'optionalFunc',
73
+ pikkuFuncId: 'optionalFunc',
74
74
  positionals: [{ name: 'name', required: false }],
75
75
  options: {},
76
76
  },
@@ -299,7 +299,7 @@ describe('Command Parser', () => {
299
299
  options: {},
300
300
  commands: {
301
301
  test: {
302
- pikkuFuncName: 'testFunc',
302
+ pikkuFuncId: 'testFunc',
303
303
  positionals: [],
304
304
  options: {
305
305
  port: {
@@ -380,7 +380,7 @@ describe('Command Parser', () => {
380
380
  commands: {
381
381
  greet: {
382
382
  parameters: '<name>',
383
- pikkuFuncName: 'greetFunc',
383
+ pikkuFuncId: 'greetFunc',
384
384
  description: 'Greet a user',
385
385
  positionals: [{ name: 'name', required: true }],
386
386
  options: {},
@@ -0,0 +1,24 @@
1
+ import type { CoreCLICommandConfig } from './cli.types.js'
2
+
3
+ /**
4
+ * Type-safe helper for defining CLI commands that can be composed.
5
+ * Returns the commands record as-is (identity function) for use with `wireCLI`.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * export const chatCommands = defineCLICommands({
10
+ * chat: pikkuCLICommand({ func: chatFunc }),
11
+ * 'chat:history': pikkuCLICommand({ func: historyFunc }),
12
+ * })
13
+ *
14
+ * wireCLI({
15
+ * program: 'my-app',
16
+ * commands: { ...chatCommands, other: pikkuCLICommand({ func: otherFunc }) }
17
+ * })
18
+ * ```
19
+ */
20
+ export function defineCLICommands<
21
+ T extends Record<string, CoreCLICommandConfig<any, any, any, any>>,
22
+ >(commands: T): T {
23
+ return commands
24
+ }
@@ -1,7 +1,3 @@
1
- export * from './cli.types.js'
2
- export * from './cli-runner.js'
3
- export * from './command-parser.js'
4
-
5
1
  export {
6
2
  wireCLI,
7
3
  runCLICommand,
@@ -11,3 +7,13 @@ export {
11
7
  } from './cli-runner.js'
12
8
 
13
9
  export { parseCLIArguments, generateCommandHelp } from './command-parser.js'
10
+
11
+ export { defineCLICommands } from './define-cli-commands.js'
12
+ export type {
13
+ CLIMeta,
14
+ CLICommandMeta,
15
+ CLIProgramMeta,
16
+ CoreCLI,
17
+ CoreCLICommandConfig,
18
+ CorePikkuCLIRender,
19
+ } from './cli.types.js'
@@ -0,0 +1,322 @@
1
+ import { test, describe, beforeEach, afterEach } from 'node:test'
2
+ import * as assert from 'assert'
3
+ import { wireHTTPRoutes, defineHTTPRoutes } from './http-routes.js'
4
+ import { pikkuState, resetPikkuState } from '../../pikku-state.js'
5
+ import { addFunction } from '../../function/function-runner.js'
6
+ import { httpRouter } from './routers/http-router.js'
7
+ import { CorePikkuMiddleware } from '../../types/core.types.js'
8
+
9
+ // Mock function for testing
10
+ const mockFunc = async () => ({ success: true })
11
+
12
+ // Mock middleware
13
+ const mockMiddleware: CorePikkuMiddleware = async (_services, _wire, next) => {
14
+ await next()
15
+ }
16
+
17
+ // Helper to set up function metadata for routes
18
+ const setupFunctionMeta = (
19
+ routes: Array<{ method: string; route: string }>
20
+ ) => {
21
+ const meta: Record<string, Record<string, any>> = {
22
+ get: {},
23
+ post: {},
24
+ delete: {},
25
+ patch: {},
26
+ head: {},
27
+ put: {},
28
+ options: {},
29
+ }
30
+
31
+ for (const { method, route } of routes) {
32
+ meta[method][route] = {
33
+ pikkuFuncId: `func_${route.replace(/[^a-z0-9]/gi, '_')}`,
34
+ route,
35
+ method,
36
+ }
37
+ }
38
+
39
+ pikkuState(null, 'http', 'meta', meta)
40
+
41
+ for (const { method, route } of routes) {
42
+ const funcName = `func_${route.replace(/[^a-z0-9]/gi, '_')}`
43
+ pikkuState(null, 'function', 'meta', {
44
+ [funcName]: {
45
+ pikkuFuncId: funcName,
46
+ services: [],
47
+ },
48
+ } as any)
49
+ addFunction(funcName, { func: mockFunc })
50
+ }
51
+ }
52
+
53
+ describe('defineHTTPRoutes', () => {
54
+ test('should accept routes directly', () => {
55
+ const routes = defineHTTPRoutes({
56
+ list: { method: 'get', route: '/todos', func: { func: mockFunc } },
57
+ get: { method: 'get', route: '/todos/:id', func: { func: mockFunc } },
58
+ })
59
+
60
+ assert.ok(routes.routes.list)
61
+ assert.ok(routes.routes.get)
62
+ assert.strictEqual(routes.routes.list.method, 'get')
63
+ assert.strictEqual(routes.routes.list.route, '/todos')
64
+ })
65
+
66
+ test('should accept config with routes', () => {
67
+ const routes = defineHTTPRoutes({
68
+ auth: true,
69
+ tags: ['todos'],
70
+ routes: {
71
+ list: { method: 'get', route: '/todos', func: { func: mockFunc } },
72
+ },
73
+ })
74
+
75
+ assert.strictEqual(routes.auth, true)
76
+ assert.deepStrictEqual(routes.tags, ['todos'])
77
+ assert.ok(routes.routes.list)
78
+ })
79
+
80
+ test('should handle basePath in config', () => {
81
+ const routes = defineHTTPRoutes({
82
+ basePath: '/api',
83
+ routes: {
84
+ list: { method: 'get', route: '/todos', func: { func: mockFunc } },
85
+ },
86
+ })
87
+
88
+ assert.strictEqual(routes.basePath, '/api')
89
+ })
90
+ })
91
+
92
+ describe('wireHTTPRoutes', () => {
93
+ beforeEach(() => {
94
+ resetPikkuState()
95
+ httpRouter.reset()
96
+ })
97
+
98
+ afterEach(() => {
99
+ resetPikkuState()
100
+ httpRouter.reset()
101
+ })
102
+
103
+ test('should wire flat array of routes', () => {
104
+ setupFunctionMeta([
105
+ { method: 'get', route: '/todos' },
106
+ { method: 'post', route: '/todos' },
107
+ ])
108
+
109
+ wireHTTPRoutes({
110
+ routes: [
111
+ { method: 'get', route: '/todos', func: { func: mockFunc } },
112
+ { method: 'post', route: '/todos', func: { func: mockFunc } },
113
+ ],
114
+ })
115
+
116
+ const routes = pikkuState(null, 'http', 'routes')
117
+ assert.ok(routes.get('get')?.has('/todos'))
118
+ assert.ok(routes.get('post')?.has('/todos'))
119
+ })
120
+
121
+ test('should wire nested object routes', () => {
122
+ setupFunctionMeta([
123
+ { method: 'get', route: '/todos' },
124
+ { method: 'get', route: '/todos/:id' },
125
+ { method: 'post', route: '/auth/login' },
126
+ ])
127
+
128
+ wireHTTPRoutes({
129
+ routes: {
130
+ todos: {
131
+ list: { method: 'get', route: '/todos', func: { func: mockFunc } },
132
+ get: {
133
+ method: 'get',
134
+ route: '/todos/:id',
135
+ func: { func: mockFunc },
136
+ },
137
+ },
138
+ auth: {
139
+ login: {
140
+ method: 'post',
141
+ route: '/auth/login',
142
+ func: { func: mockFunc },
143
+ },
144
+ },
145
+ },
146
+ })
147
+
148
+ const routes = pikkuState(null, 'http', 'routes')
149
+ assert.ok(routes.get('get')?.has('/todos'))
150
+ assert.ok(routes.get('get')?.has('/todos/:id'))
151
+ assert.ok(routes.get('post')?.has('/auth/login'))
152
+ })
153
+
154
+ test('should apply basePath to all routes', () => {
155
+ setupFunctionMeta([
156
+ { method: 'get', route: '/api/v1/todos' },
157
+ { method: 'post', route: '/api/v1/todos' },
158
+ ])
159
+
160
+ wireHTTPRoutes({
161
+ basePath: '/api/v1',
162
+ routes: [
163
+ { method: 'get', route: '/todos', func: { func: mockFunc } },
164
+ { method: 'post', route: '/todos', func: { func: mockFunc } },
165
+ ],
166
+ })
167
+
168
+ const routes = pikkuState(null, 'http', 'routes')
169
+ assert.ok(routes.get('get')?.has('/api/v1/todos'))
170
+ assert.ok(routes.get('post')?.has('/api/v1/todos'))
171
+ })
172
+
173
+ test('should merge tags from group config and route config', () => {
174
+ setupFunctionMeta([{ method: 'get', route: '/todos' }])
175
+
176
+ wireHTTPRoutes({
177
+ tags: ['api'],
178
+ routes: [
179
+ {
180
+ method: 'get',
181
+ route: '/todos',
182
+ func: { func: mockFunc },
183
+ tags: ['todos'],
184
+ },
185
+ ],
186
+ })
187
+
188
+ const routes = pikkuState(null, 'http', 'routes')
189
+ const route = routes.get('get')?.get('/todos')
190
+ assert.deepStrictEqual(route?.tags, ['api', 'todos'])
191
+ })
192
+
193
+ test('should merge middleware from group config and route config', () => {
194
+ setupFunctionMeta([{ method: 'get', route: '/todos' }])
195
+
196
+ const groupMiddleware: CorePikkuMiddleware = async (_s, _w, next) => {
197
+ await next()
198
+ }
199
+ const routeMiddleware: CorePikkuMiddleware = async (_s, _w, next) => {
200
+ await next()
201
+ }
202
+
203
+ wireHTTPRoutes({
204
+ middleware: [groupMiddleware],
205
+ routes: [
206
+ {
207
+ method: 'get',
208
+ route: '/todos',
209
+ func: { func: mockFunc },
210
+ middleware: [routeMiddleware],
211
+ },
212
+ ],
213
+ })
214
+
215
+ const routes = pikkuState(null, 'http', 'routes')
216
+ const route = routes.get('get')?.get('/todos')
217
+ assert.strictEqual(route?.middleware?.length, 2)
218
+ })
219
+
220
+ test('should allow route auth to override group auth', () => {
221
+ setupFunctionMeta([
222
+ { method: 'get', route: '/todos' },
223
+ { method: 'post', route: '/auth/login' },
224
+ ])
225
+
226
+ wireHTTPRoutes({
227
+ auth: true,
228
+ routes: [
229
+ { method: 'get', route: '/todos', func: { func: mockFunc } },
230
+ {
231
+ method: 'post',
232
+ route: '/auth/login',
233
+ func: { func: mockFunc },
234
+ auth: false,
235
+ },
236
+ ],
237
+ })
238
+
239
+ const routes = pikkuState(null, 'http', 'routes')
240
+ const todosRoute = routes.get('get')?.get('/todos')
241
+ const loginRoute = routes.get('post')?.get('/auth/login')
242
+
243
+ assert.strictEqual(todosRoute?.auth, true)
244
+ assert.strictEqual(loginRoute?.auth, false)
245
+ })
246
+
247
+ test('should wire routes from defineHTTPRoutes contracts', () => {
248
+ const todosRoutes = defineHTTPRoutes({
249
+ tags: ['todos'],
250
+ routes: {
251
+ list: { method: 'get', route: '/todos', func: { func: mockFunc } },
252
+ },
253
+ })
254
+
255
+ setupFunctionMeta([{ method: 'get', route: '/api/todos' }])
256
+
257
+ wireHTTPRoutes({
258
+ basePath: '/api',
259
+ tags: ['api'],
260
+ routes: {
261
+ todos: todosRoutes,
262
+ },
263
+ })
264
+
265
+ const routes = pikkuState(null, 'http', 'routes')
266
+ const route = routes.get('get')?.get('/api/todos')
267
+ assert.ok(route)
268
+ assert.deepStrictEqual(route?.tags, ['api', 'todos'])
269
+ })
270
+
271
+ test('should cascade basePath from multiple levels', () => {
272
+ const todosRoutes = defineHTTPRoutes({
273
+ basePath: '/todos',
274
+ routes: {
275
+ list: { method: 'get', route: '', func: { func: mockFunc } },
276
+ get: { method: 'get', route: '/:id', func: { func: mockFunc } },
277
+ },
278
+ })
279
+
280
+ setupFunctionMeta([
281
+ { method: 'get', route: '/api/v1/todos' },
282
+ { method: 'get', route: '/api/v1/todos/:id' },
283
+ ])
284
+
285
+ wireHTTPRoutes({
286
+ basePath: '/api/v1',
287
+ routes: {
288
+ todos: todosRoutes,
289
+ },
290
+ })
291
+
292
+ const routes = pikkuState(null, 'http', 'routes')
293
+ assert.ok(routes.get('get')?.has('/api/v1/todos'))
294
+ assert.ok(routes.get('get')?.has('/api/v1/todos/:id'))
295
+ })
296
+
297
+ test('should handle deeply nested route maps', () => {
298
+ setupFunctionMeta([
299
+ { method: 'get', route: '/todos' },
300
+ { method: 'get', route: '/todos/:id' },
301
+ ])
302
+
303
+ wireHTTPRoutes({
304
+ routes: {
305
+ level1: {
306
+ level2: {
307
+ list: { method: 'get', route: '/todos', func: { func: mockFunc } },
308
+ get: {
309
+ method: 'get',
310
+ route: '/todos/:id',
311
+ func: { func: mockFunc },
312
+ },
313
+ },
314
+ },
315
+ },
316
+ })
317
+
318
+ const routes = pikkuState(null, 'http', 'routes')
319
+ assert.ok(routes.get('get')?.has('/todos'))
320
+ assert.ok(routes.get('get')?.has('/todos/:id'))
321
+ })
322
+ })