@pikku/core 0.11.2 → 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 (344) hide show
  1. package/CHANGELOG.md +33 -2
  2. package/dist/crypto-utils.d.ts +2 -0
  3. package/dist/crypto-utils.js +78 -0
  4. package/dist/errors/errors.d.ts +8 -0
  5. package/dist/errors/errors.js +16 -0
  6. package/dist/errors/index.d.ts +0 -1
  7. package/dist/errors/index.js +0 -1
  8. package/dist/function/function-runner.d.ts +7 -1
  9. package/dist/function/function-runner.js +82 -23
  10. package/dist/function/functions.types.d.ts +31 -87
  11. package/dist/function/functions.types.js +16 -46
  12. package/dist/function/index.d.ts +2 -2
  13. package/dist/function/index.js +1 -2
  14. package/dist/handle-error.js +1 -10
  15. package/dist/index.d.ts +28 -26
  16. package/dist/index.js +18 -29
  17. package/dist/middleware/auth-apikey.d.ts +2 -2
  18. package/dist/middleware/auth-apikey.js +3 -3
  19. package/dist/middleware/auth-bearer.d.ts +2 -2
  20. package/dist/middleware/auth-bearer.js +3 -6
  21. package/dist/middleware/auth-cookie.d.ts +2 -2
  22. package/dist/middleware/auth-cookie.js +6 -8
  23. package/dist/middleware/cors.d.ts +46 -0
  24. package/dist/middleware/cors.js +73 -0
  25. package/dist/middleware/index.d.ts +5 -3
  26. package/dist/middleware/index.js +5 -3
  27. package/dist/middleware/remote-auth.d.ts +5 -0
  28. package/dist/middleware/remote-auth.js +55 -0
  29. package/dist/middleware/timeout.d.ts +2 -2
  30. package/dist/middleware-runner.js +8 -10
  31. package/dist/permissions.js +11 -15
  32. package/dist/pikku-state.d.ts +1 -0
  33. package/dist/pikku-state.js +18 -8
  34. package/dist/schema.js +12 -11
  35. package/dist/services/ai-agent-runner-service.d.ts +24 -0
  36. package/dist/services/ai-agent-runner-service.js +1 -0
  37. package/dist/services/ai-run-state-service.d.ts +13 -0
  38. package/dist/services/ai-run-state-service.js +1 -0
  39. package/dist/services/ai-storage-service.d.ts +18 -0
  40. package/dist/services/ai-storage-service.js +1 -0
  41. package/dist/services/deployment-service.d.ts +19 -0
  42. package/dist/services/deployment-service.js +1 -0
  43. package/dist/services/gopass-secrets.d.ts +47 -0
  44. package/dist/services/gopass-secrets.js +94 -0
  45. package/dist/services/in-memory-trigger-service.d.ts +23 -0
  46. package/dist/services/in-memory-trigger-service.js +50 -0
  47. package/dist/services/in-memory-workflow-service.d.ts +59 -0
  48. package/dist/services/in-memory-workflow-service.js +287 -0
  49. package/dist/services/index.d.ts +25 -11
  50. package/dist/services/index.js +10 -13
  51. package/dist/services/local-secrets.d.ts +23 -0
  52. package/dist/services/local-secrets.js +45 -0
  53. package/dist/services/local-variables.d.ts +5 -0
  54. package/dist/services/local-variables.js +18 -0
  55. package/dist/services/scheduler-service.d.ts +21 -1
  56. package/dist/services/scheduler-service.js +14 -0
  57. package/dist/services/schema-service.d.ts +6 -0
  58. package/dist/services/scoped-secret-service.d.ts +16 -0
  59. package/dist/services/scoped-secret-service.js +35 -0
  60. package/dist/services/secret-service.d.ts +25 -4
  61. package/dist/services/trigger-service.d.ts +23 -0
  62. package/dist/services/trigger-service.js +1 -0
  63. package/dist/services/typed-secret-service.d.ts +30 -0
  64. package/dist/services/typed-secret-service.js +40 -0
  65. package/dist/services/typed-variables-service.d.ts +27 -0
  66. package/dist/services/typed-variables-service.js +46 -0
  67. package/dist/services/user-session-service.d.ts +13 -0
  68. package/dist/services/user-session-service.js +17 -0
  69. package/dist/services/variables-service.d.ts +5 -0
  70. package/dist/services/workflow-service.d.ts +9 -4
  71. package/dist/types/core.types.d.ts +48 -12
  72. package/dist/types/core.types.js +8 -1
  73. package/dist/types/state.types.d.ts +43 -51
  74. package/dist/utils.d.ts +3 -2
  75. package/dist/utils.js +15 -3
  76. package/dist/version.d.ts +6 -0
  77. package/dist/version.js +19 -0
  78. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +29 -0
  79. package/dist/wirings/ai-agent/ai-agent-memory.js +199 -0
  80. package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +9 -0
  81. package/dist/wirings/ai-agent/ai-agent-model-config.js +36 -0
  82. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +60 -0
  83. package/dist/wirings/ai-agent/ai-agent-prepare.js +312 -0
  84. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +15 -0
  85. package/dist/wirings/ai-agent/ai-agent-registry.js +47 -0
  86. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +3 -0
  87. package/dist/wirings/ai-agent/ai-agent-runner.js +96 -0
  88. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +11 -0
  89. package/dist/wirings/ai-agent/ai-agent-stream.js +503 -0
  90. package/dist/wirings/ai-agent/ai-agent.types.d.ts +254 -0
  91. package/dist/wirings/ai-agent/ai-agent.types.js +1 -0
  92. package/dist/wirings/ai-agent/index.d.ts +5 -0
  93. package/dist/wirings/ai-agent/index.js +4 -0
  94. package/dist/wirings/channel/channel-common.d.ts +7 -4
  95. package/dist/wirings/channel/channel-common.js +15 -8
  96. package/dist/wirings/channel/channel-handler.js +12 -3
  97. package/dist/wirings/channel/channel-middleware-runner.d.ts +9 -0
  98. package/dist/wirings/channel/channel-middleware-runner.js +73 -0
  99. package/dist/wirings/channel/channel-runner.js +7 -7
  100. package/dist/wirings/channel/channel.types.d.ts +5 -1
  101. package/dist/wirings/channel/define-channel-routes.d.ts +21 -0
  102. package/dist/wirings/channel/define-channel-routes.js +23 -0
  103. package/dist/wirings/channel/index.d.ts +10 -8
  104. package/dist/wirings/channel/index.js +7 -8
  105. package/dist/wirings/channel/local/index.d.ts +3 -3
  106. package/dist/wirings/channel/local/index.js +3 -3
  107. package/dist/wirings/channel/local/local-channel-runner.js +18 -6
  108. package/dist/wirings/channel/serverless/index.d.ts +1 -1
  109. package/dist/wirings/channel/serverless/index.js +1 -1
  110. package/dist/wirings/channel/serverless/serverless-channel-runner.js +15 -4
  111. package/dist/wirings/cli/channel/index.d.ts +1 -1
  112. package/dist/wirings/cli/channel/index.js +1 -1
  113. package/dist/wirings/cli/cli-runner.d.ts +1 -1
  114. package/dist/wirings/cli/cli-runner.js +12 -8
  115. package/dist/wirings/cli/cli.types.d.ts +6 -6
  116. package/dist/wirings/cli/define-cli-commands.d.ts +19 -0
  117. package/dist/wirings/cli/define-cli-commands.js +20 -0
  118. package/dist/wirings/cli/index.d.ts +2 -3
  119. package/dist/wirings/cli/index.js +1 -3
  120. package/dist/wirings/http/http-routes.d.ts +69 -0
  121. package/dist/wirings/http/http-routes.js +139 -0
  122. package/dist/wirings/http/http-runner.js +49 -23
  123. package/dist/wirings/http/http.types.d.ts +78 -38
  124. package/dist/wirings/http/index.d.ts +5 -4
  125. package/dist/wirings/http/index.js +4 -3
  126. package/dist/wirings/mcp/index.d.ts +4 -3
  127. package/dist/wirings/mcp/index.js +3 -3
  128. package/dist/wirings/mcp/mcp-runner.d.ts +1 -3
  129. package/dist/wirings/mcp/mcp-runner.js +25 -39
  130. package/dist/wirings/mcp/mcp.types.d.ts +3 -3
  131. package/dist/wirings/node/index.d.ts +2 -0
  132. package/dist/wirings/node/index.js +1 -0
  133. package/dist/wirings/node/node.types.d.ts +20 -0
  134. package/dist/wirings/node/node.types.js +1 -0
  135. package/dist/wirings/oauth2/index.d.ts +3 -0
  136. package/dist/wirings/oauth2/index.js +2 -0
  137. package/dist/wirings/oauth2/oauth2-client.d.ts +47 -0
  138. package/dist/wirings/oauth2/oauth2-client.js +267 -0
  139. package/dist/wirings/oauth2/oauth2.types.d.ts +65 -0
  140. package/dist/wirings/oauth2/oauth2.types.js +4 -0
  141. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +20 -0
  142. package/dist/wirings/oauth2/wire-oauth2-credential.js +21 -0
  143. package/dist/wirings/queue/index.d.ts +2 -2
  144. package/dist/wirings/queue/index.js +1 -1
  145. package/dist/wirings/queue/queue-runner.d.ts +7 -0
  146. package/dist/wirings/queue/queue-runner.js +17 -6
  147. package/dist/wirings/queue/queue.types.d.ts +8 -2
  148. package/dist/wirings/rpc/index.d.ts +1 -1
  149. package/dist/wirings/rpc/index.js +1 -1
  150. package/dist/wirings/rpc/rpc-runner.d.ts +33 -5
  151. package/dist/wirings/rpc/rpc-runner.js +134 -16
  152. package/dist/wirings/rpc/rpc-types.d.ts +8 -4
  153. package/dist/wirings/scheduler/index.d.ts +3 -3
  154. package/dist/wirings/scheduler/index.js +2 -3
  155. package/dist/wirings/scheduler/scheduler-runner.d.ts +5 -0
  156. package/dist/wirings/scheduler/scheduler-runner.js +23 -5
  157. package/dist/wirings/secret/index.d.ts +3 -0
  158. package/dist/wirings/secret/index.js +2 -0
  159. package/dist/wirings/secret/secret.types.d.ts +27 -0
  160. package/dist/wirings/secret/secret.types.js +1 -0
  161. package/dist/wirings/secret/validate-secret-definitions.d.ts +6 -0
  162. package/dist/wirings/secret/validate-secret-definitions.js +55 -0
  163. package/dist/wirings/trigger/index.d.ts +3 -2
  164. package/dist/wirings/trigger/index.js +2 -2
  165. package/dist/wirings/trigger/pikku-trigger-service.d.ts +26 -0
  166. package/dist/wirings/trigger/pikku-trigger-service.js +75 -0
  167. package/dist/wirings/trigger/trigger-runner.d.ts +21 -8
  168. package/dist/wirings/trigger/trigger-runner.js +46 -18
  169. package/dist/wirings/trigger/trigger.types.d.ts +99 -11
  170. package/dist/wirings/trigger/trigger.types.js +40 -1
  171. package/dist/wirings/variable/index.d.ts +3 -0
  172. package/dist/wirings/variable/index.js +2 -0
  173. package/dist/wirings/variable/validate-variable-definitions.d.ts +6 -0
  174. package/dist/wirings/variable/validate-variable-definitions.js +45 -0
  175. package/dist/wirings/variable/variable.types.d.ts +18 -0
  176. package/dist/wirings/variable/variable.types.js +1 -0
  177. package/dist/wirings/workflow/dsl/index.d.ts +1 -1
  178. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +7 -1
  179. package/dist/wirings/workflow/dsl/workflow-runner.js +3 -2
  180. package/dist/wirings/workflow/graph/graph-node.d.ts +1 -6
  181. package/dist/wirings/workflow/graph/graph-runner.d.ts +5 -31
  182. package/dist/wirings/workflow/graph/graph-runner.js +384 -296
  183. package/dist/wirings/workflow/graph/index.d.ts +4 -3
  184. package/dist/wirings/workflow/graph/index.js +3 -3
  185. package/dist/wirings/workflow/graph/template.d.ts +15 -0
  186. package/dist/wirings/workflow/graph/template.js +27 -0
  187. package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +15 -0
  188. package/dist/wirings/workflow/graph/wire-workflow-graph.js +8 -0
  189. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +0 -13
  190. package/dist/wirings/workflow/index.d.ts +6 -6
  191. package/dist/wirings/workflow/index.js +6 -4
  192. package/dist/wirings/workflow/pikku-workflow-service.d.ts +28 -14
  193. package/dist/wirings/workflow/pikku-workflow-service.js +200 -56
  194. package/dist/wirings/workflow/workflow-helpers.d.ts +36 -0
  195. package/dist/wirings/workflow/workflow-helpers.js +38 -0
  196. package/dist/wirings/workflow/workflow-utils.d.ts +1 -23
  197. package/dist/wirings/workflow/workflow-utils.js +1 -66
  198. package/dist/wirings/workflow/workflow.types.d.ts +40 -93
  199. package/package.json +13 -15
  200. package/run-tests.sh +4 -1
  201. package/src/crypto-utils.ts +99 -0
  202. package/src/errors/errors.ts +17 -0
  203. package/src/errors/index.ts +0 -1
  204. package/src/factory-functions.test.ts +42 -2
  205. package/src/function/function-runner.test.ts +4 -11
  206. package/src/function/function-runner.ts +114 -29
  207. package/src/function/functions.types.ts +62 -132
  208. package/src/function/index.ts +5 -2
  209. package/src/handle-error.ts +2 -10
  210. package/src/index.ts +99 -25
  211. package/src/middleware/auth-apikey.test.ts +15 -12
  212. package/src/middleware/auth-apikey.ts +3 -3
  213. package/src/middleware/auth-bearer.test.ts +17 -14
  214. package/src/middleware/auth-bearer.ts +25 -27
  215. package/src/middleware/auth-cookie.test.ts +16 -13
  216. package/src/middleware/auth-cookie.ts +6 -8
  217. package/src/middleware/cors.ts +96 -0
  218. package/src/middleware/index.ts +5 -3
  219. package/src/middleware/remote-auth.ts +68 -0
  220. package/src/middleware-runner.test.ts +122 -0
  221. package/src/middleware-runner.ts +12 -12
  222. package/src/permissions.test.ts +81 -7
  223. package/src/permissions.ts +15 -17
  224. package/src/pikku-state.ts +20 -8
  225. package/src/run-tests-script.test.ts +49 -0
  226. package/src/schema.ts +18 -15
  227. package/src/services/ai-agent-runner-service.ts +29 -0
  228. package/src/services/ai-run-state-service.ts +20 -0
  229. package/src/services/ai-storage-service.ts +39 -0
  230. package/src/services/deployment-service.ts +22 -0
  231. package/src/services/gopass-secrets.ts +95 -0
  232. package/src/services/in-memory-trigger-service.ts +68 -0
  233. package/src/services/in-memory-workflow-service.ts +395 -0
  234. package/src/services/index.ts +49 -14
  235. package/src/services/local-secrets.ts +51 -0
  236. package/src/services/local-variables.ts +22 -0
  237. package/src/services/scheduler-service.ts +29 -1
  238. package/src/services/schema-service.ts +7 -0
  239. package/src/services/scoped-secret-service.ts +41 -0
  240. package/src/services/secret-service.ts +25 -4
  241. package/src/services/trigger-service.ts +38 -0
  242. package/src/services/typed-secret-service.ts +70 -0
  243. package/src/services/typed-variables-service.ts +81 -0
  244. package/src/services/user-session-service.ts +23 -0
  245. package/src/services/variables-service.ts +5 -0
  246. package/src/services/workflow-service.ts +22 -2
  247. package/src/types/core.types.ts +76 -11
  248. package/src/types/state.types.ts +37 -63
  249. package/src/utils.ts +20 -6
  250. package/src/version.test.ts +80 -0
  251. package/src/version.ts +25 -0
  252. package/src/wirings/ai-agent/ai-agent-memory.ts +300 -0
  253. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  254. package/src/wirings/ai-agent/ai-agent-prepare.ts +470 -0
  255. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  256. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  257. package/src/wirings/ai-agent/ai-agent-runner.test.ts +75 -0
  258. package/src/wirings/ai-agent/ai-agent-runner.ts +137 -0
  259. package/src/wirings/ai-agent/ai-agent-stream.test.ts +246 -0
  260. package/src/wirings/ai-agent/ai-agent-stream.ts +774 -0
  261. package/src/wirings/ai-agent/ai-agent.types.ts +273 -0
  262. package/src/wirings/ai-agent/index.ts +28 -0
  263. package/src/wirings/channel/channel-common.ts +32 -19
  264. package/src/wirings/channel/channel-handler.ts +17 -4
  265. package/src/wirings/channel/channel-middleware-runner.ts +119 -0
  266. package/src/wirings/channel/channel-runner.ts +9 -10
  267. package/src/wirings/channel/channel.types.ts +17 -1
  268. package/src/wirings/channel/define-channel-routes.ts +25 -0
  269. package/src/wirings/channel/index.ts +23 -8
  270. package/src/wirings/channel/local/index.ts +3 -3
  271. package/src/wirings/channel/local/local-channel-runner.test.ts +44 -1
  272. package/src/wirings/channel/local/local-channel-runner.ts +22 -6
  273. package/src/wirings/channel/serverless/index.ts +5 -1
  274. package/src/wirings/channel/serverless/serverless-channel-runner.ts +20 -4
  275. package/src/wirings/cli/channel/index.ts +1 -1
  276. package/src/wirings/cli/cli-runner.test.ts +9 -9
  277. package/src/wirings/cli/cli-runner.ts +17 -10
  278. package/src/wirings/cli/cli.types.ts +11 -11
  279. package/src/wirings/cli/command-parser.test.ts +8 -8
  280. package/src/wirings/cli/define-cli-commands.ts +24 -0
  281. package/src/wirings/cli/index.ts +10 -4
  282. package/src/wirings/http/http-routes.test.ts +322 -0
  283. package/src/wirings/http/http-routes.ts +198 -0
  284. package/src/wirings/http/http-runner.test.ts +3 -3
  285. package/src/wirings/http/http-runner.ts +63 -29
  286. package/src/wirings/http/http.types.ts +121 -37
  287. package/src/wirings/http/index.ts +16 -4
  288. package/src/wirings/mcp/index.ts +27 -3
  289. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +3 -3
  290. package/src/wirings/mcp/mcp-runner.ts +39 -50
  291. package/src/wirings/mcp/mcp.types.ts +3 -3
  292. package/src/wirings/node/index.ts +2 -0
  293. package/src/wirings/node/node.types.ts +23 -0
  294. package/src/wirings/oauth2/index.ts +3 -0
  295. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  296. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  297. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  298. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  299. package/src/wirings/queue/index.ts +14 -2
  300. package/src/wirings/queue/queue-runner.test.ts +97 -55
  301. package/src/wirings/queue/queue-runner.ts +33 -7
  302. package/src/wirings/queue/queue.types.ts +12 -2
  303. package/src/wirings/rpc/index.ts +1 -1
  304. package/src/wirings/rpc/rpc-runner.ts +187 -17
  305. package/src/wirings/rpc/rpc-types.ts +13 -4
  306. package/src/wirings/scheduler/index.ts +7 -4
  307. package/src/wirings/scheduler/scheduler-runner.test.ts +70 -26
  308. package/src/wirings/scheduler/scheduler-runner.ts +45 -5
  309. package/src/wirings/secret/index.ts +9 -0
  310. package/src/wirings/secret/secret.types.ts +32 -0
  311. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  312. package/src/wirings/trigger/index.ts +10 -2
  313. package/src/wirings/trigger/pikku-trigger-service.ts +134 -0
  314. package/src/wirings/trigger/trigger-runner.ts +80 -38
  315. package/src/wirings/trigger/trigger.types.ts +144 -22
  316. package/src/wirings/variable/index.ts +8 -0
  317. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  318. package/src/wirings/variable/variable.types.ts +22 -0
  319. package/src/wirings/workflow/dsl/index.ts +1 -0
  320. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +9 -1
  321. package/src/wirings/workflow/dsl/workflow-runner.ts +4 -3
  322. package/src/wirings/workflow/graph/graph-node.ts +1 -6
  323. package/src/wirings/workflow/graph/graph-runner.test.ts +495 -0
  324. package/src/wirings/workflow/graph/graph-runner.ts +496 -374
  325. package/src/wirings/workflow/graph/index.ts +14 -3
  326. package/src/wirings/workflow/graph/template.ts +42 -0
  327. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  328. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -22
  329. package/src/wirings/workflow/index.ts +21 -10
  330. package/src/wirings/workflow/pikku-workflow-service.test.ts +212 -0
  331. package/src/wirings/workflow/pikku-workflow-service.ts +285 -66
  332. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  333. package/src/wirings/workflow/workflow-utils.ts +0 -120
  334. package/src/wirings/workflow/workflow.types.ts +45 -90
  335. package/tsconfig.tsbuildinfo +1 -1
  336. package/dist/wirings/forge-node/forge-node.types.d.ts +0 -120
  337. package/dist/wirings/forge-node/forge-node.types.js +0 -38
  338. package/dist/wirings/forge-node/index.d.ts +0 -1
  339. package/dist/wirings/forge-node/index.js +0 -1
  340. package/dist/wirings/workflow/wire-workflow.d.ts +0 -42
  341. package/dist/wirings/workflow/wire-workflow.js +0 -53
  342. package/src/wirings/forge-node/forge-node.types.ts +0 -135
  343. package/src/wirings/forge-node/index.ts +0 -1
  344. package/src/wirings/workflow/wire-workflow.ts +0 -94
