@pikku/core 0.12.4 → 0.12.6

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 (235) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/dist/dev/hot-reload.d.ts +10 -0
  3. package/dist/dev/hot-reload.js +158 -0
  4. package/dist/middleware-runner.d.ts +1 -0
  5. package/dist/middleware-runner.js +5 -0
  6. package/dist/permissions.d.ts +1 -0
  7. package/dist/permissions.js +5 -0
  8. package/dist/services/content-service.d.ts +2 -0
  9. package/dist/services/in-memory-workflow-service.d.ts +1 -0
  10. package/dist/services/in-memory-workflow-service.js +16 -11
  11. package/dist/services/workflow-service.d.ts +1 -0
  12. package/dist/wirings/ai-agent/ai-agent-prepare.js +16 -1
  13. package/dist/wirings/channel/channel-middleware-runner.d.ts +1 -0
  14. package/dist/wirings/channel/channel-middleware-runner.js +5 -0
  15. package/dist/wirings/http/http-runner.js +1 -1
  16. package/dist/wirings/workflow/pikku-workflow-service.d.ts +6 -0
  17. package/dist/wirings/workflow/pikku-workflow-service.js +51 -16
  18. package/dist/wirings/workflow/workflow.types.d.ts +2 -0
  19. package/package.json +5 -3
  20. package/src/crypto-utils.test.ts +214 -0
  21. package/src/crypto-utils.ts +213 -0
  22. package/src/dev/hot-reload.test.ts +484 -0
  23. package/src/dev/hot-reload.ts +212 -0
  24. package/src/errors/error-handler.ts +62 -0
  25. package/src/errors/error.test.ts +195 -0
  26. package/src/errors/errors.ts +374 -0
  27. package/src/errors/index.ts +2 -0
  28. package/src/factory-functions.test.ts +109 -0
  29. package/src/function/function-runner.test.ts +536 -0
  30. package/src/function/function-runner.ts +365 -0
  31. package/src/function/functions.types.ts +304 -0
  32. package/src/function/index.ts +5 -0
  33. package/src/handle-error.test.ts +424 -0
  34. package/src/handle-error.ts +69 -0
  35. package/src/index.ts +118 -0
  36. package/src/internal.ts +6 -0
  37. package/src/middleware/auth-apikey.test.ts +363 -0
  38. package/src/middleware/auth-apikey.ts +47 -0
  39. package/src/middleware/auth-bearer.test.ts +450 -0
  40. package/src/middleware/auth-bearer.ts +72 -0
  41. package/src/middleware/auth-cookie.test.ts +528 -0
  42. package/src/middleware/auth-cookie.ts +80 -0
  43. package/src/middleware/cors.test.ts +424 -0
  44. package/src/middleware/cors.ts +104 -0
  45. package/src/middleware/index.ts +5 -0
  46. package/src/middleware/remote-auth.test.ts +488 -0
  47. package/src/middleware/remote-auth.ts +68 -0
  48. package/src/middleware/timeout.ts +15 -0
  49. package/src/middleware-runner.test.ts +418 -0
  50. package/src/middleware-runner.ts +240 -0
  51. package/src/permissions.test.ts +434 -0
  52. package/src/permissions.ts +327 -0
  53. package/src/pikku-request.ts +23 -0
  54. package/src/pikku-response.ts +5 -0
  55. package/src/pikku-state.test.ts +224 -0
  56. package/src/pikku-state.ts +216 -0
  57. package/src/run-tests-script.test.ts +49 -0
  58. package/src/schema.test.ts +249 -0
  59. package/src/schema.ts +151 -0
  60. package/src/services/ai-agent-runner-service.ts +41 -0
  61. package/src/services/ai-run-state-service.ts +20 -0
  62. package/src/services/ai-storage-service.ts +39 -0
  63. package/src/services/content-service.ts +81 -0
  64. package/src/services/deployment-service.ts +22 -0
  65. package/src/services/gateway-service.ts +20 -0
  66. package/src/services/gopass-secrets.ts +98 -0
  67. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  68. package/src/services/in-memory-trigger-service.ts +64 -0
  69. package/src/services/in-memory-workflow-service.test.ts +351 -0
  70. package/src/services/in-memory-workflow-service.ts +512 -0
  71. package/src/services/index.ts +56 -0
  72. package/src/services/jwt-service.ts +30 -0
  73. package/src/services/local-content.ts +120 -0
  74. package/src/services/local-gateway-service.ts +62 -0
  75. package/src/services/local-secrets.test.ts +80 -0
  76. package/src/services/local-secrets.ts +94 -0
  77. package/src/services/local-variables.test.ts +61 -0
  78. package/src/services/local-variables.ts +39 -0
  79. package/src/services/logger-console.test.ts +118 -0
  80. package/src/services/logger-console.ts +98 -0
  81. package/src/services/logger.ts +57 -0
  82. package/src/services/scheduler-service.ts +86 -0
  83. package/src/services/schema-service.ts +33 -0
  84. package/src/services/scoped-secret-service.test.ts +74 -0
  85. package/src/services/scoped-secret-service.ts +41 -0
  86. package/src/services/secret-service.ts +38 -0
  87. package/src/services/trigger-service.ts +17 -0
  88. package/src/services/typed-secret-service.test.ts +93 -0
  89. package/src/services/typed-secret-service.ts +70 -0
  90. package/src/services/typed-variables-service.test.ts +73 -0
  91. package/src/services/typed-variables-service.ts +81 -0
  92. package/src/services/user-session-service.test.ts +113 -0
  93. package/src/services/user-session-service.ts +86 -0
  94. package/src/services/variables-service.ts +11 -0
  95. package/src/services/workflow-service.ts +96 -0
  96. package/src/testing/index.ts +2 -0
  97. package/src/testing/service-tests.ts +873 -0
  98. package/src/time-utils.test.ts +56 -0
  99. package/src/time-utils.ts +107 -0
  100. package/src/types/core.types.ts +478 -0
  101. package/src/types/state.types.ts +188 -0
  102. package/src/utils/hash.test.ts +68 -0
  103. package/src/utils/hash.ts +26 -0
  104. package/src/utils.test.ts +350 -0
  105. package/src/utils.ts +129 -0
  106. package/src/version.test.ts +80 -0
  107. package/src/version.ts +25 -0
  108. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
  109. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  110. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  111. package/src/wirings/ai-agent/ai-agent-memory.ts +310 -0
  112. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  113. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  114. package/src/wirings/ai-agent/ai-agent-prepare.ts +659 -0
  115. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  116. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  117. package/src/wirings/ai-agent/ai-agent-runner.test.ts +319 -0
  118. package/src/wirings/ai-agent/ai-agent-runner.ts +773 -0
  119. package/src/wirings/ai-agent/ai-agent-stream.test.ts +859 -0
  120. package/src/wirings/ai-agent/ai-agent-stream.ts +1153 -0
  121. package/src/wirings/ai-agent/ai-agent.types.ts +382 -0
  122. package/src/wirings/ai-agent/index.ts +37 -0
  123. package/src/wirings/channel/channel-common.ts +90 -0
  124. package/src/wirings/channel/channel-handler.ts +250 -0
  125. package/src/wirings/channel/channel-middleware-runner.ts +126 -0
  126. package/src/wirings/channel/channel-runner.ts +166 -0
  127. package/src/wirings/channel/channel-store.ts +29 -0
  128. package/src/wirings/channel/channel.types.ts +172 -0
  129. package/src/wirings/channel/define-channel-routes.ts +25 -0
  130. package/src/wirings/channel/eventhub-service.ts +34 -0
  131. package/src/wirings/channel/eventhub-store.ts +13 -0
  132. package/src/wirings/channel/index.ts +24 -0
  133. package/src/wirings/channel/local/index.ts +3 -0
  134. package/src/wirings/channel/local/local-channel-handler.ts +81 -0
  135. package/src/wirings/channel/local/local-channel-runner.test.ts +215 -0
  136. package/src/wirings/channel/local/local-channel-runner.ts +210 -0
  137. package/src/wirings/channel/local/local-eventhub-service.test.ts +95 -0
  138. package/src/wirings/channel/local/local-eventhub-service.ts +101 -0
  139. package/src/wirings/channel/log-channels.ts +20 -0
  140. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +54 -0
  141. package/src/wirings/channel/pikku-abstract-channel-handler.ts +45 -0
  142. package/src/wirings/channel/serverless/index.ts +5 -0
  143. package/src/wirings/channel/serverless/serverless-channel-runner.ts +289 -0
  144. package/src/wirings/cli/channel/cli-channel-runner.ts +136 -0
  145. package/src/wirings/cli/channel/index.ts +1 -0
  146. package/src/wirings/cli/cli-runner.test.ts +403 -0
  147. package/src/wirings/cli/cli-runner.ts +529 -0
  148. package/src/wirings/cli/cli.types.ts +358 -0
  149. package/src/wirings/cli/command-parser.test.ts +445 -0
  150. package/src/wirings/cli/command-parser.ts +504 -0
  151. package/src/wirings/cli/define-cli-commands.ts +24 -0
  152. package/src/wirings/cli/index.ts +19 -0
  153. package/src/wirings/gateway/gateway-runner.test.ts +474 -0
  154. package/src/wirings/gateway/gateway-runner.ts +411 -0
  155. package/src/wirings/gateway/gateway.types.ts +149 -0
  156. package/src/wirings/gateway/index.ts +13 -0
  157. package/src/wirings/http/http-routes.test.ts +322 -0
  158. package/src/wirings/http/http-routes.ts +197 -0
  159. package/src/wirings/http/http-runner.test.ts +144 -0
  160. package/src/wirings/http/http-runner.ts +588 -0
  161. package/src/wirings/http/http.types.ts +369 -0
  162. package/src/wirings/http/index.ts +28 -0
  163. package/src/wirings/http/log-http-routes.ts +22 -0
  164. package/src/wirings/http/pikku-fetch-http-request.test.ts +237 -0
  165. package/src/wirings/http/pikku-fetch-http-request.ts +170 -0
  166. package/src/wirings/http/pikku-fetch-http-response.test.ts +82 -0
  167. package/src/wirings/http/pikku-fetch-http-response.ts +147 -0
  168. package/src/wirings/http/routers/http-router.ts +13 -0
  169. package/src/wirings/http/routers/path-to-regex.test.ts +319 -0
  170. package/src/wirings/http/routers/path-to-regex.ts +126 -0
  171. package/src/wirings/http/web-request.test.ts +236 -0
  172. package/src/wirings/http/web-request.ts +104 -0
  173. package/src/wirings/mcp/index.ts +27 -0
  174. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +389 -0
  175. package/src/wirings/mcp/mcp-endpoint-registry.ts +159 -0
  176. package/src/wirings/mcp/mcp-runner.ts +323 -0
  177. package/src/wirings/mcp/mcp.types.ts +216 -0
  178. package/src/wirings/node/index.ts +2 -0
  179. package/src/wirings/node/node.types.ts +23 -0
  180. package/src/wirings/oauth2/index.ts +3 -0
  181. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  182. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  183. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  184. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  185. package/src/wirings/queue/index.ts +31 -0
  186. package/src/wirings/queue/queue-runner.test.ts +710 -0
  187. package/src/wirings/queue/queue-runner.ts +192 -0
  188. package/src/wirings/queue/queue.types.ts +189 -0
  189. package/src/wirings/queue/register-queue-helper.ts +60 -0
  190. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  191. package/src/wirings/queue/validate-worker-config.ts +116 -0
  192. package/src/wirings/rpc/index.ts +4 -0
  193. package/src/wirings/rpc/rpc-runner.ts +460 -0
  194. package/src/wirings/rpc/rpc-types.ts +56 -0
  195. package/src/wirings/rpc/wire-addon.ts +21 -0
  196. package/src/wirings/scheduler/index.ts +11 -0
  197. package/src/wirings/scheduler/log-schedulers.ts +20 -0
  198. package/src/wirings/scheduler/scheduler-runner.test.ts +660 -0
  199. package/src/wirings/scheduler/scheduler-runner.ts +133 -0
  200. package/src/wirings/scheduler/scheduler.types.ts +53 -0
  201. package/src/wirings/secret/index.ts +9 -0
  202. package/src/wirings/secret/secret.types.ts +32 -0
  203. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  204. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  205. package/src/wirings/trigger/index.ts +10 -0
  206. package/src/wirings/trigger/pikku-trigger-service.ts +112 -0
  207. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  208. package/src/wirings/trigger/trigger-runner.ts +135 -0
  209. package/src/wirings/trigger/trigger.types.ts +178 -0
  210. package/src/wirings/variable/index.ts +8 -0
  211. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  212. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  213. package/src/wirings/variable/variable.types.ts +22 -0
  214. package/src/wirings/workflow/dsl/index.ts +31 -0
  215. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
  216. package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
  217. package/src/wirings/workflow/graph/graph-node.ts +222 -0
  218. package/src/wirings/workflow/graph/graph-runner.test.ts +487 -0
  219. package/src/wirings/workflow/graph/graph-runner.ts +816 -0
  220. package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
  221. package/src/wirings/workflow/graph/graph-validation.ts +237 -0
  222. package/src/wirings/workflow/graph/index.ts +19 -0
  223. package/src/wirings/workflow/graph/template.test.ts +49 -0
  224. package/src/wirings/workflow/graph/template.ts +42 -0
  225. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  226. package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
  227. package/src/wirings/workflow/index.ts +82 -0
  228. package/src/wirings/workflow/pikku-workflow-service.test.ts +200 -0
  229. package/src/wirings/workflow/pikku-workflow-service.ts +1229 -0
  230. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  231. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  232. package/src/wirings/workflow/workflow.types.ts +276 -0
  233. package/tsconfig.json +14 -0
  234. package/tsconfig.tsbuildinfo +1 -0
  235. package/lcov.info +0 -18891
