@pikku/core 0.12.0 → 0.12.1

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 (262) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/errors/errors.d.ts +6 -0
  3. package/dist/errors/errors.js +10 -0
  4. package/dist/errors/index.d.ts +1 -0
  5. package/dist/errors/index.js +1 -0
  6. package/dist/function/function-runner.d.ts +3 -3
  7. package/dist/function/function-runner.js +66 -44
  8. package/dist/function/functions.types.d.ts +1 -0
  9. package/dist/handle-error.d.ts +2 -2
  10. package/dist/handle-error.js +2 -2
  11. package/dist/index.d.ts +5 -3
  12. package/dist/index.js +2 -1
  13. package/dist/internal.d.ts +3 -0
  14. package/dist/internal.js +2 -0
  15. package/dist/middleware/auth-bearer.d.ts +1 -1
  16. package/dist/middleware/auth-bearer.js +1 -1
  17. package/dist/middleware/auth-cookie.d.ts +2 -2
  18. package/dist/middleware/auth-cookie.js +1 -1
  19. package/dist/middleware/cors.d.ts +2 -2
  20. package/dist/middleware/cors.js +45 -37
  21. package/dist/middleware-runner.d.ts +1 -1
  22. package/dist/permissions.d.ts +2 -2
  23. package/dist/pikku-state.d.ts +9 -8
  24. package/dist/pikku-state.js +30 -17
  25. package/dist/schema.d.ts +4 -4
  26. package/dist/schema.js +9 -5
  27. package/dist/services/ai-agent-runner-service.d.ts +22 -3
  28. package/dist/services/ai-run-state-service.d.ts +1 -1
  29. package/dist/services/ai-storage-service.d.ts +1 -1
  30. package/dist/services/content-service.d.ts +6 -0
  31. package/dist/services/gopass-secrets.d.ts +1 -1
  32. package/dist/services/gopass-secrets.js +3 -0
  33. package/dist/services/in-memory-ai-run-state-service.d.ts +15 -0
  34. package/dist/services/in-memory-ai-run-state-service.js +44 -0
  35. package/dist/services/in-memory-trigger-service.d.ts +0 -1
  36. package/dist/services/in-memory-trigger-service.js +6 -8
  37. package/dist/services/index.d.ts +3 -2
  38. package/dist/services/index.js +1 -0
  39. package/dist/services/local-content.d.ts +2 -1
  40. package/dist/services/local-content.js +6 -1
  41. package/dist/services/local-secrets.d.ts +2 -2
  42. package/dist/services/local-variables.d.ts +1 -1
  43. package/dist/services/logger-console.d.ts +2 -1
  44. package/dist/services/scheduler-service.d.ts +0 -12
  45. package/dist/services/scheduler-service.js +0 -6
  46. package/dist/services/scoped-secret-service.d.ts +1 -1
  47. package/dist/services/trigger-service.d.ts +0 -7
  48. package/dist/services/user-session-service.d.ts +3 -3
  49. package/dist/services/workflow-service.d.ts +2 -3
  50. package/dist/types/core.types.d.ts +24 -18
  51. package/dist/types/state.types.d.ts +17 -15
  52. package/dist/utils.d.ts +5 -5
  53. package/dist/utils.js +12 -9
  54. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +5 -0
  55. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +146 -0
  56. package/dist/wirings/ai-agent/ai-agent-helpers.d.ts +28 -0
  57. package/dist/wirings/ai-agent/ai-agent-helpers.js +40 -0
  58. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +4 -5
  59. package/dist/wirings/ai-agent/ai-agent-prepare.js +66 -30
  60. package/dist/wirings/ai-agent/ai-agent-registry.d.ts +1 -1
  61. package/dist/wirings/ai-agent/ai-agent-runner.js +107 -8
  62. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +2 -1
  63. package/dist/wirings/ai-agent/ai-agent-stream.js +246 -87
  64. package/dist/wirings/ai-agent/ai-agent.types.d.ts +61 -2
  65. package/dist/wirings/ai-agent/index.d.ts +3 -1
  66. package/dist/wirings/ai-agent/index.js +2 -0
  67. package/dist/wirings/channel/channel-common.d.ts +2 -2
  68. package/dist/wirings/channel/channel-handler.d.ts +3 -3
  69. package/dist/wirings/channel/channel-handler.js +4 -4
  70. package/dist/wirings/channel/channel-runner.d.ts +5 -5
  71. package/dist/wirings/channel/channel-runner.js +3 -2
  72. package/dist/wirings/channel/channel-store.d.ts +1 -1
  73. package/dist/wirings/channel/channel.types.d.ts +4 -6
  74. package/dist/wirings/channel/local/local-channel-runner.d.ts +7 -2
  75. package/dist/wirings/channel/local/local-channel-runner.js +4 -3
  76. package/dist/wirings/channel/local/local-eventhub-service.d.ts +2 -2
  77. package/dist/wirings/channel/log-channels.d.ts +1 -1
  78. package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +1 -1
  79. package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +4 -4
  80. package/dist/wirings/channel/serverless/serverless-channel-runner.js +25 -19
  81. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +1 -1
  82. package/dist/wirings/cli/cli-runner.d.ts +2 -2
  83. package/dist/wirings/cli/cli.types.d.ts +3 -3
  84. package/dist/wirings/cli/command-parser.d.ts +1 -1
  85. package/dist/wirings/http/http-runner.d.ts +9 -9
  86. package/dist/wirings/http/http-runner.js +33 -14
  87. package/dist/wirings/http/http.types.d.ts +2 -6
  88. package/dist/wirings/http/log-http-routes.d.ts +1 -1
  89. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -1
  90. package/dist/wirings/http/pikku-fetch-http-response.d.ts +2 -2
  91. package/dist/wirings/http/pikku-fetch-http-response.js +6 -1
  92. package/dist/wirings/http/routers/http-router.d.ts +1 -1
  93. package/dist/wirings/http/routers/path-to-regex.d.ts +2 -2
  94. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  95. package/dist/wirings/mcp/mcp-runner.d.ts +0 -3
  96. package/dist/wirings/mcp/mcp-runner.js +4 -2
  97. package/dist/wirings/mcp/mcp.types.d.ts +2 -2
  98. package/dist/wirings/oauth2/oauth2-client.d.ts +3 -3
  99. package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +1 -1
  100. package/dist/wirings/queue/index.d.ts +1 -1
  101. package/dist/wirings/queue/index.js +1 -1
  102. package/dist/wirings/queue/queue-runner.d.ts +1 -12
  103. package/dist/wirings/queue/queue-runner.js +4 -12
  104. package/dist/wirings/queue/queue.types.d.ts +3 -9
  105. package/dist/wirings/queue/register-queue-helper.d.ts +2 -2
  106. package/dist/wirings/queue/register-queue-helper.js +1 -1
  107. package/dist/wirings/queue/validate-worker-config.d.ts +1 -1
  108. package/dist/wirings/rpc/index.d.ts +2 -0
  109. package/dist/wirings/rpc/index.js +1 -0
  110. package/dist/wirings/rpc/rpc-runner.d.ts +36 -14
  111. package/dist/wirings/rpc/rpc-runner.js +56 -28
  112. package/dist/wirings/rpc/rpc-types.d.ts +14 -2
  113. package/dist/wirings/rpc/wire-addon.d.ts +10 -0
  114. package/dist/wirings/rpc/wire-addon.js +9 -0
  115. package/dist/wirings/scheduler/index.d.ts +1 -1
  116. package/dist/wirings/scheduler/index.js +1 -1
  117. package/dist/wirings/scheduler/log-schedulers.d.ts +1 -1
  118. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -10
  119. package/dist/wirings/scheduler/scheduler-runner.js +4 -25
  120. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
  121. package/dist/wirings/trigger/pikku-trigger-service.d.ts +2 -6
  122. package/dist/wirings/trigger/pikku-trigger-service.js +11 -16
  123. package/dist/wirings/trigger/trigger-runner.d.ts +1 -5
  124. package/dist/wirings/trigger/trigger-runner.js +4 -3
  125. package/dist/wirings/workflow/pikku-workflow-service.d.ts +2 -6
  126. package/dist/wirings/workflow/pikku-workflow-service.js +20 -29
  127. package/dist/wirings/workflow/workflow.types.d.ts +2 -2
  128. package/package.json +2 -1
  129. package/src/crypto-utils.test.ts +116 -0
  130. package/src/errors/error.test.ts +143 -2
  131. package/src/errors/errors.ts +10 -0
  132. package/src/errors/index.ts +1 -0
  133. package/src/function/function-runner.test.ts +2 -2
  134. package/src/function/function-runner.ts +72 -49
  135. package/src/function/functions.types.ts +1 -0
  136. package/src/handle-error.test.ts +361 -0
  137. package/src/handle-error.ts +4 -4
  138. package/src/index.ts +3 -10
  139. package/src/internal.ts +6 -0
  140. package/src/middleware/auth-apikey.test.ts +1 -1
  141. package/src/middleware/auth-bearer.test.ts +1 -1
  142. package/src/middleware/auth-bearer.ts +2 -5
  143. package/src/middleware/auth-cookie.test.ts +1 -1
  144. package/src/middleware/auth-cookie.ts +3 -5
  145. package/src/middleware/cors.test.ts +424 -0
  146. package/src/middleware/cors.ts +14 -6
  147. package/src/middleware/remote-auth.test.ts +488 -0
  148. package/src/middleware-runner.test.ts +1 -1
  149. package/src/middleware-runner.ts +1 -1
  150. package/src/permissions.test.ts +2 -2
  151. package/src/permissions.ts +2 -2
  152. package/src/pikku-state.test.ts +224 -0
  153. package/src/pikku-state.ts +40 -28
  154. package/src/schema.test.ts +198 -6
  155. package/src/schema.ts +11 -7
  156. package/src/services/ai-agent-runner-service.ts +15 -3
  157. package/src/services/ai-run-state-service.ts +1 -1
  158. package/src/services/ai-storage-service.ts +1 -1
  159. package/src/services/content-service.ts +7 -0
  160. package/src/services/gopass-secrets.ts +4 -1
  161. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  162. package/src/services/in-memory-trigger-service.ts +6 -10
  163. package/src/services/in-memory-workflow-service.test.ts +351 -0
  164. package/src/services/index.ts +2 -1
  165. package/src/services/local-content.ts +9 -3
  166. package/src/services/local-secrets.test.ts +80 -0
  167. package/src/services/local-secrets.ts +2 -2
  168. package/src/services/local-variables.test.ts +61 -0
  169. package/src/services/local-variables.ts +1 -1
  170. package/src/services/logger-console.test.ts +118 -0
  171. package/src/services/logger-console.ts +2 -1
  172. package/src/services/scheduler-service.ts +0 -18
  173. package/src/services/scoped-secret-service.test.ts +74 -0
  174. package/src/services/scoped-secret-service.ts +1 -1
  175. package/src/services/trigger-service.ts +0 -21
  176. package/src/services/typed-secret-service.test.ts +93 -0
  177. package/src/services/typed-variables-service.test.ts +73 -0
  178. package/src/services/user-session-service.test.ts +113 -0
  179. package/src/services/user-session-service.ts +3 -3
  180. package/src/services/workflow-service.ts +2 -12
  181. package/src/time-utils.test.ts +1 -1
  182. package/src/types/core.types.ts +25 -19
  183. package/src/types/state.types.ts +24 -15
  184. package/src/utils.test.ts +350 -0
  185. package/src/utils.ts +14 -13
  186. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +363 -0
  187. package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +214 -0
  188. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  189. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  190. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  191. package/src/wirings/ai-agent/ai-agent-prepare.ts +79 -45
  192. package/src/wirings/ai-agent/ai-agent-registry.ts +1 -1
  193. package/src/wirings/ai-agent/ai-agent-runner.test.ts +245 -3
  194. package/src/wirings/ai-agent/ai-agent-runner.ts +121 -7
  195. package/src/wirings/ai-agent/ai-agent-stream.test.ts +430 -24
  196. package/src/wirings/ai-agent/ai-agent-stream.ts +377 -102
  197. package/src/wirings/ai-agent/ai-agent.types.ts +57 -2
  198. package/src/wirings/ai-agent/index.ts +11 -0
  199. package/src/wirings/channel/channel-common.ts +2 -2
  200. package/src/wirings/channel/channel-handler.ts +13 -9
  201. package/src/wirings/channel/channel-runner.ts +6 -6
  202. package/src/wirings/channel/channel-store.ts +1 -1
  203. package/src/wirings/channel/channel.types.ts +4 -8
  204. package/src/wirings/channel/local/local-channel-runner.test.ts +19 -12
  205. package/src/wirings/channel/local/local-channel-runner.ts +18 -13
  206. package/src/wirings/channel/local/local-eventhub-service.test.ts +2 -2
  207. package/src/wirings/channel/local/local-eventhub-service.ts +2 -2
  208. package/src/wirings/channel/log-channels.ts +1 -1
  209. package/src/wirings/channel/pikku-abstract-channel-handler.ts +1 -1
  210. package/src/wirings/channel/serverless/serverless-channel-runner.ts +37 -32
  211. package/src/wirings/cli/channel/cli-channel-runner.ts +1 -1
  212. package/src/wirings/cli/cli-runner.test.ts +1 -1
  213. package/src/wirings/cli/cli-runner.ts +11 -4
  214. package/src/wirings/cli/cli.types.ts +3 -3
  215. package/src/wirings/cli/command-parser.test.ts +1 -1
  216. package/src/wirings/cli/command-parser.ts +1 -1
  217. package/src/wirings/http/http-routes.test.ts +1 -1
  218. package/src/wirings/http/http-runner.test.ts +9 -17
  219. package/src/wirings/http/http-runner.ts +43 -27
  220. package/src/wirings/http/http.types.ts +1 -14
  221. package/src/wirings/http/log-http-routes.ts +1 -1
  222. package/src/wirings/http/pikku-fetch-http-request.ts +1 -1
  223. package/src/wirings/http/pikku-fetch-http-response.ts +12 -5
  224. package/src/wirings/http/routers/http-router.ts +1 -1
  225. package/src/wirings/http/routers/path-to-regex.test.ts +1 -1
  226. package/src/wirings/http/routers/path-to-regex.ts +4 -3
  227. package/src/wirings/mcp/mcp-endpoint-registry.ts +5 -1
  228. package/src/wirings/mcp/mcp-runner.ts +9 -15
  229. package/src/wirings/mcp/mcp.types.ts +2 -2
  230. package/src/wirings/oauth2/oauth2-client.ts +3 -3
  231. package/src/wirings/oauth2/wire-oauth2-credential.ts +1 -1
  232. package/src/wirings/queue/index.ts +0 -1
  233. package/src/wirings/queue/queue-runner.test.ts +52 -25
  234. package/src/wirings/queue/queue-runner.ts +8 -30
  235. package/src/wirings/queue/queue.types.ts +3 -13
  236. package/src/wirings/queue/register-queue-helper.ts +3 -5
  237. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  238. package/src/wirings/queue/validate-worker-config.ts +4 -1
  239. package/src/wirings/rpc/index.ts +2 -0
  240. package/src/wirings/rpc/rpc-runner.ts +92 -49
  241. package/src/wirings/rpc/rpc-types.ts +17 -2
  242. package/src/wirings/rpc/wire-addon.ts +20 -0
  243. package/src/wirings/scheduler/index.ts +0 -1
  244. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  245. package/src/wirings/scheduler/scheduler-runner.test.ts +49 -60
  246. package/src/wirings/scheduler/scheduler-runner.ts +9 -56
  247. package/src/wirings/scheduler/scheduler.types.ts +2 -2
  248. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  249. package/src/wirings/trigger/pikku-trigger-service.ts +17 -39
  250. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  251. package/src/wirings/trigger/trigger-runner.ts +8 -11
  252. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  253. package/src/wirings/workflow/graph/graph-runner.test.ts +14 -22
  254. package/src/wirings/workflow/graph/template.test.ts +49 -0
  255. package/src/wirings/workflow/pikku-workflow-service.test.ts +15 -27
  256. package/src/wirings/workflow/pikku-workflow-service.ts +29 -45
  257. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  258. package/src/wirings/workflow/workflow.types.ts +2 -2
  259. package/tsconfig.tsbuildinfo +1 -1
  260. package/dist/wirings/workflow/workflow-utils.d.ts +0 -1
  261. package/dist/wirings/workflow/workflow-utils.js +0 -1
  262. package/src/wirings/workflow/workflow-utils.ts +0 -0