@@ -3,7 +3,10 @@ import assert from 'node:assert/strict'
3
3
  import { authAPIKey } from './auth-apikey.js'
4
4
  import { resetPikkuState } from '../pikku-state.js'
5
5
  import { CoreUserSession } from '../types/core.types.js'
6
- import { PikkuSessionService } from '../services/user-session-service.js'
6
+ import {
7
+ PikkuSessionService,
8
+ createMiddlewareSessionWireProps,
9
+ } from '../services/user-session-service.js'
7
10
 
8
11
  beforeEach(() => {
9
12
  resetPikkuState()
@@ -43,7 +46,7 @@ describe('authAPIKey middleware', () => {
43
46
  request: createMockHTTPRequest({ 'x-api-key': 'my-api-key' }),
44
47
  response: createMockHTTPResponse(),
45
48
  },
46
- session: SessionService,
49
+ ...createMiddlewareSessionWireProps(SessionService),
47
50
  } as any,
48
51
  async () => {
49
52
  nextCalled = true
@@ -75,7 +78,7 @@ describe('authAPIKey middleware', () => {
75
78
  request: createMockHTTPRequest({}, { apiKey: 'query-api-key' }),
76
79
  response: createMockHTTPResponse(),
77
80
  },
78
- session: SessionService,
81
+ ...createMiddlewareSessionWireProps(SessionService),
79
82
  } as any,
80
83
  async () => {
81
84
  nextCalled = true
@@ -111,7 +114,7 @@ describe('authAPIKey middleware', () => {
111
114
  ),
112
115
  response: createMockHTTPResponse(),
113
116
  },
114
- session: SessionService,
117
+ ...createMiddlewareSessionWireProps(SessionService),
115
118
  } as any,
116
119
  async () => {
117
120
  nextCalled = true
@@ -143,7 +146,7 @@ describe('authAPIKey middleware', () => {
143
146
  request: createMockHTTPRequest({}, { apiKey: 'query-fallback' }),
144
147
  response: createMockHTTPResponse(),
145
148
  },
146
- session: SessionService,
149
+ ...createMiddlewareSessionWireProps(SessionService),
147
150
  } as any,
148
151
  async () => {
149
152
  nextCalled = true
@@ -171,7 +174,7 @@ describe('authAPIKey middleware', () => {
171
174
  request: createMockHTTPRequest({}),
172
175
  response: createMockHTTPResponse(),
173
176
  },
174
- session: SessionService,
177
+ ...createMiddlewareSessionWireProps(SessionService),
175
178
  } as any,
176
179
  async () => {
177
180
  nextCalled = true
@@ -199,7 +202,7 @@ describe('authAPIKey middleware', () => {
199
202
  request: createMockHTTPRequest({ 'x-api-key': 'invalid-key' }),
200
203
  response: createMockHTTPResponse(),
201
204
  },
202
- session: SessionService,
205
+ ...createMiddlewareSessionWireProps(SessionService),
203
206
  } as any,
204
207
  async () => {
205
208
  nextCalled = true
@@ -234,7 +237,7 @@ describe('authAPIKey middleware', () => {
234
237
  request: createMockHTTPRequest({ 'x-api-key': 'some-key' }),
235
238
  response: createMockHTTPResponse(),
236
239
  },
237
- session: SessionService,
240
+ ...createMiddlewareSessionWireProps(SessionService),
238
241
  } as any,
239
242
  async () => {
240
243
  nextCalled = true
@@ -258,7 +261,7 @@ describe('authAPIKey middleware', () => {
258
261
 
259
262
  await middleware(
260
263
  { jwt: jwtService } as any,
261
- { session: SessionService } as any,
264
+ { ...createMiddlewareSessionWireProps(SessionService) } as any,
262
265
  async () => {
263
266
  nextCalled = true
264
267
  }
@@ -281,7 +284,7 @@ describe('authAPIKey middleware', () => {
281
284
  request: createMockHTTPRequest({ 'x-api-key': 'some-key' }),
282
285
  response: createMockHTTPResponse(),
283
286
  },
284
- session: SessionService,
287
+ ...createMiddlewareSessionWireProps(SessionService),
285
288
  } as any,
286
289
  async () => {
287
290
  nextCalled = true
@@ -313,7 +316,7 @@ describe('authAPIKey middleware', () => {
313
316
  request: createMockHTTPRequest({}, { apiKey: 'query-key' }),
314
317
  response: createMockHTTPResponse(),
315
318
  },
316
- session: SessionService,
319
+ ...createMiddlewareSessionWireProps(SessionService),
317
320
  } as any,
318
321
  async () => {
319
322
  nextCalled = true
@@ -346,7 +349,7 @@ describe('authAPIKey middleware', () => {
346
349
  request: createMockHTTPRequest({ 'x-api-key': 'header-key' }),
347
350
  response: createMockHTTPResponse(),
348
351
  },
349
- session: SessionService,
352
+ ...createMiddlewareSessionWireProps(SessionService),
350
353
  } as any,
351
354
  async () => {
352
355
  nextCalled = true
@@ -21,8 +21,8 @@ export const authAPIKey = pikkuMiddlewareFactory<{
21
21
  source: 'header' | 'query' | 'all'
22
22
  }>(({ source }) =>
23
23
  pikkuMiddleware(
24
- async ({ jwt: jwtService }, { http, session: SessionService }, next) => {
25
- if (!http?.request || !SessionService || SessionService.get()) {
24
+ async ({ jwt: jwtService }, { http, setSession, session }, next) => {
25
+ if (!http?.request || !setSession || session) {
26
26
  return next()
27
27
  }
28
28
 
@@ -37,7 +37,7 @@ export const authAPIKey = pikkuMiddlewareFactory<{
37
37
  if (apiKey && jwtService) {
38
38
  const userSession = await jwtService.decode(apiKey)
39
39
  if (userSession) {
40
- SessionService.setInitial(userSession)
40
+ setSession?.(userSession)
41
41
  }
42
42
  }
43
43
 
@@ -3,7 +3,10 @@ import assert from 'node:assert/strict'
3
3
  import { authBearer } from './auth-bearer.js'
4
4
  import { resetPikkuState } from '../pikku-state.js'
5
5
  import { CoreUserSession } from '../types/core.types.js'
6
- import { PikkuSessionService } from '../services/user-session-service.js'
6
+ import {
7
+ PikkuSessionService,
8
+ createMiddlewareSessionWireProps,
9
+ } from '../services/user-session-service.js'
7
10
  import { InvalidSessionError } from '../errors/errors.js'
8
11
 
9
12
  beforeEach(() => {
@@ -39,7 +42,7 @@ describe('authBearer middleware', () => {
39
42
  await middleware(
40
43
  { jwt: jwtService } as any,
41
44
  {
42
- session: SessionService,
45
+ ...createMiddlewareSessionWireProps(SessionService),
43
46
  http: {
44
47
  request: createMockHTTPRequest({
45
48
  authorization: 'Bearer jwt-token',
@@ -73,7 +76,7 @@ describe('authBearer middleware', () => {
73
76
  await middleware(
74
77
  { jwt: jwtService } as any,
75
78
  {
76
- session: SessionService,
79
+ ...createMiddlewareSessionWireProps(SessionService),
77
80
  http: {
78
81
  request: createMockHTTPRequest({
79
82
  Authorization: 'Bearer jwt-token-2',
@@ -116,7 +119,7 @@ describe('authBearer middleware', () => {
116
119
  await middleware(
117
120
  { jwt: jwtService } as any,
118
121
  {
119
- session: SessionService,
122
+ ...createMiddlewareSessionWireProps(SessionService),
120
123
  http: {
121
124
  request: createMockHTTPRequest({
122
125
  authorization: 'Bearer my-static-token',
@@ -147,7 +150,7 @@ describe('authBearer middleware', () => {
147
150
  await middleware(
148
151
  { jwt: jwtService } as any,
149
152
  {
150
- session: SessionService,
153
+ ...createMiddlewareSessionWireProps(SessionService),
151
154
  http: {
152
155
  request: createMockHTTPRequest({
153
156
  authorization: 'Basic some-token',
@@ -179,7 +182,7 @@ describe('authBearer middleware', () => {
179
182
  await middleware(
180
183
  { jwt: jwtService } as any,
181
184
  {
182
- session: SessionService,
185
+ ...createMiddlewareSessionWireProps(SessionService),
183
186
  http: {
184
187
  request: createMockHTTPRequest({
185
188
  authorization: 'Bearer',
@@ -211,7 +214,7 @@ describe('authBearer middleware', () => {
211
214
  await middleware(
212
215
  { jwt: jwtService } as any,
213
216
  {
214
- session: SessionService,
217
+ ...createMiddlewareSessionWireProps(SessionService),
215
218
  http: {
216
219
  request: createMockHTTPRequest({
217
220
  authorization: 'BearerToken',
@@ -242,7 +245,7 @@ describe('authBearer middleware', () => {
242
245
  await middleware(
243
246
  { jwt: jwtService } as any,
244
247
  {
245
- session: SessionService,
248
+ ...createMiddlewareSessionWireProps(SessionService),
246
249
  http: {
247
250
  request: createMockHTTPRequest({
248
251
  authorization: 'Bearer invalid-token',
@@ -282,7 +285,7 @@ describe('authBearer middleware', () => {
282
285
  await middleware(
283
286
  { jwt: jwtService } as any,
284
287
  {
285
- session: SessionService,
288
+ ...createMiddlewareSessionWireProps(SessionService),
286
289
  http: {
287
290
  request: createMockHTTPRequest({
288
291
  authorization: 'Bearer wrong-token',
@@ -319,7 +322,7 @@ describe('authBearer middleware', () => {
319
322
  await middleware(
320
323
  { jwt: jwtService } as any,
321
324
  {
322
- session: SessionService,
325
+ ...createMiddlewareSessionWireProps(SessionService),
323
326
  http: {
324
327
  request: createMockHTTPRequest({
325
328
  authorization: 'Bearer some-token',
@@ -349,7 +352,7 @@ describe('authBearer middleware', () => {
349
352
 
350
353
  await middleware(
351
354
  { jwt: jwtService } as any,
352
- { session: SessionService } as any,
355
+ { ...createMiddlewareSessionWireProps(SessionService) } as any,
353
356
  async () => {
354
357
  nextCalled = true
355
358
  }
@@ -372,7 +375,7 @@ describe('authBearer middleware', () => {
372
375
  await middleware(
373
376
  { jwt: jwtService } as any,
374
377
  {
375
- session: SessionService,
378
+ ...createMiddlewareSessionWireProps(SessionService),
376
379
  http: {
377
380
  request: createMockHTTPRequest({}),
378
381
  response: createMockHTTPResponse(),
@@ -396,7 +399,7 @@ describe('authBearer middleware', () => {
396
399
  await middleware(
397
400
  { jwt: undefined } as any,
398
401
  {
399
- session: SessionService,
402
+ ...createMiddlewareSessionWireProps(SessionService),
400
403
  http: {
401
404
  request: createMockHTTPRequest({
402
405
  authorization: 'Bearer some-token',
@@ -428,7 +431,7 @@ describe('authBearer middleware', () => {
428
431
  await middleware(
429
432
  { jwt: undefined } as any,
430
433
  {
431
- session: SessionService,
434
+ ...createMiddlewareSessionWireProps(SessionService),
432
435
  http: {
433
436
  request: createMockHTTPRequest({
434
437
  authorization: 'Bearer static-token',
@@ -40,38 +40,36 @@ export const authBearer = pikkuMiddlewareFactory<{
40
40
  userSession: CoreUserSession
41
41
  }
42
42
  }>(({ token } = {}) =>
43
- pikkuMiddleware(async ({ jwt: jwtService }, { http, session }, next) => {
44
- // Skip if session already exists.
45
- if (!http?.request || !session || session.get()) {
46
- return next()
47
- }
43
+ pikkuMiddleware(
44
+ async ({ jwt: jwtService }, { http, setSession, session }, next) => {
45
+ if (!http?.request || !setSession || session) {
46
+ return next()
47
+ }
48
48
 
49
- const authHeader =
50
- http.request.header('authorization') ||
51
- http.request.header('Authorization')
49
+ const authHeader =
50
+ http.request.header('authorization') ||
51
+ http.request.header('Authorization')
52
52
 
53
- if (authHeader) {
54
- const [scheme, bearerToken] = authHeader.split(' ')
55
- if (scheme !== 'Bearer' || !bearerToken) {
56
- throw new InvalidSessionError()
57
- }
53
+ if (authHeader) {
54
+ const [scheme, bearerToken] = authHeader.split(' ')
55
+ if (scheme !== 'Bearer' || !bearerToken) {
56
+ throw new InvalidSessionError()
57
+ }
58
58
 
59
- let userSession: CoreUserSession | null = null
59
+ let userSession: CoreUserSession | null = null
60
60
 
61
- // If static token provided, validate against it
62
- if (token && bearerToken === token.value) {
63
- userSession = token.userSession
64
- }
65
- // Otherwise, default to JWT decoding
66
- else if (jwtService && !token) {
67
- userSession = await jwtService.decode(bearerToken)
68
- }
61
+ if (token && bearerToken === token.value) {
62
+ userSession = token.userSession
63
+ } else if (jwtService && !token) {
64
+ userSession = await jwtService.decode(bearerToken)
65
+ }
69
66
 
70
- if (userSession) {
71
- session.setInitial(userSession)
67
+ if (userSession) {
68
+ setSession?.(userSession)
69
+ }
72
70
  }
73
- }
74
71
 
75
- return next()
76
- })
72
+ return next()
73
+ }
74
+ )
77
75
  )
@@ -3,7 +3,10 @@ import assert from 'node:assert/strict'
3
3
  import { authCookie } from './auth-cookie.js'
4
4
  import { resetPikkuState } from '../pikku-state.js'
5
5
  import { CoreUserSession } from '../types/core.types.js'
6
- import { PikkuSessionService } from '../services/user-session-service.js'
6
+ import {
7
+ PikkuSessionService,
8
+ createMiddlewareSessionWireProps,
9
+ } from '../services/user-session-service.js'
7
10
 
8
11
  beforeEach(() => {
9
12
  resetPikkuState()
@@ -66,7 +69,7 @@ describe('authCookie middleware', () => {
66
69
  logger: createMockLogger(),
67
70
  } as any,
68
71
  {
69
- session: SessionService,
72
+ ...createMiddlewareSessionWireProps(SessionService),
70
73
  http: {
71
74
  request: createMockHTTPRequest({ session: 'cookie-jwt-value' }),
72
75
  response: mockResponse,
@@ -107,7 +110,7 @@ describe('authCookie middleware', () => {
107
110
  logger: createMockLogger(),
108
111
  } as any,
109
112
  {
110
- session: SessionService,
113
+ ...createMiddlewareSessionWireProps(SessionService),
111
114
  http: {
112
115
  request: createMockHTTPRequest({}),
113
116
  response: mockResponse,
@@ -151,7 +154,7 @@ describe('authCookie middleware', () => {
151
154
  logger: createMockLogger(),
152
155
  } as any,
153
156
  {
154
- session: SessionService,
157
+ ...createMiddlewareSessionWireProps(SessionService),
155
158
  http: {
156
159
  request: createMockHTTPRequest({ session: 'existing-jwt' }),
157
160
  response: mockResponse,
@@ -192,7 +195,7 @@ describe('authCookie middleware', () => {
192
195
  logger: createMockLogger(),
193
196
  } as any,
194
197
  {
195
- session: SessionService,
198
+ ...createMiddlewareSessionWireProps(SessionService),
196
199
  http: {
197
200
  request: createMockHTTPRequest({}),
198
201
  response: mockResponse,
@@ -230,7 +233,7 @@ describe('authCookie middleware', () => {
230
233
  logger: createMockLogger(),
231
234
  } as any,
232
235
  {
233
- session: SessionService,
236
+ ...createMiddlewareSessionWireProps(SessionService),
234
237
  http: {
235
238
  request: createMockHTTPRequest({ session: 'invalid-jwt' }),
236
239
  response: mockResponse,
@@ -274,7 +277,7 @@ describe('authCookie middleware', () => {
274
277
  logger: createMockLogger(),
275
278
  } as any,
276
279
  {
277
- session: SessionService,
280
+ ...createMiddlewareSessionWireProps(SessionService),
278
281
  http: {
279
282
  request: createMockHTTPRequest({ session: 'some-jwt' }),
280
283
  response: mockResponse,
@@ -309,7 +312,7 @@ describe('authCookie middleware', () => {
309
312
  jwt: jwtService,
310
313
  logger: createMockLogger(),
311
314
  } as any,
312
- { session: SessionService } as any,
315
+ { ...createMiddlewareSessionWireProps(SessionService) } as any,
313
316
  async () => {
314
317
  nextCalled = true
315
318
  }
@@ -337,7 +340,7 @@ describe('authCookie middleware', () => {
337
340
  logger: createMockLogger(),
338
341
  } as any,
339
342
  {
340
- session: SessionService,
343
+ ...createMiddlewareSessionWireProps(SessionService),
341
344
  http: {
342
345
  request: createMockHTTPRequest({ session: 'some-jwt' }),
343
346
  response: mockResponse,
@@ -371,7 +374,7 @@ describe('authCookie middleware', () => {
371
374
  logger: mockLogger,
372
375
  } as any,
373
376
  {
374
- session: SessionService,
377
+ ...createMiddlewareSessionWireProps(SessionService),
375
378
  http: {
376
379
  request: createMockHTTPRequest({}),
377
380
  response: mockResponse,
@@ -415,7 +418,7 @@ describe('authCookie middleware', () => {
415
418
  logger: createMockLogger(),
416
419
  } as any,
417
420
  {
418
- session: SessionService,
421
+ ...createMiddlewareSessionWireProps(SessionService),
419
422
  http: {
420
423
  request: createMockHTTPRequest({}),
421
424
  response: undefined,
@@ -456,7 +459,7 @@ describe('authCookie middleware', () => {
456
459
  logger: createMockLogger(),
457
460
  } as any,
458
461
  {
459
- session: SessionService,
462
+ ...createMiddlewareSessionWireProps(SessionService),
460
463
  http: {
461
464
  request: createMockHTTPRequest({
462
465
  my_custom_cookie: 'custom-cookie-value',
@@ -498,7 +501,7 @@ describe('authCookie middleware', () => {
498
501
  logger: createMockLogger(),
499
502
  } as any,
500
503
  {
501
- session: SessionService,
504
+ ...createMiddlewareSessionWireProps(SessionService),
502
505
  http: {
503
506
  request: createMockHTTPRequest({}),
504
507
  response: mockResponse,
@@ -41,35 +41,33 @@ export const authCookie = pikkuMiddlewareFactory<{
41
41
  pikkuMiddleware(
42
42
  async (
43
43
  { jwt: jwtService, logger },
44
- { http, session: SessionService },
44
+ { http, setSession, getSession, session, hasSessionChanged },
45
45
  next
46
46
  ) => {
47
- if (!http?.request || !SessionService || SessionService.get()) {
47
+ if (!http?.request || !setSession || session) {
48
48
  return next()
49
49
  }
50
50
 
51
- // Try to decode session from cookie
52
51
  const cookieValue = http.request.cookie(name)
53
52
  if (cookieValue && jwtService) {
54
53
  const userSession = await jwtService.decode(cookieValue)
55
54
  if (userSession) {
56
- SessionService.setInitial(userSession)
55
+ setSession?.(userSession)
57
56
  }
58
57
  }
59
58
 
60
59
  await next()
61
60
 
62
- // Set the cookie in the response if the session has changed
63
61
  if (!http?.response) {
64
62
  return
65
63
  }
66
64
 
67
- if (SessionService.sessionChanged) {
68
- const session = SessionService.get()
65
+ if (hasSessionChanged?.()) {
66
+ const currentSession = await getSession?.()
69
67
  if (jwtService) {
70
68
  http.response.cookie(
71
69
  name,
72
- await jwtService.encode(expiresIn, session),
70
+ await jwtService.encode(expiresIn, currentSession),
73
71
  {
74
72
  ...options,
75
73
  expires: getRelativeTimeOffsetFromNow(expiresIn),
@@ -0,0 +1,96 @@
1
+ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
2
+
3
+ /**
4
+ * CORS middleware that handles cross-origin requests including OPTIONS preflight.
5
+ *
6
+ * Sets appropriate CORS headers on all responses and short-circuits OPTIONS
7
+ * preflight requests with a 204 No Content response.
8
+ *
9
+ * @param options.origin - Allowed origin(s). Use `'*'` for any origin, a string for a single origin, or an array for multiple origins. Defaults to `'*'`.
10
+ * @param options.methods - Allowed HTTP methods. Defaults to common methods.
11
+ * @param options.headers - Allowed request headers. Defaults to common headers.
12
+ * @param options.credentials - Whether to allow credentials. Defaults to `false`.
13
+ * @param options.maxAge - Preflight cache duration in seconds. Defaults to `86400` (24 hours).
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { cors } from '@pikku/core/middleware'
18
+ *
19
+ * // Allow all origins
20
+ * addHTTPMiddleware('*', [cors()])
21
+ *
22
+ * // Specific origin with credentials
23
+ * addHTTPMiddleware('*', [
24
+ * cors({
25
+ * origin: 'https://app.example.com',
26
+ * credentials: true,
27
+ * })
28
+ * ])
29
+ *
30
+ * // Multiple origins
31
+ * addHTTPMiddleware('*', [
32
+ * cors({
33
+ * origin: ['https://app.example.com', 'https://admin.example.com'],
34
+ * })
35
+ * ])
36
+ * ```
37
+ */
38
+ export const cors = pikkuMiddlewareFactory<{
39
+ origin?: string | string[]
40
+ methods?: string[]
41
+ headers?: string[]
42
+ credentials?: boolean
43
+ maxAge?: number
44
+ }>(
45
+ ({
46
+ origin = '*',
47
+ methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
48
+ headers = ['Content-Type', 'Authorization', 'x-api-key'],
49
+ credentials = false,
50
+ maxAge = 86400,
51
+ } = {}) =>
52
+ pikkuMiddleware({
53
+ name: 'CORS',
54
+ description: 'Handles cross-origin requests including OPTIONS preflight',
55
+ func: async (_services, wires, next) => {
56
+ const request = wires.http?.request
57
+ const response = wires.http?.response
58
+
59
+ if (!request || !response) {
60
+ return next()
61
+ }
62
+
63
+ const requestOrigin = request.header('origin')
64
+
65
+ let allowedOrigin: string
66
+ if (Array.isArray(origin)) {
67
+ allowedOrigin =
68
+ requestOrigin && origin.includes(requestOrigin)
69
+ ? requestOrigin
70
+ : origin[0]
71
+ } else {
72
+ allowedOrigin = origin
73
+ }
74
+
75
+ response.header('Access-Control-Allow-Origin', allowedOrigin)
76
+ response.header('Access-Control-Allow-Methods', methods.join(', '))
77
+ response.header('Access-Control-Allow-Headers', headers.join(', '))
78
+
79
+ if (credentials) {
80
+ response.header('Access-Control-Allow-Credentials', 'true')
81
+ }
82
+
83
+ if (Array.isArray(origin)) {
84
+ response.header('Vary', 'Origin')
85
+ }
86
+
87
+ if (request.method() === 'options') {
88
+ response.header('Access-Control-Max-Age', String(maxAge))
89
+ response.status(204).json(undefined as any)
90
+ return
91
+ }
92
+
93
+ return next()
94
+ },
95
+ })
96
+ )
@@ -1,3 +1,5 @@
1
- export * from './auth-apikey.js'
2
- export * from './auth-cookie.js'
3
- export * from './auth-bearer.js'
1
+ export { authAPIKey } from './auth-apikey.js'
2
+ export { authCookie } from './auth-cookie.js'
3
+ export { authBearer } from './auth-bearer.js'
4
+ export { pikkuRemoteAuthMiddleware } from './remote-auth.js'
5
+ export { cors } from './cors.js'
@@ -0,0 +1,68 @@
1
+ import { UnauthorizedError } from '../errors/errors.js'
2
+ import { pikkuMiddleware } from '../types/core.types.js'
3
+ import { decryptJSON } from '../crypto-utils.js'
4
+
5
+ export const pikkuRemoteAuthMiddleware = pikkuMiddleware(
6
+ async ({ secrets, jwt }, { http, setSession }, next) => {
7
+ if (!http?.request || !secrets) {
8
+ return next()
9
+ }
10
+
11
+ let secret: string
12
+ try {
13
+ secret = await secrets.getSecret('PIKKU_REMOTE_SECRET')
14
+ } catch {
15
+ return next()
16
+ }
17
+ if (!jwt) {
18
+ throw new Error('PIKKU_REMOTE_SECRET set but JWT service missing')
19
+ }
20
+
21
+ const authHeader =
22
+ http.request.header('authorization') ||
23
+ http.request.header('Authorization')
24
+
25
+ if (!authHeader) {
26
+ throw new UnauthorizedError()
27
+ }
28
+
29
+ const [scheme, token] = authHeader.split(' ')
30
+ if (scheme !== 'Bearer' || !token) {
31
+ throw new UnauthorizedError()
32
+ }
33
+
34
+ let payload: any
35
+ try {
36
+ payload = await jwt.decode(token)
37
+ } catch {
38
+ throw new UnauthorizedError()
39
+ }
40
+
41
+ if (payload?.aud !== 'pikku-remote') {
42
+ throw new UnauthorizedError()
43
+ }
44
+
45
+ if (payload?.fn && http.request.path().startsWith('/rpc/')) {
46
+ const fn = decodeURIComponent(http.request.path().slice('/rpc/'.length))
47
+ if (fn && payload.fn !== fn) {
48
+ throw new UnauthorizedError()
49
+ }
50
+ }
51
+
52
+ if (payload?.session) {
53
+ try {
54
+ const decrypted = await decryptJSON<{ session?: unknown }>(
55
+ secret,
56
+ payload.session
57
+ )
58
+ if (decrypted?.session && setSession) {
59
+ await setSession(decrypted.session)
60
+ }
61
+ } catch {
62
+ throw new UnauthorizedError()
63
+ }
64
+ }
65
+
66
+ return next()
67
+ }
68
+ )