@pikku/core 0.11.0 → 0.11.2

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 (184) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/errors/error-handler.js +3 -3
  3. package/dist/errors/errors.d.ts +1 -1
  4. package/dist/errors/errors.js +3 -3
  5. package/dist/function/function-runner.d.ts +8 -8
  6. package/dist/function/function-runner.js +88 -23
  7. package/dist/function/functions.types.d.ts +104 -33
  8. package/dist/function/functions.types.js +50 -7
  9. package/dist/handle-error.d.ts +1 -1
  10. package/dist/handle-error.js +1 -1
  11. package/dist/index.d.ts +6 -2
  12. package/dist/index.js +5 -2
  13. package/dist/middleware/auth-apikey.d.ts +1 -1
  14. package/dist/middleware/auth-apikey.js +3 -3
  15. package/dist/middleware/auth-bearer.d.ts +1 -1
  16. package/dist/middleware/auth-bearer.js +3 -3
  17. package/dist/middleware/auth-cookie.d.ts +1 -1
  18. package/dist/middleware/auth-cookie.js +5 -5
  19. package/dist/middleware/timeout.d.ts +1 -1
  20. package/dist/middleware/timeout.js +3 -2
  21. package/dist/middleware-runner.d.ts +6 -20
  22. package/dist/middleware-runner.js +19 -20
  23. package/dist/permissions.d.ts +6 -89
  24. package/dist/permissions.js +24 -81
  25. package/dist/pikku-state.d.ts +32 -104
  26. package/dist/pikku-state.js +140 -66
  27. package/dist/schema.d.ts +8 -6
  28. package/dist/schema.js +25 -13
  29. package/dist/services/user-session-service.d.ts +7 -3
  30. package/dist/services/user-session-service.js +8 -1
  31. package/dist/services/workflow-service.d.ts +38 -0
  32. package/dist/services/workflow-service.js +1 -0
  33. package/dist/types/core.types.d.ts +40 -41
  34. package/dist/types/core.types.js +4 -15
  35. package/dist/types/state.types.d.ts +135 -0
  36. package/dist/types/state.types.js +1 -0
  37. package/dist/utils.d.ts +9 -1
  38. package/dist/utils.js +48 -3
  39. package/dist/wirings/channel/channel-common.js +2 -3
  40. package/dist/wirings/channel/channel-handler.d.ts +3 -2
  41. package/dist/wirings/channel/channel-handler.js +7 -10
  42. package/dist/wirings/channel/channel-runner.d.ts +2 -2
  43. package/dist/wirings/channel/channel-runner.js +5 -5
  44. package/dist/wirings/channel/channel.types.d.ts +10 -5
  45. package/dist/wirings/channel/local/local-channel-runner.d.ts +1 -1
  46. package/dist/wirings/channel/local/local-channel-runner.js +19 -26
  47. package/dist/wirings/channel/log-channels.js +1 -1
  48. package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +1 -1
  49. package/dist/wirings/channel/serverless/serverless-channel-runner.js +43 -48
  50. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  51. package/dist/wirings/cli/cli-runner.d.ts +5 -5
  52. package/dist/wirings/cli/cli-runner.js +20 -38
  53. package/dist/wirings/cli/cli.types.d.ts +16 -11
  54. package/dist/wirings/forge-node/forge-node.types.d.ts +120 -0
  55. package/dist/wirings/forge-node/forge-node.types.js +38 -0
  56. package/dist/wirings/forge-node/index.d.ts +1 -0
  57. package/dist/wirings/forge-node/index.js +1 -0
  58. package/dist/wirings/http/http-runner.d.ts +8 -8
  59. package/dist/wirings/http/http-runner.js +46 -51
  60. package/dist/wirings/http/http.types.d.ts +19 -11
  61. package/dist/wirings/http/log-http-routes.js +1 -1
  62. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  63. package/dist/wirings/mcp/mcp-runner.d.ts +2 -2
  64. package/dist/wirings/mcp/mcp-runner.js +33 -40
  65. package/dist/wirings/mcp/mcp.types.d.ts +6 -0
  66. package/dist/wirings/queue/queue-runner.d.ts +3 -3
  67. package/dist/wirings/queue/queue-runner.js +13 -23
  68. package/dist/wirings/queue/queue.types.d.ts +4 -11
  69. package/dist/wirings/rpc/index.d.ts +1 -1
  70. package/dist/wirings/rpc/index.js +1 -1
  71. package/dist/wirings/rpc/rpc-runner.d.ts +17 -16
  72. package/dist/wirings/rpc/rpc-runner.js +98 -61
  73. package/dist/wirings/rpc/rpc-types.d.ts +7 -0
  74. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  75. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -3
  76. package/dist/wirings/scheduler/scheduler-runner.js +18 -29
  77. package/dist/wirings/scheduler/scheduler.types.d.ts +3 -8
  78. package/dist/wirings/trigger/index.d.ts +2 -0
  79. package/dist/wirings/trigger/index.js +2 -0
  80. package/dist/wirings/trigger/trigger-runner.d.ts +29 -0
  81. package/dist/wirings/trigger/trigger-runner.js +57 -0
  82. package/dist/wirings/trigger/trigger.types.d.ts +42 -0
  83. package/dist/wirings/trigger/trigger.types.js +1 -0
  84. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  85. package/dist/wirings/workflow/dsl/index.js +4 -0
  86. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +286 -0
  87. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  88. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  89. package/dist/wirings/workflow/dsl/workflow-runner.js +22 -0
  90. package/dist/wirings/workflow/graph/graph-node.d.ts +122 -0
  91. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  92. package/dist/wirings/workflow/graph/graph-runner.d.ts +35 -0
  93. package/dist/wirings/workflow/graph/graph-runner.js +452 -0
  94. package/dist/wirings/workflow/graph/index.d.ts +3 -0
  95. package/dist/wirings/workflow/graph/index.js +3 -0
  96. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +95 -0
  97. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  98. package/dist/wirings/workflow/index.d.ts +7 -3
  99. package/dist/wirings/workflow/index.js +6 -3
  100. package/dist/wirings/workflow/pikku-workflow-service.d.ts +108 -10
  101. package/dist/wirings/workflow/pikku-workflow-service.js +274 -165
  102. package/dist/wirings/workflow/wire-workflow.d.ts +42 -0
  103. package/dist/wirings/workflow/wire-workflow.js +53 -0
  104. package/dist/wirings/workflow/workflow-utils.d.ts +23 -0
  105. package/dist/wirings/workflow/workflow-utils.js +66 -0
  106. package/dist/wirings/workflow/workflow.types.d.ts +135 -128
  107. package/package.json +11 -2
  108. package/src/errors/error-handler.ts +3 -3
  109. package/src/errors/errors.ts +3 -3
  110. package/src/factory-functions.test.ts +9 -36
  111. package/src/function/function-runner.test.ts +58 -69
  112. package/src/function/function-runner.ts +130 -37
  113. package/src/function/functions.types.ts +193 -54
  114. package/src/handle-error.ts +1 -1
  115. package/src/index.ts +10 -5
  116. package/src/middleware/auth-apikey.test.ts +360 -0
  117. package/src/middleware/auth-apikey.ts +3 -7
  118. package/src/middleware/auth-bearer.test.ts +447 -0
  119. package/src/middleware/auth-bearer.ts +27 -33
  120. package/src/middleware/auth-cookie.test.ts +525 -0
  121. package/src/middleware/auth-cookie.ts +6 -6
  122. package/src/middleware/timeout.ts +4 -2
  123. package/src/middleware-runner.test.ts +58 -127
  124. package/src/middleware-runner.ts +29 -25
  125. package/src/permissions.test.ts +68 -130
  126. package/src/permissions.ts +41 -100
  127. package/src/pikku-state.ts +156 -201
  128. package/src/schema.ts +42 -13
  129. package/src/services/user-session-service.ts +14 -4
  130. package/src/services/workflow-service.ts +74 -0
  131. package/src/types/core.types.ts +79 -58
  132. package/src/types/state.types.ts +195 -0
  133. package/src/utils.ts +59 -4
  134. package/src/wirings/channel/channel-common.ts +4 -7
  135. package/src/wirings/channel/channel-handler.ts +17 -23
  136. package/src/wirings/channel/channel-runner.ts +7 -7
  137. package/src/wirings/channel/channel.types.ts +14 -11
  138. package/src/wirings/channel/local/local-channel-runner.test.ts +8 -4
  139. package/src/wirings/channel/local/local-channel-runner.ts +23 -38
  140. package/src/wirings/channel/log-channels.ts +1 -1
  141. package/src/wirings/channel/serverless/serverless-channel-runner.ts +53 -79
  142. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  143. package/src/wirings/cli/cli-runner.test.ts +26 -26
  144. package/src/wirings/cli/cli-runner.ts +34 -71
  145. package/src/wirings/cli/cli.types.ts +23 -16
  146. package/src/wirings/forge-node/forge-node.types.ts +135 -0
  147. package/src/wirings/forge-node/index.ts +1 -0
  148. package/src/wirings/http/http-runner.test.ts +10 -10
  149. package/src/wirings/http/http-runner.ts +56 -66
  150. package/src/wirings/http/http.types.ts +19 -14
  151. package/src/wirings/http/log-http-routes.ts +1 -1
  152. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  153. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  154. package/src/wirings/mcp/mcp-runner.ts +44 -69
  155. package/src/wirings/mcp/mcp.types.ts +6 -0
  156. package/src/wirings/queue/queue-runner.test.ts +641 -0
  157. package/src/wirings/queue/queue-runner.ts +16 -38
  158. package/src/wirings/queue/queue.types.ts +4 -11
  159. package/src/wirings/rpc/index.ts +1 -1
  160. package/src/wirings/rpc/rpc-runner.ts +129 -77
  161. package/src/wirings/rpc/rpc-types.ts +8 -0
  162. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  163. package/src/wirings/scheduler/scheduler-runner.test.ts +627 -0
  164. package/src/wirings/scheduler/scheduler-runner.ts +29 -55
  165. package/src/wirings/scheduler/scheduler.types.ts +3 -9
  166. package/src/wirings/trigger/index.ts +2 -0
  167. package/src/wirings/trigger/trigger-runner.ts +96 -0
  168. package/src/wirings/trigger/trigger.types.ts +56 -0
  169. package/src/wirings/workflow/dsl/index.ts +30 -0
  170. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +312 -0
  171. package/src/wirings/workflow/dsl/workflow-runner.ts +27 -0
  172. package/src/wirings/workflow/graph/graph-node.ts +227 -0
  173. package/src/wirings/workflow/graph/graph-runner.ts +694 -0
  174. package/src/wirings/workflow/graph/index.ts +3 -0
  175. package/src/wirings/workflow/graph/workflow-graph.types.ts +126 -0
  176. package/src/wirings/workflow/index.ts +60 -12
  177. package/src/wirings/workflow/pikku-workflow-service.ts +401 -195
  178. package/src/wirings/workflow/wire-workflow.ts +94 -0
  179. package/src/wirings/workflow/workflow-utils.ts +120 -0
  180. package/src/wirings/workflow/workflow.types.ts +167 -188
  181. package/tsconfig.tsbuildinfo +1 -1
  182. package/dist/wirings/workflow/workflow-runner.d.ts +0 -35
  183. package/dist/wirings/workflow/workflow-runner.js +0 -68
  184. package/src/wirings/workflow/workflow-runner.ts +0 -85