@@ -0,0 +1,224 @@
1
+ import { describe, test, beforeEach } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import {
4
+ pikkuState,
5
+ resetPikkuState,
6
+ initializePikkuState,
7
+ getAllPackageStates,
8
+ getSingletonServices,
9
+ getCreateWireServices,
10
+ getPikkuMetaDir,
11
+ addPackageServiceFactories,
12
+ } from './pikku-state.js'
13
+
14
+ beforeEach(() => {
15
+ resetPikkuState()
16
+ })
17
+
18
+ describe('pikkuState', () => {
19
+ test('should get state for main package with null name', () => {
20
+ const functions = pikkuState(null, 'function', 'functions')
21
+ assert.ok(functions instanceof Map)
22
+ })
23
+
24
+ test('should get state for main package with __main__ name', () => {
25
+ const functions = pikkuState('__main__', 'function', 'functions')
26
+ assert.ok(functions instanceof Map)
27
+ })
28
+
29
+ test('should set and get state values', () => {
30
+ const testMeta = { key: 'value' }
31
+ pikkuState(null, 'function', 'meta', testMeta as any)
32
+ const result = pikkuState(null, 'function', 'meta')
33
+ assert.deepStrictEqual(result, testMeta)
34
+ })
35
+
36
+ test('should auto-initialize package state on first access', () => {
37
+ const states = getAllPackageStates()
38
+ assert.ok(!states.has('@test/package'))
39
+
40
+ pikkuState('@test/package', 'function', 'functions')
41
+
42
+ assert.ok(states.has('@test/package'))
43
+ })
44
+
45
+ test('should isolate state between packages', () => {
46
+ pikkuState(null, 'function', 'meta', { main: true } as any)
47
+ pikkuState('@addon/pkg', 'function', 'meta', { addon: true } as any)
48
+
49
+ assert.deepStrictEqual(pikkuState(null, 'function', 'meta'), { main: true })
50
+ assert.deepStrictEqual(pikkuState('@addon/pkg', 'function', 'meta'), {
51
+ addon: true,
52
+ })
53
+ })
54
+
55
+ test('should return correct state types', () => {
56
+ const httpRoutes = pikkuState(null, 'http', 'routes')
57
+ assert.ok(httpRoutes instanceof Map)
58
+
59
+ const schemas = pikkuState(null, 'misc', 'schemas')
60
+ assert.ok(schemas instanceof Map)
61
+
62
+ const errors = pikkuState(null, 'misc', 'errors')
63
+ assert.ok(errors instanceof Map)
64
+ })
65
+ })
66
+
67
+ describe('getAllPackageStates', () => {
68
+ test('should return a Map', () => {
69
+ const states = getAllPackageStates()
70
+ assert.ok(states instanceof Map)
71
+ })
72
+
73
+ test('should contain __main__ after reset', () => {
74
+ const states = getAllPackageStates()
75
+ assert.ok(states.has('__main__'))
76
+ })
77
+
78
+ test('should return same reference on multiple calls', () => {
79
+ const states1 = getAllPackageStates()
80
+ const states2 = getAllPackageStates()
81
+ assert.strictEqual(states1, states2)
82
+ })
83
+ })
84
+
85
+ describe('initializePikkuState', () => {
86
+ test('should create a new package state', () => {
87
+ const states = getAllPackageStates()
88
+ assert.ok(!states.has('@new/package'))
89
+
90
+ initializePikkuState('@new/package')
91
+
92
+ assert.ok(states.has('@new/package'))
93
+ })
94
+
95
+ test('should not overwrite existing package state', () => {
96
+ pikkuState('@existing/pkg', 'function', 'meta', { test: true } as any)
97
+
98
+ initializePikkuState('@existing/pkg')
99
+
100
+ const meta = pikkuState('@existing/pkg', 'function', 'meta')
101
+ assert.deepStrictEqual(meta, { test: true })
102
+ })
103
+ })
104
+
105
+ describe('resetPikkuState', () => {
106
+ test('should clear all package states', () => {
107
+ initializePikkuState('@addon/one')
108
+ initializePikkuState('@addon/two')
109
+ assert.ok(getAllPackageStates().has('@addon/one'))
110
+
111
+ resetPikkuState()
112
+
113
+ assert.ok(!getAllPackageStates().has('@addon/one'))
114
+ assert.ok(!getAllPackageStates().has('@addon/two'))
115
+ })
116
+
117
+ test('should preserve errors map from __main__', () => {
118
+ const errors = pikkuState(null, 'misc', 'errors')
119
+ errors.set('TestError', { status: 400, message: 'test' })
120
+
121
+ resetPikkuState()
122
+
123
+ const restoredErrors = pikkuState(null, 'misc', 'errors')
124
+ assert.strictEqual(restoredErrors.get('TestError')?.message, 'test')
125
+ })
126
+
127
+ test('should re-initialize __main__ package', () => {
128
+ resetPikkuState()
129
+
130
+ const states = getAllPackageStates()
131
+ assert.ok(states.has('__main__'))
132
+ assert.ok(pikkuState(null, 'function', 'functions') instanceof Map)
133
+ })
134
+
135
+ test('should create fresh state objects after reset', () => {
136
+ const functionsBefore = pikkuState(null, 'function', 'functions')
137
+ functionsBefore.set('testFunc', {} as any)
138
+
139
+ resetPikkuState()
140
+
141
+ const functionsAfter = pikkuState(null, 'function', 'functions')
142
+ assert.strictEqual(functionsAfter.size, 0)
143
+ })
144
+ })
145
+
146
+ describe('getSingletonServices', () => {
147
+ test('should throw when singleton services not initialized', () => {
148
+ assert.throws(() => getSingletonServices(), {
149
+ message: 'Singleton services not initialized',
150
+ })
151
+ })
152
+
153
+ test('should return services when initialized', () => {
154
+ const mockServices = { logger: { info: () => {} } }
155
+ pikkuState(null, 'package', 'singletonServices', mockServices as any)
156
+
157
+ const result = getSingletonServices()
158
+ assert.strictEqual(result, mockServices)
159
+ })
160
+ })
161
+
162
+ describe('getCreateWireServices', () => {
163
+ test('should return undefined when no factories set', () => {
164
+ const result = getCreateWireServices()
165
+ assert.strictEqual(result, undefined)
166
+ })
167
+
168
+ test('should return createWireServices when factories set', () => {
169
+ const mockCreateWire = async () => ({})
170
+ pikkuState(null, 'package', 'factories', {
171
+ createWireServices: mockCreateWire,
172
+ } as any)
173
+
174
+ const result = getCreateWireServices()
175
+ assert.strictEqual(result, mockCreateWire)
176
+ })
177
+ })
178
+
179
+ describe('getPikkuMetaDir', () => {
180
+ test('should return null by default', () => {
181
+ const result = getPikkuMetaDir()
182
+ assert.strictEqual(result, null)
183
+ })
184
+
185
+ test('should return metaDir when set', () => {
186
+ pikkuState(null, 'package', 'metaDir', '/some/path' as any)
187
+ const result = getPikkuMetaDir()
188
+ assert.strictEqual(result, '/some/path')
189
+ })
190
+
191
+ test('should return metaDir for specific package', () => {
192
+ pikkuState('@test/pkg', 'package', 'metaDir', '/addon/path' as any)
193
+ const result = getPikkuMetaDir('@test/pkg')
194
+ assert.strictEqual(result, '/addon/path')
195
+ })
196
+
197
+ test('should treat null packageName as main', () => {
198
+ pikkuState(null, 'package', 'metaDir', '/main/path' as any)
199
+ const result = getPikkuMetaDir(null)
200
+ assert.strictEqual(result, '/main/path')
201
+ })
202
+ })
203
+
204
+ describe('addPackageServiceFactories', () => {
205
+ test('should register factories for addon package', () => {
206
+ const mockFactories = {
207
+ createSingletonServices: async () => ({}),
208
+ createWireServices: async () => ({}),
209
+ }
210
+
211
+ addPackageServiceFactories('@addon/pkg', mockFactories as any)
212
+
213
+ const result = pikkuState('@addon/pkg', 'package', 'factories')
214
+ assert.strictEqual(result, mockFactories)
215
+ })
216
+
217
+ test('should not affect main package factories', () => {
218
+ const addonFactories = { createSingletonServices: async () => ({}) }
219
+ addPackageServiceFactories('@addon/pkg', addonFactories as any)
220
+
221
+ const mainFactories = pikkuState(null, 'package', 'factories')
222
+ assert.strictEqual(mainFactories, null)
223
+ })
224
+ })
@@ -1,23 +1,31 @@
1
- import { PikkuPackageState } from './index.js'
2
- import { HTTPWiringsMeta } from './wirings/http/http.types.js'
3
- import {
1
+ import type { PikkuPackageState } from './index.js'
2
+ import type {
3
+ CoreSingletonServices,
4
+ CreateWireServices,
5
+ } from './types/core.types.js'
6
+ import type { HTTPWiringsMeta } from './wirings/http/http.types.js'
7
+ import type {
4
8
  MCPResourceMeta,
5
9
  MCPToolMeta,
6
10
  MCPPromptMeta,
7
11
  } from './wirings/mcp/mcp.types.js'
8
- import { AIAgentMeta } from './wirings/ai-agent/ai-agent.types.js'
9
- import { ScheduledTasksMeta } from './wirings/scheduler/scheduler.types.js'
10
- import { TriggerMeta } from './wirings/trigger/trigger.types.js'
12
+ import type { AIAgentMeta } from './wirings/ai-agent/ai-agent.types.js'
13
+ import type { ScheduledTasksMeta } from './wirings/scheduler/scheduler.types.js'
14
+ import type { TriggerMeta } from './wirings/trigger/trigger.types.js'
11
15
 
12
- declare global {
13
- // eslint-disable-next-line no-var
14
- var pikkuState: Map<string, PikkuPackageState> | undefined
16
+ const PIKKU_STATE_KEY = Symbol.for('@pikku/core/state')
17
+
18
+ export const getAllPackageStates = (): Map<string, PikkuPackageState> => {
19
+ if (!(globalThis as any)[PIKKU_STATE_KEY]) {
20
+ ;(globalThis as any)[PIKKU_STATE_KEY] = new Map<string, PikkuPackageState>()
21
+ }
22
+ return (globalThis as any)[PIKKU_STATE_KEY] as Map<string, PikkuPackageState>
15
23
  }
16
24
 
17
25
  /**
18
26
  * Get or set package-scoped pikku state
19
27
  *
20
- * @param packageName - Package name (null for main package, '@scope/package' for external packages)
28
+ * @param packageName - Package name (null for main package, '@scope/package' for addon packages)
21
29
  * @param type - State category (function, rpc, http, etc.)
22
30
  * @param content - Content key within the category
23
31
  * @param value - Optional value to set
@@ -27,7 +35,7 @@ declare global {
27
35
  * // Main package
28
36
  * pikkuState(null, 'function', 'functions').get(funcName)
29
37
  *
30
- * // External package
38
+ * // Addon package
31
39
  * pikkuState('@acme/stripe-functions', 'rpc', 'meta')
32
40
  */
33
41
  export const pikkuState = <
@@ -42,14 +50,11 @@ export const pikkuState = <
42
50
  const resolvedPackageName = packageName ?? '__main__'
43
51
 
44
52
  // Initialize package state if it doesn't exist
45
- if (
46
- !globalThis.pikkuState ||
47
- !globalThis.pikkuState.has(resolvedPackageName)
48
- ) {
53
+ if (!getAllPackageStates().has(resolvedPackageName)) {
49
54
  initializePikkuState(resolvedPackageName)
50
55
  }
51
56
 
52
- const packageState = globalThis.pikkuState!.get(resolvedPackageName)!
57
+ const packageState = getAllPackageStates().get(resolvedPackageName)!
53
58
 
54
59
  if (value !== undefined) {
55
60
  packageState[type][content] = value
@@ -66,7 +71,7 @@ const createEmptyPackageState = (): PikkuPackageState => ({
66
71
  rpc: {
67
72
  meta: {},
68
73
  files: new Map(),
69
- externalPackages: new Map(),
74
+ addons: new Map(),
70
75
  },
71
76
  http: {
72
77
  middleware: new Map(),
@@ -152,29 +157,26 @@ const createEmptyPackageState = (): PikkuPackageState => ({
152
157
  * Initialize state for a new package
153
158
  */
154
159
  export const initializePikkuState = (packageName: string): void => {
155
- if (!globalThis.pikkuState) {
156
- globalThis.pikkuState = new Map()
157
- }
158
- if (!globalThis.pikkuState.has(packageName)) {
159
- globalThis.pikkuState.set(packageName, createEmptyPackageState())
160
+ if (!getAllPackageStates().has(packageName)) {
161
+ getAllPackageStates().set(packageName, createEmptyPackageState())
160
162
  }
161
163
  }
162
164
 
163
165
  export const resetPikkuState = () => {
164
166
  // Preserve the errors map before resetting
165
- const existingErrors = globalThis.pikkuState?.get('__main__')?.misc.errors
167
+ const existingErrors = getAllPackageStates().get('__main__')?.misc.errors
166
168
 
167
- globalThis.pikkuState = new Map()
169
+ ;(globalThis as any)[PIKKU_STATE_KEY] = new Map<string, PikkuPackageState>()
168
170
  initializePikkuState('__main__')
169
171
 
170
172
  // Restore the errors map if it existed
171
173
  if (existingErrors) {
172
- const mainState = globalThis.pikkuState.get('__main__')!
174
+ const mainState = getAllPackageStates().get('__main__')!
173
175
  mainState.misc.errors = existingErrors
174
176
  }
175
177
  }
176
178
 
177
- if (!globalThis.pikkuState) {
179
+ if (!getAllPackageStates().has('__main__')) {
178
180
  resetPikkuState()
179
181
  }
180
182
 
@@ -182,11 +184,21 @@ export const getPikkuMetaDir = (packageName?: string | null): string | null => {
182
184
  return pikkuState(packageName ?? null, 'package', 'metaDir')
183
185
  }
184
186
 
187
+ export const getSingletonServices = (): CoreSingletonServices => {
188
+ const services = pikkuState(null, 'package', 'singletonServices')
189
+ if (!services) throw new Error('Singleton services not initialized')
190
+ return services
191
+ }
192
+
193
+ export const getCreateWireServices = (): CreateWireServices | undefined => {
194
+ return pikkuState(null, 'package', 'factories')?.createWireServices
195
+ }
196
+
185
197
  /**
186
- * Register service factories for an external package.
198
+ * Register service factories for an addon package.
187
199
  * These factories are used to create services when the package's functions are invoked.
188
200
  *
189
- * @param packageName - The package name (e.g., '@pikku/templates-function-external')
201
+ * @param packageName - The package name (e.g., '@pikku/templates-function-addon')
190
202
  * @param factories - The service factory functions
191
203
  */
192
204
  export const addPackageServiceFactories = (
@@ -1,11 +1,55 @@
1
- import { test, describe, before } from 'node:test'
1
+ import { test, describe, before, beforeEach } from 'node:test'
2
2
  import * as assert from 'assert'
3
- import { addSchema, coerceTopLevelDataFromSchema } from './schema.js'
3
+ import {
4
+ addSchema,
5
+ getSchema,
6
+ coerceTopLevelDataFromSchema,
7
+ validateSchema,
8
+ compileAllSchemas,
9
+ } from './schema.js'
10
+ import { resetPikkuState } from './pikku-state.js'
4
11
 
5
12
  describe('Schema', () => {
6
- describe('coerceQueryStringToArray', () => {
13
+ describe('addSchema and getSchema', () => {
14
+ beforeEach(() => {
15
+ resetPikkuState()
16
+ })
17
+
18
+ test('should add and retrieve a schema', () => {
19
+ const schema = { properties: { name: { type: 'string' } } }
20
+ addSchema('testAdd', schema)
21
+ const result = getSchema('testAdd')
22
+ assert.deepStrictEqual(result, schema)
23
+ })
24
+
25
+ test('should return undefined for non-existent schema', () => {
26
+ const result = getSchema('nonExistent')
27
+ assert.strictEqual(result, undefined)
28
+ })
29
+
30
+ test('should add schema for specific package', () => {
31
+ const schema = { properties: { id: { type: 'number' } } }
32
+ addSchema('pkgSchema', schema, '@addon/pkg')
33
+ const result = getSchema('pkgSchema', '@addon/pkg')
34
+ assert.deepStrictEqual(result, schema)
35
+ })
36
+
37
+ test('should not find package schema in main', () => {
38
+ addSchema('pkgOnly', { properties: {} }, '@addon/pkg')
39
+ const result = getSchema('pkgOnly', null)
40
+ assert.strictEqual(result, undefined)
41
+ })
42
+
43
+ test('should unwrap default export', () => {
44
+ const schema = { default: { properties: { x: { type: 'string' } } } }
45
+ addSchema('withDefault', schema)
46
+ const result = getSchema('withDefault')
47
+ assert.deepStrictEqual(result, { properties: { x: { type: 'string' } } })
48
+ })
49
+ })
50
+
51
+ describe('coerceTopLevelDataFromSchema', () => {
7
52
  before(() => {
8
- // Add schemas dynamically using addSchema
9
53
  addSchema('testSchema', {
10
54
  properties: {
11
55
  tags: { type: 'array' },
@@ -16,10 +60,16 @@ describe('Schema', () => {
16
60
 
17
61
  addSchema('booleanSchema', {
18
62
  properties: {
19
- isActive: true, // Invalid schema definition, but the function should handle it
63
+ isActive: true,
20
64
  tags: { type: 'array' },
21
65
  },
22
66
  })
67
+
68
+ addSchema('dateSchema', {
69
+ properties: {
70
+ createdAt: { type: 'string', format: 'date-time' },
71
+ },
72
+ })
23
73
  })
24
74
 
25
75
  test('should split a string into an array for properties of type array', () => {
@@ -51,7 +101,149 @@ describe('Schema', () => {
51
101
  const data = { tags: 'a,b,c', isActive: 'true' }
52
102
  coerceTopLevelDataFromSchema('booleanSchema', data)
53
103
  assert.deepStrictEqual(data.tags, ['a', 'b', 'c'])
54
- assert.strictEqual(data.isActive, 'true') // No coercion should happen
104
+ assert.strictEqual(data.isActive, 'true')
105
+ })
106
+
107
+ test('should coerce date-time strings to Date objects', () => {
108
+ const data = { createdAt: '2024-01-01T00:00:00Z' } as any
109
+ coerceTopLevelDataFromSchema('dateSchema', data)
110
+ assert.ok(data.createdAt instanceof Date)
111
+ })
112
+ })
113
+
114
+ describe('validateSchema', () => {
115
+ beforeEach(() => {
116
+ resetPikkuState()
117
+ })
118
+
119
+ test('should pass when no schema service provided', async () => {
120
+ const logger = {
121
+ info: () => {},
122
+ warn: () => {},
123
+ error: () => {},
124
+ debug: () => {},
125
+ } as any
126
+ await validateSchema(logger, undefined, 'some-schema', { data: 1 })
127
+ })
128
+
129
+ test('should warn when no schema name but data is passed', async () => {
130
+ let warningMsg = ''
131
+ const logger = {
132
+ info: () => {},
133
+ warn: (msg: string) => {
134
+ warningMsg = msg
135
+ },
136
+ error: () => {},
137
+ debug: () => {},
138
+ } as any
139
+ const schemaService = {
140
+ compileSchema: async () => {},
141
+ validateSchema: async () => {},
142
+ getSchemaNames: () => new Set<string>(),
143
+ }
144
+ await assert.rejects(
145
+ () => validateSchema(logger, schemaService, null, { key: 'value' }),
146
+ (err: any) => err.message === 'No data expected'
147
+ )
148
+ })
149
+
150
+ test('should return early when no schema name and empty data', async () => {
151
+ const logger = {
152
+ info: () => {},
153
+ warn: () => {},
154
+ error: () => {},
155
+ debug: () => {},
156
+ } as any
157
+ const schemaService = {
158
+ compileSchema: async () => {},
159
+ validateSchema: async () => {},
160
+ getSchemaNames: () => new Set<string>(),
161
+ }
162
+ await validateSchema(logger, schemaService, null, {})
163
+ })
164
+
165
+ test('should throw MissingSchemaError when schema not found', async () => {
166
+ const logger = {
167
+ info: () => {},
168
+ warn: () => {},
169
+ error: () => {},
170
+ debug: () => {},
171
+ } as any
172
+ const schemaService = {
173
+ compileSchema: async () => {},
174
+ validateSchema: async () => {},
175
+ getSchemaNames: () => new Set<string>(),
176
+ }
177
+ await assert.rejects(
178
+ () => validateSchema(logger, schemaService, 'nonExistent', { data: 1 }),
179
+ (err: any) => err.message.includes('not found')
180
+ )
181
+ })
182
+
183
+ test('should compile and validate schema when found', async () => {
184
+ addSchema('validSchema', { properties: { name: { type: 'string' } } })
185
+ const logger = {
186
+ info: () => {},
187
+ warn: () => {},
188
+ error: () => {},
189
+ debug: () => {},
190
+ } as any
191
+ let compiled = false
192
+ let validated = false
193
+ const schemaService = {
194
+ compileSchema: async () => {
195
+ compiled = true
196
+ },
197
+ validateSchema: async () => {
198
+ validated = true
199
+ },
200
+ getSchemaNames: () => new Set<string>(),
201
+ }
202
+ await validateSchema(logger, schemaService, 'validSchema', {
203
+ name: 'test',
204
+ })
205
+ assert.strictEqual(compiled, true)
206
+ assert.strictEqual(validated, true)
207
+ })
208
+ })
209
+
210
+ describe('compileAllSchemas', () => {
211
+ beforeEach(() => {
212
+ resetPikkuState()
213
+ })
214
+
215
+ test('should throw when no schema service available', () => {
216
+ const logger = {
217
+ info: () => {},
218
+ warn: () => {},
219
+ error: () => {},
220
+ debug: () => {},
221
+ } as any
222
+ assert.throws(() => compileAllSchemas(logger), {
223
+ message: 'SchemaService needs to be defined to load schemas',
224
+ })
225
+ })
226
+
227
+ test('should compile all schemas across packages', () => {
228
+ addSchema('schema1', { properties: { a: { type: 'string' } } })
229
+ addSchema('schema2', { properties: { b: { type: 'number' } } })
230
+
231
+ const compiled: string[] = []
232
+ const logger = {
233
+ info: () => {},
234
+ warn: () => {},
235
+ error: () => {},
236
+ debug: () => {},
237
+ } as any
238
+ const schemaService = {
239
+ compileSchema: (name: string, _schema: any) => {
240
+ compiled.push(name)
241
+ },
242
+ getSchemaNames: () => new Set(compiled),
243
+ }
244
+ compileAllSchemas(logger, schemaService as any)
245
+ assert.ok(compiled.includes('schema1'))
246
+ assert.ok(compiled.includes('schema2'))
55
247
  })
56
248
  })
57
249
  })
package/src/schema.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { Logger } from './services/logger.js'
2
- import { SchemaService } from './services/schema-service.js'
1
+ import type { Logger } from './services/logger.js'
2
+ import type { SchemaService } from './services/schema-service.js'
3
3
  import {
4
4
  MissingSchemaError,
5
5
  UnprocessableContentError,
6
6
  } from './errors/errors.js'
7
- import { pikkuState } from './pikku-state.js'
7
+ import { pikkuState, getAllPackageStates } from './pikku-state.js'
8
8
 
9
9
  const schemaKey = (name: string, packageName: string | null): string =>
10
10
  packageName ? `${packageName}:${name}` : name
@@ -13,7 +13,7 @@ const schemaKey = (name: string, packageName: string | null): string =>
13
13
  * Adds a schema to the schemas map for a specific package.
14
14
  * @param name - The name of the schema.
15
15
  * @param value - The schema value.
16
- * @param packageName - The package name (null for main package, '@scope/package' for external packages).
16
+ * @param packageName - The package name (null for main package, '@scope/package' for addon packages).
17
17
  * @ignore
18
18
  */
19
19
  export const addSchema = (
@@ -21,13 +21,14 @@ export const addSchema = (
21
21
  value: any,
22
22
  packageName: string | null = null
23
23
  ) => {
24
- pikkuState(packageName, 'misc', 'schemas').set(name, value)
24
+ const schema = value?.default ?? value
25
+ pikkuState(packageName, 'misc', 'schemas').set(name, schema)
25
26
  }
26
27
 
27
28
  /**
28
29
  * Retrieves a schema from the schemas map for a specific package.
29
30
  * @param name - The name of the schema.
30
- * @param packageName - The package name (null for main package, '@scope/package' for external packages).
31
+ * @param packageName - The package name (null for main package, '@scope/package' for addon packages).
31
32
  * @returns The schema value or undefined if not found.
32
33
  * @ignore
33
34
  */
@@ -46,10 +47,13 @@ export const compileAllSchemas = (
46
47
  logger: Logger,
47
48
  schemaService?: SchemaService
48
49
  ) => {
50
+ if (!schemaService) {
51
+ schemaService = pikkuState(null, 'package', 'singletonServices')?.schema
52
+ }
49
53
  if (!schemaService) {
50
54
  throw new Error('SchemaService needs to be defined to load schemas')
51
55
  }
52
- for (const [pkgName, packageState] of globalThis.pikkuState!) {
56
+ for (const [pkgName, packageState] of getAllPackageStates()) {
53
57
  const resolvedPkgName = pkgName === '__main__' ? null : pkgName
54
58
  for (const [name, schema] of packageState.misc.schemas) {
55
59
  schemaService.compileSchema(schemaKey(name, resolvedPkgName), schema)
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  AIMessage,
3
3
  AIAgentStep,
4
4
  AIAgentToolDef,
@@ -23,7 +23,19 @@ export type AIAgentRunnerResult = {
23
23
  usage: { inputTokens: number; outputTokens: number }
24
24
  }
25
25
 
26
+ export type AIAgentStepResult = {
27
+ text: string
28
+ object?: unknown
29
+ toolCalls: { toolCallId: string; toolName: string; args: unknown }[]
30
+ toolResults: { toolCallId: string; toolName: string; result: unknown }[]
31
+ usage: { inputTokens: number; outputTokens: number }
32
+ finishReason: 'stop' | 'tool-calls' | 'length' | 'error' | 'unknown'
33
+ }
34
+
26
35
  export interface AIAgentRunnerService {
27
- stream(params: AIAgentRunnerParams, channel: AIStreamChannel): Promise<void>
28
- run(params: AIAgentRunnerParams): Promise<AIAgentRunnerResult>
36
+ stream(
37
+ params: AIAgentRunnerParams,
38
+ channel: AIStreamChannel
39
+ ): Promise<AIAgentStepResult>
40
+ run(params: AIAgentRunnerParams): Promise<AIAgentStepResult>
29
41
  }
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  AgentRunState,
3
3
  PendingApproval,
4
4
  } from '../wirings/ai-agent/ai-agent.types.js'
@@ -1,4 +1,4 @@
1
- import { AIThread, AIMessage } from '../wirings/ai-agent/ai-agent.types.js'
1
+ import type { AIThread, AIMessage } from '../wirings/ai-agent/ai-agent.types.js'
2
2
 
3
3
  export interface AIStorageService {
4
4
  createThread(