@@ -0,0 +1,109 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import {
4
+ pikkuAuth,
5
+ pikkuPermission,
6
+ pikkuMiddleware,
7
+ pikkuPermissionFactory,
8
+ pikkuMiddlewareFactory,
9
+ } from './index.js'
10
+
11
+ test('pikkuPermission returns the same function', async () => {
12
+ const originalFn = async () => true
13
+ const wrappedFn = pikkuPermission(originalFn)
14
+ assert.strictEqual(wrappedFn, originalFn)
15
+ })
16
+
17
+ test('pikkuMiddleware returns the same function', async () => {
18
+ const originalFn = async ({ logger }, wires, next) => next
19
+ const wrappedFn = pikkuMiddleware(originalFn)
20
+ assert.strictEqual(wrappedFn, originalFn)
21
+ })
22
+
23
+ test('pikkuPermissionFactory creates a factory function', async () => {
24
+ const factory = pikkuPermissionFactory<{ role: string }>(({ role }) => {
25
+ return pikkuPermission(async ({ logger }, data, { session }) => {
26
+ return (session as any)?.role === role
27
+ })
28
+ })
29
+
30
+ assert.strictEqual(typeof factory, 'function')
31
+ assert.strictEqual(factory.length, 1) // input parameter
32
+
33
+ // Call the factory to get a permission
34
+ const permission = factory({ role: 'admin' })
35
+ assert.strictEqual(typeof permission, 'function')
36
+ assert.strictEqual(permission.length, 3) // services, data, session
37
+ })
38
+
39
+ test('pikkuMiddlewareFactory creates a factory function', async () => {
40
+ const factory = pikkuMiddlewareFactory<{ message: string }>(({ message }) => {
41
+ return pikkuMiddleware(async ({ logger }, wires, next) => {
42
+ logger.info(message)
43
+ await next()
44
+ })
45
+ })
46
+
47
+ assert.strictEqual(typeof factory, 'function')
48
+ assert.strictEqual(factory.length, 1) // input parameter
49
+
50
+ // Call the factory to get middleware
51
+ const middleware = factory({ message: 'test' })
52
+ assert.strictEqual(typeof middleware, 'function')
53
+ assert.strictEqual(middleware.length, 3) // services, wires, next
54
+ })
55
+
56
+ test('pikkuPermissionFactory returns the same factory', async () => {
57
+ const originalFactory = () => pikkuPermission(async () => true)
58
+ const wrappedFactory = pikkuPermissionFactory(originalFactory as any)
59
+ assert.strictEqual(wrappedFactory, originalFactory)
60
+ })
61
+
62
+ test('pikkuMiddlewareFactory returns the same factory', async () => {
63
+ const originalFactory = ({ message }) =>
64
+ pikkuMiddleware(async ({ logger }, _wires, next) => {
65
+ logger.info(message)
66
+ await next()
67
+ })
68
+ const wrappedFactory = pikkuMiddlewareFactory(originalFactory)
69
+ assert.strictEqual(wrappedFactory, originalFactory)
70
+ })
71
+
72
+ test('pikkuAuth returns a 3-arg wrapper function', async () => {
73
+ const authFn = async (_services, session) => !!session
74
+ const wrapped = pikkuAuth(authFn)
75
+ assert.strictEqual(typeof wrapped, 'function')
76
+ assert.strictEqual(wrapped.length, 3)
77
+ })
78
+
79
+ test('pikkuAuth wrapper extracts session from wire and calls auth function', async () => {
80
+ let receivedSession = null
81
+ const authFn = async (_services, session) => {
82
+ receivedSession = session
83
+ return session.role === 'admin'
84
+ }
85
+ const wrapped = pikkuAuth(authFn)
86
+ const result = await wrapped({}, null, { session: { role: 'admin' } })
87
+ assert.strictEqual(result, true)
88
+ assert.deepStrictEqual(receivedSession, { role: 'admin' })
89
+ })
90
+
91
+ test('pikkuAuth wrapper returns false when no session', async () => {
92
+ const authFn = async (_services, session) => !!session
93
+ const wrapped = pikkuAuth(authFn)
94
+ const result = await wrapped({}, null, { session: null })
95
+ assert.strictEqual(result, false)
96
+ })
97
+
98
+ test('pikkuAuth config object syntax works', async () => {
99
+ const authFn = async (_services, session) => !!session
100
+ const wrapped = pikkuAuth({
101
+ func: authFn,
102
+ name: 'Is Authenticated',
103
+ description: 'Checks if user has a session',
104
+ })
105
+ assert.strictEqual(typeof wrapped, 'function')
106
+ assert.strictEqual(wrapped.length, 3)
107
+ const result = await wrapped({}, null, { session: { id: '1' } })
108
+ assert.strictEqual(result, true)
109
+ })
@@ -0,0 +1,536 @@
1
+ import { describe, test, beforeEach } from 'node:test'
2
+ import * as assert from 'node:assert'
3
+ import { addFunction, runPikkuFunc } from './function-runner.js'
4
+ import { addMiddleware, addPermission } from '../index.js'
5
+ import { resetPikkuState, pikkuState } from '../pikku-state.js'
6
+ import type { CoreServices, CorePikkuMiddleware } from '../types/core.types.js'
7
+ import type { CorePermissionGroup } from './functions.types.js'
8
+
9
+ beforeEach(() => {
10
+ resetPikkuState()
11
+ })
12
+
13
+ // Helper function to add function with metadata for tests
14
+ const addTestFunction = (funcName: string, funcConfig: any) => {
15
+ addFunction(funcName, funcConfig)
16
+ // Convert tags to middleware metadata
17
+ const middleware = funcConfig.tags
18
+ ? funcConfig.tags.map((tag: string) => ({ type: 'tag' as const, tag }))
19
+ : undefined
20
+ // Convert tags to permissions metadata
21
+ const permissions = funcConfig.tags
22
+ ? funcConfig.tags.map((tag: string) => ({ type: 'tag' as const, tag }))
23
+ : undefined
24
+ pikkuState(null, 'function', 'meta')[funcName] = {
25
+ pikkuFuncId: funcName,
26
+ inputSchemaName: null,
27
+ outputSchemaName: null,
28
+ middleware,
29
+ permissions,
30
+ }
31
+ }
32
+
33
+ const mockSingletonServices = {
34
+ logger: {
35
+ info: () => {},
36
+ warn: () => {},
37
+ error: () => {},
38
+ debug: () => {},
39
+ },
40
+ } as any
41
+
42
+ const mockServices: CoreServices = {
43
+ ...mockSingletonServices,
44
+ } as any
45
+
46
+ describe('runPikkuFunc - Integration Tests', () => {
47
+ test('should execute middleware in correct order: wiringTags → wiringMiddleware → funcTags → funcMiddleware', async () => {
48
+ const executionOrder: string[] = []
49
+ const createMiddleware = (name: string): CorePikkuMiddleware => {
50
+ return async (services, wire, next) => {
51
+ executionOrder.push(name)
52
+ await next()
53
+ }
54
+ }
55
+
56
+ addMiddleware('wiringTag', [createMiddleware('wiringTag')])
57
+ addMiddleware('funcTag', [createMiddleware('funcTag')])
58
+
59
+ // Register function with middleware and tags
60
+ addTestFunction('testFunc', {
61
+ func: async () => {
62
+ executionOrder.push('main')
63
+ return 'success'
64
+ },
65
+ middleware: [createMiddleware('funcMiddleware')],
66
+ tags: ['funcTag'],
67
+ })
68
+
69
+ const result = await runPikkuFunc(
70
+ 'rpc',
71
+ Math.random().toString(),
72
+ 'testFunc',
73
+ {
74
+ singletonServices: mockSingletonServices,
75
+ getAllServices: () => mockServices,
76
+ data: () => ({}),
77
+ wireMiddleware: [createMiddleware('wiringMiddleware')],
78
+ inheritedMiddleware: [{ type: 'tag', tag: 'wiringTag' }],
79
+ auth: false,
80
+ wire: {},
81
+ }
82
+ )
83
+
84
+ assert.equal(result, 'success')
85
+ // Order: wireInheritedMiddleware (tags), wireMiddleware, funcInheritedMiddleware (tags), funcMiddleware
86
+ assert.deepEqual(executionOrder, [
87
+ 'wiringTag',
88
+ 'wiringMiddleware',
89
+ 'funcTag',
90
+ 'funcMiddleware',
91
+ 'main',
92
+ ])
93
+ })
94
+
95
+ test('should execute permissions in correct order: wiringTags → wiringPermissions → funcTags → funcPermissions', async () => {
96
+ const executionOrder: string[] = []
97
+
98
+ // Setup tagged permissions
99
+ const wiringTagPermission = async () => {
100
+ executionOrder.push('wiringTag')
101
+ return true
102
+ }
103
+ const funcTagPermission = async () => {
104
+ executionOrder.push('funcTag')
105
+ return true
106
+ }
107
+
108
+ addPermission('wiringTag', [wiringTagPermission])
109
+ addPermission('funcTag', [funcTagPermission])
110
+
111
+ // Setup direct permissions
112
+ const wiringPermissions: CorePermissionGroup = {
113
+ permissions: [
114
+ async () => {
115
+ executionOrder.push('wiringPermission')
116
+ return true
117
+ },
118
+ ],
119
+ }
120
+ const funcPermissions: CorePermissionGroup = {
121
+ permissions: [
122
+ async () => {
123
+ executionOrder.push('funcPermission')
124
+ return true
125
+ },
126
+ ],
127
+ }
128
+
129
+ // Register function with permissions and tags
130
+ addTestFunction('testFunc', {
131
+ func: async () => {
132
+ executionOrder.push('main')
133
+ return 'success'
134
+ },
135
+ permissions: funcPermissions,
136
+ tags: ['funcTag'],
137
+ })
138
+
139
+ const result = await runPikkuFunc(
140
+ 'rpc',
141
+ Math.random().toString(),
142
+ 'testFunc',
143
+ {
144
+ singletonServices: mockSingletonServices,
145
+ getAllServices: () => mockServices,
146
+ data: () => ({}),
147
+ wirePermissions: wiringPermissions,
148
+ tags: ['wiringTag'],
149
+ auth: false,
150
+ wire: {},
151
+ }
152
+ )
153
+
154
+ assert.equal(result, 'success')
155
+ // Order: wiringTags → main (short-circuit on first passing group)
156
+ assert.deepEqual(executionOrder, ['wiringTag', 'main'])
157
+ })
158
+
159
+ test('should throw specific error for wiring tag permission failures', async () => {
160
+ const failingWiringTagPermission = async () => false
161
+
162
+ addPermission('wiringTag', [failingWiringTagPermission])
163
+
164
+ addTestFunction('testFunc', {
165
+ func: async () => 'success',
166
+ })
167
+
168
+ await assert.rejects(
169
+ runPikkuFunc('rpc', Math.random().toString(), 'testFunc', {
170
+ singletonServices: mockSingletonServices,
171
+ getAllServices: () => mockServices,
172
+ data: () => ({}),
173
+ tags: ['wiringTag'],
174
+ auth: false,
175
+ wire: {},
176
+ }),
177
+ {
178
+ message: 'Permission denied',
179
+ }
180
+ )
181
+ })
182
+
183
+ test('should throw specific error for wiring permission failures', async () => {
184
+ const wiringPermissions: CorePermissionGroup = {
185
+ permissions: [async () => false],
186
+ }
187
+
188
+ addTestFunction('testFunc', {
189
+ func: async () => 'success',
190
+ })
191
+
192
+ await assert.rejects(
193
+ runPikkuFunc('rpc', Math.random().toString(), 'testFunc', {
194
+ singletonServices: mockSingletonServices,
195
+ getAllServices: () => mockServices,
196
+ data: () => ({}),
197
+ wirePermissions: wiringPermissions,
198
+ auth: false,
199
+ wire: {},
200
+ }),
201
+ {
202
+ message: 'Permission denied',
203
+ }
204
+ )
205
+ })
206
+
207
+ test('should throw specific error for function tag permission failures', async () => {
208
+ const failingFuncTagPermission = async () => false
209
+
210
+ addPermission('funcTag', [failingFuncTagPermission])
211
+
212
+ addTestFunction('testFunc', {
213
+ func: async () => 'success',
214
+ tags: ['funcTag'],
215
+ })
216
+
217
+ await assert.rejects(
218
+ runPikkuFunc('rpc', Math.random().toString(), 'testFunc', {
219
+ singletonServices: mockSingletonServices,
220
+ getAllServices: () => mockServices,
221
+ data: () => ({}),
222
+ auth: false,
223
+ wire: {},
224
+ }),
225
+ {
226
+ message: 'Permission denied',
227
+ }
228
+ )
229
+ })
230
+
231
+ test('should throw specific error for function permission failures', async () => {
232
+ const funcPermissions: CorePermissionGroup = {
233
+ permissions: [async () => false],
234
+ }
235
+
236
+ addTestFunction('testFunc', {
237
+ func: async () => 'success',
238
+ permissions: funcPermissions,
239
+ })
240
+
241
+ await assert.rejects(
242
+ runPikkuFunc('rpc', Math.random().toString(), 'testFunc', {
243
+ singletonServices: mockSingletonServices,
244
+ getAllServices: () => mockServices,
245
+ data: () => ({}),
246
+ auth: false,
247
+ wire: {},
248
+ }),
249
+ {
250
+ message: 'Permission denied',
251
+ }
252
+ )
253
+ })
254
+
255
+ test('should deduplicate middleware when same middleware appears in tags and direct', async () => {
256
+ let executionCount = 0
257
+
258
+ const duplicatedMiddleware: CorePikkuMiddleware = async (
259
+ services,
260
+ wire,
261
+ next
262
+ ) => {
263
+ executionCount++
264
+ await next()
265
+ }
266
+
267
+ // Add same middleware to tag and directly
268
+ addMiddleware('testTag', [duplicatedMiddleware])
269
+
270
+ addTestFunction('testFunc', {
271
+ func: async () => 'success',
272
+ middleware: [duplicatedMiddleware], // Same middleware directly
273
+ tags: ['testTag'], // Same middleware via tag
274
+ })
275
+
276
+ await runPikkuFunc('rpc', Math.random().toString(), 'testFunc', {
277
+ singletonServices: mockSingletonServices,
278
+ getAllServices: () => mockServices,
279
+ data: () => ({}),
280
+ auth: false,
281
+ wire: {},
282
+ })
283
+
284
+ // Should only execute once due to deduplication
285
+ assert.equal(executionCount, 1)
286
+ })
287
+
288
+ test('should handle mixed permission types correctly', async () => {
289
+ const executionOrder: string[] = []
290
+
291
+ // Mix of array and object permissions
292
+ const arrayPermission = [
293
+ async () => {
294
+ executionOrder.push('arrayPermission1')
295
+ return false
296
+ },
297
+ async () => {
298
+ executionOrder.push('arrayPermission2')
299
+ return true
300
+ },
301
+ ]
302
+
303
+ const objectPermission: CorePermissionGroup = {
304
+ permissions: [
305
+ async () => {
306
+ executionOrder.push('objectPermission')
307
+ return true
308
+ },
309
+ ],
310
+ }
311
+
312
+ addPermission('mixedTag', arrayPermission)
313
+
314
+ addTestFunction('testFunc', {
315
+ func: async () => {
316
+ executionOrder.push('main')
317
+ return 'success'
318
+ },
319
+ permissions: objectPermission,
320
+ tags: ['mixedTag'],
321
+ })
322
+
323
+ const result = await runPikkuFunc(
324
+ 'rpc',
325
+ Math.random().toString(),
326
+ 'testFunc',
327
+ {
328
+ singletonServices: mockSingletonServices,
329
+ getAllServices: () => mockServices,
330
+ data: () => ({}),
331
+ auth: false,
332
+ wire: {},
333
+ }
334
+ )
335
+
336
+ assert.equal(result, 'success')
337
+ // Should execute tag permissions and short-circuit before function permissions
338
+ assert.deepEqual(executionOrder, [
339
+ 'arrayPermission1',
340
+ 'arrayPermission2',
341
+ 'main',
342
+ ])
343
+ })
344
+
345
+ test('should work without any middleware or permissions', async () => {
346
+ addTestFunction('simpleFunc', {
347
+ func: async () => 'simple success',
348
+ })
349
+
350
+ const result = await runPikkuFunc(
351
+ 'rpc',
352
+ Math.random().toString(),
353
+ 'simpleFunc',
354
+ {
355
+ singletonServices: mockSingletonServices,
356
+ getAllServices: () => mockServices,
357
+ data: () => ({}),
358
+ auth: false,
359
+ wire: {},
360
+ }
361
+ )
362
+
363
+ assert.equal(result, 'simple success')
364
+ })
365
+
366
+ test('should work with only wiring-level middleware and permissions', async () => {
367
+ const executionOrder: string[] = []
368
+
369
+ const wiringMiddleware: CorePikkuMiddleware = async (
370
+ services,
371
+ wire,
372
+ next
373
+ ) => {
374
+ executionOrder.push('wiringMiddleware')
375
+ await next()
376
+ }
377
+
378
+ const wiringPermissions: CorePermissionGroup = {
379
+ permissions: [
380
+ async () => {
381
+ executionOrder.push('wiringPermission')
382
+ return true
383
+ },
384
+ ],
385
+ }
386
+
387
+ addTestFunction('testFunc', {
388
+ func: async () => {
389
+ executionOrder.push('main')
390
+ return 'success'
391
+ },
392
+ })
393
+
394
+ const result = await runPikkuFunc(
395
+ 'rpc',
396
+ Math.random().toString(),
397
+ 'testFunc',
398
+ {
399
+ singletonServices: mockSingletonServices,
400
+ getAllServices: () => mockServices,
401
+ data: () => ({}),
402
+ wireMiddleware: [wiringMiddleware],
403
+ wirePermissions: wiringPermissions,
404
+ auth: false,
405
+ wire: {},
406
+ }
407
+ )
408
+
409
+ assert.equal(result, 'success')
410
+ // Permissions run after middleware (middleware can set/modify session)
411
+ assert.deepEqual(executionOrder, [
412
+ 'wiringMiddleware',
413
+ 'wiringPermission',
414
+ 'main',
415
+ ])
416
+ })
417
+
418
+ test('should work with only function-level middleware and permissions', async () => {
419
+ const executionOrder: string[] = []
420
+
421
+ const funcMiddleware: CorePikkuMiddleware = async (
422
+ services,
423
+ wire,
424
+ next
425
+ ) => {
426
+ executionOrder.push('funcMiddleware')
427
+ await next()
428
+ }
429
+
430
+ const funcPermissions: CorePermissionGroup = {
431
+ permissions: [
432
+ async () => {
433
+ executionOrder.push('funcPermission')
434
+ return true
435
+ },
436
+ ],
437
+ }
438
+
439
+ addTestFunction('testFunc', {
440
+ func: async () => {
441
+ executionOrder.push('main')
442
+ return 'success'
443
+ },
444
+ middleware: [funcMiddleware],
445
+ permissions: funcPermissions,
446
+ })
447
+
448
+ const result = await runPikkuFunc(
449
+ 'rpc',
450
+ Math.random().toString(),
451
+ 'testFunc',
452
+ {
453
+ singletonServices: mockSingletonServices,
454
+ getAllServices: () => mockServices,
455
+ data: () => ({}),
456
+ auth: false,
457
+ wire: {},
458
+ }
459
+ )
460
+
461
+ assert.equal(result, 'success')
462
+ // Permissions run after middleware (middleware can set/modify session)
463
+ assert.deepEqual(executionOrder, [
464
+ 'funcMiddleware',
465
+ 'funcPermission',
466
+ 'main',
467
+ ])
468
+ })
469
+
470
+ test('should pass correct parameters to function', async () => {
471
+ let receivedServices: any
472
+ let receivedData: any
473
+ let receivedWire: any
474
+
475
+ const testData = { test: 'data' }
476
+
477
+ addTestFunction('testFunc', {
478
+ func: async (services, data, wire) => {
479
+ receivedServices = services
480
+ receivedData = data
481
+ receivedWire = wire
482
+ return 'success'
483
+ },
484
+ })
485
+
486
+ await runPikkuFunc('rpc', Math.random().toString(), 'testFunc', {
487
+ singletonServices: mockServices,
488
+ data: () => testData,
489
+ auth: false,
490
+ wire: { rpc: {} },
491
+ })
492
+
493
+ assert.deepEqual(receivedServices, mockServices)
494
+ assert.equal(receivedData, testData)
495
+ // Check that wire has rpc service and session
496
+ assert.ok(receivedWire.rpc)
497
+ assert.equal(receivedWire.session, undefined)
498
+ })
499
+
500
+ test('should handle async createWireServices function', async () => {
501
+ let servicesProvided: any
502
+ const wireServices = { customService: 'value' }
503
+
504
+ addTestFunction('testFunc', {
505
+ func: async (services, data, wire) => {
506
+ servicesProvided = services
507
+ return 'success'
508
+ },
509
+ })
510
+
511
+ const asyncCreateWireServices = async () => {
512
+ // Simulate async service creation
513
+ await new Promise((resolve) => setTimeout(resolve, 1))
514
+ return wireServices
515
+ }
516
+
517
+ const result = await runPikkuFunc(
518
+ 'rpc',
519
+ Math.random().toString(),
520
+ 'testFunc',
521
+ {
522
+ singletonServices: mockSingletonServices,
523
+ createWireServices: asyncCreateWireServices,
524
+ data: () => ({}),
525
+ auth: false,
526
+ wire: {},
527
+ }
528
+ )
529
+
530
+ assert.equal(result, 'success')
531
+ assert.deepEqual(servicesProvided, {
532
+ ...mockSingletonServices,
533
+ ...wireServices,
534
+ })
535
+ })
536
+ })