@@ -0,0 +1,525 @@
1
+ import { describe, test, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { authCookie } from './auth-cookie.js'
4
+ import { resetPikkuState } from '../pikku-state.js'
5
+ import { CoreUserSession } from '../types/core.types.js'
6
+ import { PikkuSessionService } from '../services/user-session-service.js'
7
+
8
+ beforeEach(() => {
9
+ resetPikkuState()
10
+ })
11
+
12
+ const createMockHTTPRequest = (cookies: Record<string, string> = {}) => ({
13
+ cookie: (name: string) => cookies[name] || null,
14
+ })
15
+
16
+ const createMockHTTPResponse = () => {
17
+ const setCookies: Array<{
18
+ name: string
19
+ value: string
20
+ options: any
21
+ }> = []
22
+
23
+ return {
24
+ cookie: (name: string, value: string, options: any) => {
25
+ setCookies.push({ name, value, options })
26
+ },
27
+ getCookies: () => setCookies,
28
+ }
29
+ }
30
+
31
+ const createMockLogger = () => {
32
+ const logs: Array<{ level: string; message: string }> = []
33
+ return {
34
+ info: (msg: string) => logs.push({ level: 'info', message: msg }),
35
+ warn: (msg: string) => logs.push({ level: 'warn', message: msg }),
36
+ error: (msg: string) => logs.push({ level: 'error', message: msg }),
37
+ debug: (msg: string) => logs.push({ level: 'debug', message: msg }),
38
+ getLogs: () => logs,
39
+ }
40
+ }
41
+
42
+ describe('authCookie middleware', () => {
43
+ test('should decode session from cookie on request', async () => {
44
+ const mockUserSession: CoreUserSession = { userId: 'user123' }
45
+ const SessionService = new PikkuSessionService<CoreUserSession>()
46
+ const jwtService = {
47
+ encode: async () => 'encoded-jwt',
48
+ decode: async (token: string) => {
49
+ assert.equal(token, 'cookie-jwt-value')
50
+ return mockUserSession
51
+ },
52
+ }
53
+
54
+ const middleware = authCookie({
55
+ name: 'session',
56
+ expiresIn: { value: 30, unit: 'day' },
57
+ options: { httpOnly: true },
58
+ })
59
+ let nextCalled = false
60
+
61
+ const mockResponse = createMockHTTPResponse()
62
+
63
+ await middleware(
64
+ {
65
+ jwt: jwtService,
66
+ logger: createMockLogger(),
67
+ } as any,
68
+ {
69
+ session: SessionService,
70
+ http: {
71
+ request: createMockHTTPRequest({ session: 'cookie-jwt-value' }),
72
+ response: mockResponse,
73
+ },
74
+ } as any,
75
+ async () => {
76
+ nextCalled = true
77
+ }
78
+ )
79
+
80
+ assert.equal(nextCalled, true)
81
+ assert.deepEqual(SessionService.get(), mockUserSession)
82
+ })
83
+
84
+ test('should set cookie in response when session changes', async () => {
85
+ const mockUserSession: CoreUserSession = { userId: 'user456' }
86
+ const SessionService = new PikkuSessionService<CoreUserSession>()
87
+ const jwtService = {
88
+ encode: async (expiresIn: any, payload: any) => {
89
+ assert.deepEqual(expiresIn, { value: 7, unit: 'day' })
90
+ assert.deepEqual(payload, mockUserSession)
91
+ return 'new-encoded-jwt'
92
+ },
93
+ decode: async () => null,
94
+ }
95
+
96
+ const middleware = authCookie({
97
+ name: 'auth_token',
98
+ expiresIn: { value: 7, unit: 'day' },
99
+ options: { httpOnly: true, secure: true, sameSite: 'strict' },
100
+ })
101
+
102
+ const mockResponse = createMockHTTPResponse()
103
+
104
+ await middleware(
105
+ {
106
+ jwt: jwtService,
107
+ logger: createMockLogger(),
108
+ } as any,
109
+ {
110
+ session: SessionService,
111
+ http: {
112
+ request: createMockHTTPRequest({}),
113
+ response: mockResponse,
114
+ },
115
+ } as any,
116
+ async () => {
117
+ // Simulate session change
118
+ SessionService.set(mockUserSession)
119
+ }
120
+ )
121
+
122
+ const setCookies = mockResponse.getCookies()
123
+ assert.equal(setCookies.length, 1)
124
+ assert.equal(setCookies[0].name, 'auth_token')
125
+ assert.equal(setCookies[0].value, 'new-encoded-jwt')
126
+ assert.equal(setCookies[0].options.httpOnly, true)
127
+ assert.equal(setCookies[0].options.secure, true)
128
+ assert.equal(setCookies[0].options.sameSite, 'strict')
129
+ assert(setCookies[0].options.expires instanceof Date)
130
+ })
131
+
132
+ test('should not set cookie when session does not change', async () => {
133
+ const mockUserSession: CoreUserSession = { userId: 'user789' }
134
+ const SessionService = new PikkuSessionService<CoreUserSession>()
135
+ const jwtService = {
136
+ encode: async () => 'encoded-jwt',
137
+ decode: async () => mockUserSession,
138
+ }
139
+
140
+ const middleware = authCookie({
141
+ name: 'session',
142
+ expiresIn: { value: 30, unit: 'day' },
143
+ options: { httpOnly: true },
144
+ })
145
+
146
+ const mockResponse = createMockHTTPResponse()
147
+
148
+ await middleware(
149
+ {
150
+ jwt: jwtService,
151
+ logger: createMockLogger(),
152
+ } as any,
153
+ {
154
+ session: SessionService,
155
+ http: {
156
+ request: createMockHTTPRequest({ session: 'existing-jwt' }),
157
+ response: mockResponse,
158
+ },
159
+ } as any,
160
+ async () => {
161
+ // Session is loaded but not changed
162
+ }
163
+ )
164
+
165
+ const setCookies = mockResponse.getCookies()
166
+ assert.equal(setCookies.length, 0)
167
+ })
168
+
169
+ test('should not decode cookie when cookie is missing', async () => {
170
+ const SessionService = new PikkuSessionService<CoreUserSession>()
171
+ let decodeCalled = false
172
+ const jwtService = {
173
+ encode: async () => 'encoded-jwt',
174
+ decode: async () => {
175
+ decodeCalled = true
176
+ return { userId: 'test' }
177
+ },
178
+ }
179
+
180
+ const middleware = authCookie({
181
+ name: 'session',
182
+ expiresIn: { value: 30, unit: 'day' },
183
+ options: { httpOnly: true },
184
+ })
185
+ let nextCalled = false
186
+
187
+ const mockResponse = createMockHTTPResponse()
188
+
189
+ await middleware(
190
+ {
191
+ jwt: jwtService,
192
+ logger: createMockLogger(),
193
+ } as any,
194
+ {
195
+ session: SessionService,
196
+ http: {
197
+ request: createMockHTTPRequest({}),
198
+ response: mockResponse,
199
+ },
200
+ } as any,
201
+ async () => {
202
+ nextCalled = true
203
+ }
204
+ )
205
+
206
+ assert.equal(nextCalled, true)
207
+ assert.equal(decodeCalled, false)
208
+ assert.equal(SessionService.get(), undefined)
209
+ })
210
+
211
+ test('should not set session when JWT decode returns null', async () => {
212
+ const SessionService = new PikkuSessionService<CoreUserSession>()
213
+ const jwtService = {
214
+ encode: async () => 'encoded-jwt',
215
+ decode: async () => null,
216
+ }
217
+
218
+ const middleware = authCookie({
219
+ name: 'session',
220
+ expiresIn: { value: 30, unit: 'day' },
221
+ options: { httpOnly: true },
222
+ })
223
+ let nextCalled = false
224
+
225
+ const mockResponse = createMockHTTPResponse()
226
+
227
+ await middleware(
228
+ {
229
+ jwt: jwtService,
230
+ logger: createMockLogger(),
231
+ } as any,
232
+ {
233
+ session: SessionService,
234
+ http: {
235
+ request: createMockHTTPRequest({ session: 'invalid-jwt' }),
236
+ response: mockResponse,
237
+ },
238
+ } as any,
239
+ async () => {
240
+ nextCalled = true
241
+ }
242
+ )
243
+
244
+ assert.equal(nextCalled, true)
245
+ assert.equal(SessionService.get(), undefined)
246
+ })
247
+
248
+ test('should skip middleware when session already exists', async () => {
249
+ const existingSession: CoreUserSession = { userId: 'existing' }
250
+ const SessionService = new PikkuSessionService<CoreUserSession>()
251
+ SessionService.setInitial(existingSession)
252
+
253
+ let decodeCalled = false
254
+ const jwtService = {
255
+ encode: async () => 'encoded-jwt',
256
+ decode: async () => {
257
+ decodeCalled = true
258
+ return { userId: 'new' }
259
+ },
260
+ }
261
+
262
+ const middleware = authCookie({
263
+ name: 'session',
264
+ expiresIn: { value: 30, unit: 'day' },
265
+ options: { httpOnly: true },
266
+ })
267
+ let nextCalled = false
268
+
269
+ const mockResponse = createMockHTTPResponse()
270
+
271
+ await middleware(
272
+ {
273
+ jwt: jwtService,
274
+ logger: createMockLogger(),
275
+ } as any,
276
+ {
277
+ session: SessionService,
278
+ http: {
279
+ request: createMockHTTPRequest({ session: 'some-jwt' }),
280
+ response: mockResponse,
281
+ },
282
+ } as any,
283
+ async () => {
284
+ nextCalled = true
285
+ }
286
+ )
287
+
288
+ assert.equal(nextCalled, true)
289
+ assert.equal(decodeCalled, false)
290
+ assert.deepEqual(SessionService.get(), existingSession)
291
+ })
292
+
293
+ test('should skip middleware when http.request is not available', async () => {
294
+ const SessionService = new PikkuSessionService<CoreUserSession>()
295
+ const jwtService = {
296
+ encode: async () => 'encoded-jwt',
297
+ decode: async () => ({ userId: 'test' }),
298
+ }
299
+
300
+ const middleware = authCookie({
301
+ name: 'session',
302
+ expiresIn: { value: 30, unit: 'day' },
303
+ options: { httpOnly: true },
304
+ })
305
+ let nextCalled = false
306
+
307
+ await middleware(
308
+ {
309
+ jwt: jwtService,
310
+ logger: createMockLogger(),
311
+ } as any,
312
+ { session: SessionService } as any,
313
+ async () => {
314
+ nextCalled = true
315
+ }
316
+ )
317
+
318
+ assert.equal(nextCalled, true)
319
+ assert.equal(SessionService.get(), undefined)
320
+ })
321
+
322
+ test('should not decode when jwtService is not available on request', async () => {
323
+ const SessionService = new PikkuSessionService<CoreUserSession>()
324
+
325
+ const middleware = authCookie({
326
+ name: 'session',
327
+ expiresIn: { value: 30, unit: 'day' },
328
+ options: { httpOnly: true },
329
+ })
330
+ let nextCalled = false
331
+
332
+ const mockResponse = createMockHTTPResponse()
333
+
334
+ await middleware(
335
+ {
336
+ jwt: undefined,
337
+ logger: createMockLogger(),
338
+ } as any,
339
+ {
340
+ session: SessionService,
341
+ http: {
342
+ request: createMockHTTPRequest({ session: 'some-jwt' }),
343
+ response: mockResponse,
344
+ },
345
+ } as any,
346
+ async () => {
347
+ nextCalled = true
348
+ }
349
+ )
350
+
351
+ assert.equal(nextCalled, true)
352
+ assert.equal(SessionService.get(), undefined)
353
+ })
354
+
355
+ test('should warn when jwtService is not available on response and session changed', async () => {
356
+ const mockUserSession: CoreUserSession = { userId: 'user999' }
357
+ const SessionService = new PikkuSessionService<CoreUserSession>()
358
+ const mockLogger = createMockLogger()
359
+
360
+ const middleware = authCookie({
361
+ name: 'session',
362
+ expiresIn: { value: 30, unit: 'day' },
363
+ options: { httpOnly: true },
364
+ })
365
+
366
+ const mockResponse = createMockHTTPResponse()
367
+
368
+ await middleware(
369
+ {
370
+ jwt: undefined,
371
+ logger: mockLogger,
372
+ } as any,
373
+ {
374
+ session: SessionService,
375
+ http: {
376
+ request: createMockHTTPRequest({}),
377
+ response: mockResponse,
378
+ },
379
+ } as any,
380
+ async () => {
381
+ // Simulate session change
382
+ SessionService.set(mockUserSession)
383
+ }
384
+ )
385
+
386
+ const logs = mockLogger.getLogs()
387
+ assert.equal(logs.length, 1)
388
+ assert.equal(logs[0].level, 'warn')
389
+ assert.equal(
390
+ logs[0].message,
391
+ 'No JWT service available, unable to set cookie'
392
+ )
393
+
394
+ const setCookies = mockResponse.getCookies()
395
+ assert.equal(setCookies.length, 0)
396
+ })
397
+
398
+ test('should not set cookie when http.response is not available', async () => {
399
+ const mockUserSession: CoreUserSession = { userId: 'user888' }
400
+ const SessionService = new PikkuSessionService<CoreUserSession>()
401
+ const jwtService = {
402
+ encode: async () => 'encoded-jwt',
403
+ decode: async () => null,
404
+ }
405
+
406
+ const middleware = authCookie({
407
+ name: 'session',
408
+ expiresIn: { value: 30, unit: 'day' },
409
+ options: { httpOnly: true },
410
+ })
411
+
412
+ await middleware(
413
+ {
414
+ jwt: jwtService,
415
+ logger: createMockLogger(),
416
+ } as any,
417
+ {
418
+ session: SessionService,
419
+ http: {
420
+ request: createMockHTTPRequest({}),
421
+ response: undefined,
422
+ },
423
+ } as any,
424
+ async () => {
425
+ // Simulate session change
426
+ SessionService.set(mockUserSession)
427
+ }
428
+ )
429
+
430
+ // Should complete without errors even though response is not available
431
+ assert.equal(SessionService.get(), mockUserSession)
432
+ })
433
+
434
+ test('should handle custom cookie name', async () => {
435
+ const mockUserSession: CoreUserSession = { userId: 'custom-user' }
436
+ const SessionService = new PikkuSessionService<CoreUserSession>()
437
+ const jwtService = {
438
+ encode: async () => 'custom-encoded-jwt',
439
+ decode: async (token: string) => {
440
+ assert.equal(token, 'custom-cookie-value')
441
+ return mockUserSession
442
+ },
443
+ }
444
+
445
+ const middleware = authCookie({
446
+ name: 'my_custom_cookie',
447
+ expiresIn: { value: 1, unit: 'hour' },
448
+ options: {},
449
+ })
450
+
451
+ const mockResponse = createMockHTTPResponse()
452
+
453
+ await middleware(
454
+ {
455
+ jwt: jwtService,
456
+ logger: createMockLogger(),
457
+ } as any,
458
+ {
459
+ session: SessionService,
460
+ http: {
461
+ request: createMockHTTPRequest({
462
+ my_custom_cookie: 'custom-cookie-value',
463
+ }),
464
+ response: mockResponse,
465
+ },
466
+ } as any,
467
+ async () => {
468
+ // Modify session to trigger cookie setting
469
+ SessionService.set({ ...mockUserSession, extra: 'data' })
470
+ }
471
+ )
472
+
473
+ const setCookies = mockResponse.getCookies()
474
+ assert.equal(setCookies.length, 1)
475
+ assert.equal(setCookies[0].name, 'my_custom_cookie')
476
+ })
477
+
478
+ test('should properly set expiration time in cookie options', async () => {
479
+ const mockUserSession: CoreUserSession = { userId: 'expiry-test' }
480
+ const SessionService = new PikkuSessionService<CoreUserSession>()
481
+ const jwtService = {
482
+ encode: async () => 'encoded-jwt',
483
+ decode: async () => null,
484
+ }
485
+
486
+ const middleware = authCookie({
487
+ name: 'session',
488
+ expiresIn: { value: 15, unit: 'minute' },
489
+ options: { httpOnly: true },
490
+ })
491
+
492
+ const mockResponse = createMockHTTPResponse()
493
+ const beforeTime = new Date()
494
+
495
+ await middleware(
496
+ {
497
+ jwt: jwtService,
498
+ logger: createMockLogger(),
499
+ } as any,
500
+ {
501
+ session: SessionService,
502
+ http: {
503
+ request: createMockHTTPRequest({}),
504
+ response: mockResponse,
505
+ },
506
+ } as any,
507
+ async () => {
508
+ SessionService.set(mockUserSession)
509
+ }
510
+ )
511
+
512
+ const afterTime = new Date()
513
+ const setCookies = mockResponse.getCookies()
514
+ assert.equal(setCookies.length, 1)
515
+
516
+ const expiresDate = setCookies[0].options.expires
517
+ assert(expiresDate instanceof Date)
518
+
519
+ // Expiration should be approximately 15 minutes from now
520
+ const expectedExpiry = new Date(beforeTime.getTime() + 15 * 60 * 1000)
521
+ const timeDiff = Math.abs(expiresDate.getTime() - expectedExpiry.getTime())
522
+ // Allow 5 second tolerance for test execution time
523
+ assert(timeDiff < 5000, `Time difference ${timeDiff}ms is too large`)
524
+ })
525
+ })
@@ -40,11 +40,11 @@ export const authCookie = pikkuMiddlewareFactory<{
40
40
  }>(({ name, options, expiresIn }) =>
41
41
  pikkuMiddleware(
42
42
  async (
43
- { userSession: userSessionService, jwt: jwtService, logger },
44
- { http },
43
+ { jwt: jwtService, logger },
44
+ { http, session: SessionService },
45
45
  next
46
46
  ) => {
47
- if (!http?.request || userSessionService.get()) {
47
+ if (!http?.request || !SessionService || SessionService.get()) {
48
48
  return next()
49
49
  }
50
50
 
@@ -53,7 +53,7 @@ export const authCookie = pikkuMiddlewareFactory<{
53
53
  if (cookieValue && jwtService) {
54
54
  const userSession = await jwtService.decode(cookieValue)
55
55
  if (userSession) {
56
- userSessionService.setInitial(userSession)
56
+ SessionService.setInitial(userSession)
57
57
  }
58
58
  }
59
59
 
@@ -64,8 +64,8 @@ export const authCookie = pikkuMiddlewareFactory<{
64
64
  return
65
65
  }
66
66
 
67
- if (userSessionService.sessionChanged) {
68
- const session = userSessionService.get()
67
+ if (SessionService.sessionChanged) {
68
+ const session = SessionService.get()
69
69
  if (jwtService) {
70
70
  http.response.cookie(
71
71
  name,
@@ -3,11 +3,13 @@ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
3
3
 
4
4
  export const timeout = () =>
5
5
  pikkuMiddlewareFactory<{ timeout: number }>(({ timeout }) =>
6
- pikkuMiddleware(async (_services, _interaction, next) => {
7
- setTimeout(() => {
6
+ pikkuMiddleware(async (_services, _wire, next) => {
7
+ const timeoutId = setTimeout(() => {
8
8
  throw new RequestTimeoutError()
9
9
  }, timeout)
10
10
 
11
11
  await next()
12
+
13
+ clearTimeout(timeoutId)
12
14
  })
13
15
  )