@pikku/core 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/function/function-runner.js +2 -2
- package/dist/function/functions.types.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/middleware-runner.js +1 -0
- package/dist/permissions.js +1 -0
- package/dist/pikku-state.d.ts +7 -2
- package/dist/pikku-state.js +4 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/scheduler-service.d.ts +63 -0
- package/dist/services/scheduler-service.js +6 -0
- package/dist/time-utils.d.ts +14 -0
- package/dist/time-utils.js +62 -0
- package/dist/types/core.types.d.ts +24 -2
- package/dist/types/core.types.js +1 -0
- package/dist/wirings/channel/channel-common.d.ts +28 -0
- package/dist/wirings/channel/channel-common.js +42 -0
- package/dist/wirings/channel/channel-handler.js +37 -11
- package/dist/wirings/channel/channel-runner.js +9 -4
- package/dist/wirings/channel/channel.types.d.ts +8 -2
- package/dist/wirings/channel/local/local-channel-handler.js +3 -0
- package/dist/wirings/channel/local/local-channel-runner.js +47 -14
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +90 -41
- package/dist/wirings/cli/channel/cli-channel-runner.js +10 -1
- package/dist/wirings/cli/cli-runner.d.ts +1 -1
- package/dist/wirings/cli/cli-runner.js +1 -1
- package/dist/wirings/http/http-runner.js +0 -1
- package/dist/wirings/mcp/mcp-runner.js +3 -2
- package/dist/wirings/queue/queue-runner.js +6 -3
- package/dist/wirings/queue/queue.types.d.ts +1 -3
- package/dist/wirings/rpc/index.d.ts +1 -1
- package/dist/wirings/rpc/index.js +1 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +15 -0
- package/dist/wirings/rpc/rpc-runner.js +38 -3
- package/dist/wirings/rpc/rpc-types.d.ts +1 -0
- package/dist/wirings/workflow/index.d.ts +6 -0
- package/dist/wirings/workflow/index.js +6 -0
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +152 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +448 -0
- package/dist/wirings/workflow/workflow-runner.d.ts +35 -0
- package/dist/wirings/workflow/workflow-runner.js +68 -0
- package/dist/wirings/workflow/workflow.types.d.ts +247 -0
- package/dist/wirings/workflow/workflow.types.js +1 -0
- package/package.json +2 -3
- package/src/function/function-runner.ts +2 -2
- package/src/function/functions.types.ts +1 -0
- package/src/index.ts +2 -0
- package/src/middleware-runner.ts +1 -0
- package/src/permissions.ts +1 -0
- package/src/pikku-state.ts +14 -2
- package/src/services/index.ts +1 -0
- package/src/services/scheduler-service.ts +76 -0
- package/src/time-utils.ts +75 -0
- package/src/types/core.types.ts +30 -1
- package/src/wirings/channel/channel-common.ts +80 -0
- package/src/wirings/channel/channel-handler.ts +48 -18
- package/src/wirings/channel/channel-runner.ts +15 -4
- package/src/wirings/channel/channel.types.ts +12 -2
- package/src/wirings/channel/local/local-channel-handler.ts +3 -0
- package/src/wirings/channel/local/local-channel-runner.ts +48 -36
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +134 -66
- package/src/wirings/cli/channel/cli-channel-runner.ts +13 -1
- package/src/wirings/cli/cli-runner.ts +2 -2
- package/src/wirings/http/http-runner.ts +0 -2
- package/src/wirings/mcp/mcp-runner.ts +5 -2
- package/src/wirings/queue/queue-runner.ts +14 -6
- package/src/wirings/queue/queue.types.ts +1 -4
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +57 -4
- package/src/wirings/rpc/rpc-types.ts +1 -0
- package/src/wirings/workflow/index.ts +22 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +756 -0
- package/src/wirings/workflow/workflow-runner.ts +85 -0
- package/src/wirings/workflow/workflow.types.ts +332 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CoreServices,
|
|
3
|
+
PikkuWiringTypes,
|
|
4
|
+
CorePikkuMiddleware,
|
|
5
|
+
} from '../../types/core.types.js'
|
|
6
|
+
import { CoreChannel, ChannelMessageMeta } from './channel.types.js'
|
|
7
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
|
|
8
|
+
import { runPikkuFuncDirectly } from '../../function/function-runner.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Runs a channel lifecycle function (onConnect or onDisconnect) with proper middleware handling.
|
|
12
|
+
*
|
|
13
|
+
* This function:
|
|
14
|
+
* 1. Extracts inline middleware from the lifecycle config if present
|
|
15
|
+
* 2. Combines metadata middleware with inline middleware using combineMiddleware()
|
|
16
|
+
* 3. Runs the lifecycle function with middleware support
|
|
17
|
+
*
|
|
18
|
+
* @param channelConfig - The channel configuration
|
|
19
|
+
* @param meta - Metadata for the lifecycle function (connect or disconnect)
|
|
20
|
+
* @param lifecycleConfig - The onConnect or onDisconnect config (can be function or object with middleware)
|
|
21
|
+
* @param lifecycleType - Type of lifecycle for cache key ('connect' or 'disconnect')
|
|
22
|
+
* @param services - All services (singleton + session)
|
|
23
|
+
* @param channel - The channel instance
|
|
24
|
+
* @param data - Optional data to pass to the lifecycle function (for onConnect)
|
|
25
|
+
* @returns Promise<unknown> - Result from the lifecycle function (if any)
|
|
26
|
+
*/
|
|
27
|
+
export const runChannelLifecycleWithMiddleware = async ({
|
|
28
|
+
channelConfig,
|
|
29
|
+
meta,
|
|
30
|
+
lifecycleConfig,
|
|
31
|
+
lifecycleType,
|
|
32
|
+
services,
|
|
33
|
+
channel,
|
|
34
|
+
data,
|
|
35
|
+
}: {
|
|
36
|
+
channelConfig: CoreChannel<unknown, any>
|
|
37
|
+
meta: ChannelMessageMeta
|
|
38
|
+
lifecycleConfig: any
|
|
39
|
+
lifecycleType: 'connect' | 'disconnect'
|
|
40
|
+
services: CoreServices
|
|
41
|
+
channel: any
|
|
42
|
+
data?: unknown
|
|
43
|
+
}): Promise<unknown> => {
|
|
44
|
+
// Extract middleware if lifecycle config is an object
|
|
45
|
+
const lifecycleMiddleware =
|
|
46
|
+
typeof lifecycleConfig === 'object' && 'middleware' in lifecycleConfig
|
|
47
|
+
? (lifecycleConfig.middleware as CorePikkuMiddleware[]) || []
|
|
48
|
+
: []
|
|
49
|
+
|
|
50
|
+
// Use combineMiddleware to properly resolve metadata + inline middleware
|
|
51
|
+
const allMiddleware = combineMiddleware(
|
|
52
|
+
PikkuWiringTypes.channel,
|
|
53
|
+
`${channelConfig.name}:${lifecycleType}`,
|
|
54
|
+
{
|
|
55
|
+
wireInheritedMiddleware: meta.middleware,
|
|
56
|
+
wireMiddleware: lifecycleMiddleware,
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Run the lifecycle function
|
|
61
|
+
const runLifecycle = async () => {
|
|
62
|
+
return await runPikkuFuncDirectly(
|
|
63
|
+
meta.pikkuFuncName,
|
|
64
|
+
{ ...services, channel },
|
|
65
|
+
data
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Run with middleware if any
|
|
70
|
+
if (allMiddleware.length > 0) {
|
|
71
|
+
return await runMiddleware(
|
|
72
|
+
services,
|
|
73
|
+
{ channel },
|
|
74
|
+
allMiddleware,
|
|
75
|
+
runLifecycle
|
|
76
|
+
)
|
|
77
|
+
} else {
|
|
78
|
+
return await runLifecycle()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -87,19 +87,43 @@ export const processMessageHandlers = (
|
|
|
87
87
|
|
|
88
88
|
const {
|
|
89
89
|
pikkuFuncName,
|
|
90
|
-
middleware:
|
|
90
|
+
middleware: routeInheritedMiddleware,
|
|
91
91
|
permissions: inheritedPermissions,
|
|
92
92
|
} = getRouteMeta(channelConfig.name, routingProperty, routerValue)
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// Get wire middleware: channel-level middleware + message-specific middleware
|
|
95
|
+
const channelWireMiddleware = channelConfig.middleware || []
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// Check if onMessage is a wrapper object vs direct function config:
|
|
98
|
+
// - Direct config: onMessage.func is a plain Function
|
|
99
|
+
// - Wrapper: onMessage.func is a CorePikkuFunctionConfig (has onMessage.func.func)
|
|
100
|
+
// - Simple wrapper: onMessage has both func (plain Function) and middleware properties
|
|
101
|
+
const isWrapper =
|
|
102
|
+
onMessage &&
|
|
103
|
+
typeof onMessage === 'object' &&
|
|
104
|
+
'func' in onMessage &&
|
|
105
|
+
((typeof onMessage.func === 'object' && 'func' in onMessage.func) ||
|
|
106
|
+
'middleware' in onMessage)
|
|
107
|
+
const messageWireMiddleware = isWrapper ? onMessage.middleware || [] : []
|
|
108
|
+
|
|
109
|
+
// Combine channel middleware with message middleware (actual functions)
|
|
110
|
+
// Channel middleware runs first, then message middleware
|
|
111
|
+
const wireMiddleware = [...channelWireMiddleware, ...messageWireMiddleware]
|
|
112
|
+
|
|
113
|
+
// Inherited middleware comes from metadata (tag groups, non-inline wire)
|
|
114
|
+
const inheritedMiddleware = routeInheritedMiddleware || []
|
|
115
|
+
|
|
116
|
+
const wirePermissions = isWrapper ? onMessage.permissions : undefined
|
|
117
|
+
|
|
118
|
+
// Create unique cache key that includes routing info to avoid cache collisions
|
|
119
|
+
// when multiple message handlers use the same function
|
|
120
|
+
const cacheKey = routingProperty
|
|
121
|
+
? `${channelConfig.name}:${routingProperty}:${routerValue}`
|
|
122
|
+
: `${channelConfig.name}:default`
|
|
99
123
|
|
|
100
124
|
return await runPikkuFunc(
|
|
101
125
|
PikkuWiringTypes.channel,
|
|
102
|
-
|
|
126
|
+
cacheKey,
|
|
103
127
|
pikkuFuncName,
|
|
104
128
|
{
|
|
105
129
|
singletonServices: services,
|
|
@@ -119,25 +143,35 @@ export const processMessageHandlers = (
|
|
|
119
143
|
)
|
|
120
144
|
}
|
|
121
145
|
|
|
122
|
-
|
|
146
|
+
return async (rawData): Promise<unknown> => {
|
|
123
147
|
let result: unknown
|
|
124
148
|
let processed = false
|
|
125
149
|
|
|
126
150
|
// Route-specific handling
|
|
127
151
|
if (typeof rawData === 'string' && channelConfig.onMessageWiring) {
|
|
152
|
+
let messageData: any
|
|
128
153
|
try {
|
|
129
|
-
|
|
154
|
+
messageData = JSON.parse(rawData)
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Most likely a json error.. ignore
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (messageData) {
|
|
130
160
|
const entries = Object.entries(channelConfig.onMessageWiring)
|
|
131
161
|
for (const [routingProperty, routes] of entries) {
|
|
132
162
|
const routerValue = messageData[routingProperty]
|
|
133
163
|
if (routerValue && routes[routerValue]) {
|
|
164
|
+
const { [routingProperty]: _, ...data } = messageData
|
|
165
|
+
|
|
134
166
|
processed = true
|
|
135
|
-
result =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
167
|
+
result =
|
|
168
|
+
(await processMessage(
|
|
169
|
+
data as any,
|
|
170
|
+
routes[routerValue],
|
|
171
|
+
routingProperty,
|
|
172
|
+
routerValue
|
|
173
|
+
)) || {}
|
|
174
|
+
;(result as any)[routingProperty] = routerValue
|
|
141
175
|
break
|
|
142
176
|
}
|
|
143
177
|
}
|
|
@@ -147,8 +181,6 @@ export const processMessageHandlers = (
|
|
|
147
181
|
processed = true
|
|
148
182
|
result = await processMessage(messageData, channelConfig.onMessage)
|
|
149
183
|
}
|
|
150
|
-
} catch (error) {
|
|
151
|
-
// Most likely a json error.. ignore
|
|
152
184
|
}
|
|
153
185
|
}
|
|
154
186
|
|
|
@@ -167,6 +199,4 @@ export const processMessageHandlers = (
|
|
|
167
199
|
|
|
168
200
|
return result
|
|
169
201
|
}
|
|
170
|
-
|
|
171
|
-
return onMessage
|
|
172
202
|
}
|
|
@@ -56,7 +56,12 @@ export const wireChannel = <
|
|
|
56
56
|
|
|
57
57
|
// Register onMessage function if provided
|
|
58
58
|
if (channel.onMessage && channelMeta.message?.pikkuFuncName) {
|
|
59
|
-
addFunction(
|
|
59
|
+
addFunction(
|
|
60
|
+
channelMeta.message.pikkuFuncName,
|
|
61
|
+
(channel.onMessage as any).func instanceof Function
|
|
62
|
+
? channel.onMessage
|
|
63
|
+
: (channel.onMessage as any).func
|
|
64
|
+
)
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
// Register functions in onMessageWiring
|
|
@@ -72,7 +77,13 @@ export const wireChannel = <
|
|
|
72
77
|
if (!wiringMeta) return
|
|
73
78
|
|
|
74
79
|
// Register the function using the pikku name from metadata
|
|
75
|
-
|
|
80
|
+
// It could be a FuncConfig or a wiring override with a funcConfig
|
|
81
|
+
addFunction(
|
|
82
|
+
wiringMeta.pikkuFuncName,
|
|
83
|
+
(handler as any).func instanceof Function
|
|
84
|
+
? handler
|
|
85
|
+
: (handler as any).func
|
|
86
|
+
)
|
|
76
87
|
})
|
|
77
88
|
})
|
|
78
89
|
}
|
|
@@ -81,8 +92,8 @@ export const wireChannel = <
|
|
|
81
92
|
pikkuState('channel', 'channels').set(channel.name, channel as any)
|
|
82
93
|
}
|
|
83
94
|
|
|
84
|
-
const getMatchingChannelConfig = (
|
|
85
|
-
const matchedPath = httpRouter.match('get',
|
|
95
|
+
const getMatchingChannelConfig = (path: string) => {
|
|
96
|
+
const matchedPath = httpRouter.match('get', path)
|
|
86
97
|
if (!matchedPath) {
|
|
87
98
|
return null
|
|
88
99
|
}
|
|
@@ -88,8 +88,18 @@ export type CoreChannel<
|
|
|
88
88
|
> = {
|
|
89
89
|
name: string
|
|
90
90
|
route: Channel
|
|
91
|
-
onConnect?:
|
|
92
|
-
|
|
91
|
+
onConnect?:
|
|
92
|
+
| ChannelConnect
|
|
93
|
+
| {
|
|
94
|
+
func?: ChannelConnect
|
|
95
|
+
middleware?: PikkuMiddleware[]
|
|
96
|
+
}
|
|
97
|
+
onDisconnect?:
|
|
98
|
+
| ChannelDisconnect
|
|
99
|
+
| {
|
|
100
|
+
func?: ChannelDisconnect
|
|
101
|
+
middleware?: PikkuMiddleware[]
|
|
102
|
+
}
|
|
93
103
|
onMessage?: ChannelFunctionMessage
|
|
94
104
|
onMessageWiring?: Record<
|
|
95
105
|
string,
|
|
@@ -8,17 +8,12 @@ import {
|
|
|
8
8
|
RunChannelParams,
|
|
9
9
|
} from '../channel.types.js'
|
|
10
10
|
import { PikkuLocalChannelHandler } from './local-channel-handler.js'
|
|
11
|
-
import {
|
|
12
|
-
PikkuInteraction,
|
|
13
|
-
PikkuWiringTypes,
|
|
14
|
-
SessionServices,
|
|
15
|
-
} from '../../../types/core.types.js'
|
|
11
|
+
import { PikkuInteraction, SessionServices } from '../../../types/core.types.js'
|
|
16
12
|
import { handleHTTPError } from '../../../handle-error.js'
|
|
17
|
-
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
|
|
18
13
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js'
|
|
19
14
|
import { PikkuHTTP } from '../../http/http.types.js'
|
|
20
|
-
import { runPikkuFuncDirectly } from '../../../function/function-runner.js'
|
|
21
15
|
import { rpcService } from '../../rpc/rpc-runner.js'
|
|
16
|
+
import { runChannelLifecycleWithMiddleware } from '../channel-common.js'
|
|
22
17
|
|
|
23
18
|
export const runLocalChannel = async ({
|
|
24
19
|
singletonServices,
|
|
@@ -95,6 +90,7 @@ export const runLocalChannel = async ({
|
|
|
95
90
|
{
|
|
96
91
|
...singletonServices,
|
|
97
92
|
...sessionServices,
|
|
93
|
+
channel,
|
|
98
94
|
userSession,
|
|
99
95
|
},
|
|
100
96
|
interaction,
|
|
@@ -103,23 +99,43 @@ export const runLocalChannel = async ({
|
|
|
103
99
|
|
|
104
100
|
const interaction: PikkuInteraction = { channel }
|
|
105
101
|
|
|
106
|
-
channelHandler.registerOnOpen(() => {
|
|
102
|
+
channelHandler.registerOnOpen(async () => {
|
|
107
103
|
if (channelConfig.onConnect && meta.connect) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
try {
|
|
105
|
+
const result = await runChannelLifecycleWithMiddleware({
|
|
106
|
+
channelConfig,
|
|
107
|
+
meta: meta.connect,
|
|
108
|
+
lifecycleConfig: channelConfig.onConnect,
|
|
109
|
+
lifecycleType: 'connect',
|
|
110
|
+
services: getAllServices(channel, false),
|
|
111
|
+
channel,
|
|
112
|
+
data: openingData,
|
|
113
|
+
})
|
|
114
|
+
if (result !== undefined) {
|
|
115
|
+
await channel.send(result)
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
singletonServices.logger.error(`Error handling onConnect: ${e}`)
|
|
119
|
+
channel.send({ error: e.message || 'Unknown error' })
|
|
120
|
+
}
|
|
113
121
|
}
|
|
114
122
|
})
|
|
115
123
|
|
|
116
124
|
channelHandler.registerOnClose(async () => {
|
|
117
125
|
if (channelConfig.onDisconnect && meta.disconnect) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
try {
|
|
127
|
+
await runChannelLifecycleWithMiddleware({
|
|
128
|
+
channelConfig,
|
|
129
|
+
meta: meta.disconnect,
|
|
130
|
+
lifecycleConfig: channelConfig.onDisconnect,
|
|
131
|
+
lifecycleType: 'disconnect',
|
|
132
|
+
services: getAllServices(channel, false),
|
|
133
|
+
channel,
|
|
134
|
+
})
|
|
135
|
+
} catch (e) {
|
|
136
|
+
singletonServices.logger.error(`Error handling onDisconnect: ${e}`)
|
|
137
|
+
channel.send({ error: e.message || 'Unknown error' })
|
|
138
|
+
}
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
if (sessionServices) {
|
|
@@ -127,13 +143,20 @@ export const runLocalChannel = async ({
|
|
|
127
143
|
}
|
|
128
144
|
})
|
|
129
145
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
channelHandler
|
|
135
|
-
)
|
|
146
|
+
const onMessage = processMessageHandlers(
|
|
147
|
+
getAllServices(channel),
|
|
148
|
+
channelConfig as any,
|
|
149
|
+
channelHandler
|
|
136
150
|
)
|
|
151
|
+
channelHandler.registerOnMessage(async (data) => {
|
|
152
|
+
try {
|
|
153
|
+
const result = await onMessage(data)
|
|
154
|
+
await channel.send(result)
|
|
155
|
+
} catch (e) {
|
|
156
|
+
singletonServices.logger.error(`Error handling message: ${e.message}`)
|
|
157
|
+
channel.send({ error: e.message || 'Unknown error' })
|
|
158
|
+
}
|
|
159
|
+
})
|
|
137
160
|
} catch (e: any) {
|
|
138
161
|
handleHTTPError(
|
|
139
162
|
e,
|
|
@@ -151,18 +174,7 @@ export const runLocalChannel = async ({
|
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
176
|
|
|
154
|
-
await
|
|
155
|
-
{
|
|
156
|
-
...singletonServices,
|
|
157
|
-
userSession,
|
|
158
|
-
},
|
|
159
|
-
{ http },
|
|
160
|
-
combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
161
|
-
wireInheritedMiddleware: meta.middleware,
|
|
162
|
-
wireMiddleware: channelConfig.middleware,
|
|
163
|
-
}),
|
|
164
|
-
main
|
|
165
|
-
)
|
|
177
|
+
await main()
|
|
166
178
|
|
|
167
179
|
return channelHandler
|
|
168
180
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PikkuInteraction, SessionServices } from '../../../types/core.types.js'
|
|
2
2
|
import { closeSessionServices } from '../../../utils.js'
|
|
3
3
|
import { processMessageHandlers } from '../channel-handler.js'
|
|
4
4
|
import { openChannel } from '../channel-runner.js'
|
|
@@ -12,11 +12,11 @@ import { createHTTPInteraction } from '../../http/http-runner.js'
|
|
|
12
12
|
import { ChannelStore } from '../channel-store.js'
|
|
13
13
|
import { handleHTTPError } from '../../../handle-error.js'
|
|
14
14
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js'
|
|
15
|
-
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
|
|
16
15
|
import { pikkuState } from '../../../pikku-state.js'
|
|
17
16
|
import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js'
|
|
18
17
|
import { PikkuHTTP } from '../../http/http.types.js'
|
|
19
|
-
import {
|
|
18
|
+
import { runChannelLifecycleWithMiddleware } from '../channel-common.js'
|
|
19
|
+
import { rpcService } from '../../rpc/rpc-runner.js'
|
|
20
20
|
|
|
21
21
|
export interface RunServerlessChannelParams<ChannelData>
|
|
22
22
|
extends RunChannelParams<ChannelData> {
|
|
@@ -37,7 +37,7 @@ const getVariablesForChannel = ({
|
|
|
37
37
|
openingData?: unknown
|
|
38
38
|
}) => {
|
|
39
39
|
const channels = pikkuState('channel', 'channels')
|
|
40
|
-
const channelConfig = channels
|
|
40
|
+
const channelConfig = channels.get(channelName)
|
|
41
41
|
const channelsMeta = pikkuState('channel', 'meta')
|
|
42
42
|
const meta = channelsMeta[channelName]
|
|
43
43
|
if (!channelConfig) {
|
|
@@ -95,63 +95,66 @@ export const runChannelConnect = async ({
|
|
|
95
95
|
userSession,
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
await userSession.get()
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
if (channelConfig.onConnect && meta.connect) {
|
|
119
|
-
await runPikkuFuncDirectly(
|
|
120
|
-
meta.connect.pikkuFuncName,
|
|
121
|
-
{ ...singletonServices, ...sessionServices, channel },
|
|
122
|
-
openingData
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
http?.response?.status(101)
|
|
126
|
-
} catch (e: any) {
|
|
127
|
-
handleHTTPError(
|
|
128
|
-
e,
|
|
129
|
-
http,
|
|
130
|
-
channelId,
|
|
131
|
-
singletonServices.logger,
|
|
132
|
-
logWarningsForStatusCodes,
|
|
133
|
-
respondWith404,
|
|
134
|
-
bubbleErrors
|
|
98
|
+
try {
|
|
99
|
+
await channelStore.addChannel({
|
|
100
|
+
channelId,
|
|
101
|
+
channelName: channelConfig.name,
|
|
102
|
+
openingData,
|
|
103
|
+
channelObject,
|
|
104
|
+
})
|
|
105
|
+
const { channel } = getVariablesForChannel({
|
|
106
|
+
channelId,
|
|
107
|
+
channelHandlerFactory,
|
|
108
|
+
channelName: channelConfig.name,
|
|
109
|
+
})
|
|
110
|
+
if (createSessionServices) {
|
|
111
|
+
sessionServices = await createSessionServices(
|
|
112
|
+
singletonServices,
|
|
113
|
+
{ http },
|
|
114
|
+
await userSession.get()
|
|
135
115
|
)
|
|
136
|
-
} finally {
|
|
137
|
-
if (sessionServices) {
|
|
138
|
-
await closeSessionServices(singletonServices.logger, sessionServices)
|
|
139
|
-
}
|
|
140
116
|
}
|
|
141
|
-
}
|
|
142
117
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
118
|
+
const interaction: PikkuInteraction = { channel }
|
|
119
|
+
const getAllServices = (requiresAuth?: boolean) =>
|
|
120
|
+
rpcService.injectRPCService(
|
|
121
|
+
{
|
|
122
|
+
...singletonServices,
|
|
123
|
+
...sessionServices,
|
|
124
|
+
channel,
|
|
125
|
+
userSession,
|
|
126
|
+
},
|
|
127
|
+
interaction,
|
|
128
|
+
requiresAuth
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if (channelConfig.onConnect && meta.connect) {
|
|
132
|
+
await runChannelLifecycleWithMiddleware({
|
|
133
|
+
channelConfig,
|
|
134
|
+
meta: meta.connect,
|
|
135
|
+
lifecycleConfig: channelConfig.onConnect,
|
|
136
|
+
lifecycleType: 'connect',
|
|
137
|
+
services: getAllServices(false),
|
|
138
|
+
channel,
|
|
139
|
+
data: openingData,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
http?.response?.status(101)
|
|
143
|
+
} catch (e: any) {
|
|
144
|
+
handleHTTPError(
|
|
145
|
+
e,
|
|
146
|
+
http,
|
|
147
|
+
channelId,
|
|
148
|
+
singletonServices.logger,
|
|
149
|
+
logWarningsForStatusCodes,
|
|
150
|
+
respondWith404,
|
|
151
|
+
bubbleErrors
|
|
152
|
+
)
|
|
153
|
+
} finally {
|
|
154
|
+
if (sessionServices) {
|
|
155
|
+
await closeSessionServices(singletonServices.logger, sessionServices)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
155
158
|
}
|
|
156
159
|
|
|
157
160
|
export const runChannelDisconnect = async ({
|
|
@@ -159,13 +162,33 @@ export const runChannelDisconnect = async ({
|
|
|
159
162
|
...params
|
|
160
163
|
}: RunServerlessChannelParams<unknown>): Promise<void> => {
|
|
161
164
|
let sessionServices: SessionServices | undefined
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
|
|
166
|
+
// Try to get channel from store. In serverless environments (especially with
|
|
167
|
+
// serverless-offline or worker threads), disconnect can be called multiple times
|
|
168
|
+
// or after the channel has already been cleaned up. If channel doesn't exist,
|
|
169
|
+
// there's nothing to disconnect, so we can return early.
|
|
170
|
+
let channelData
|
|
171
|
+
try {
|
|
172
|
+
channelData = await params.channelStore.getChannelAndSession(
|
|
173
|
+
params.channelId
|
|
174
|
+
)
|
|
175
|
+
} catch (error) {
|
|
176
|
+
singletonServices.logger.info(
|
|
177
|
+
`Channel ${params.channelId} not found during disconnect - already cleaned up`
|
|
178
|
+
)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const { openingData, channelName, session } = channelData
|
|
164
183
|
const { channel, channelConfig, meta } = getVariablesForChannel({
|
|
165
184
|
...params,
|
|
166
185
|
openingData,
|
|
167
186
|
channelName,
|
|
168
187
|
})
|
|
188
|
+
const userSession = new PikkuUserSessionService(
|
|
189
|
+
params.channelStore,
|
|
190
|
+
params.channelId
|
|
191
|
+
)
|
|
169
192
|
if (!sessionServices && params.createSessionServices) {
|
|
170
193
|
sessionServices = await params.createSessionServices(
|
|
171
194
|
singletonServices,
|
|
@@ -173,12 +196,35 @@ export const runChannelDisconnect = async ({
|
|
|
173
196
|
session
|
|
174
197
|
)
|
|
175
198
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
199
|
+
|
|
200
|
+
const interaction: PikkuInteraction = { channel }
|
|
201
|
+
const getAllServices = (requiresAuth?: boolean) =>
|
|
202
|
+
rpcService.injectRPCService(
|
|
203
|
+
{
|
|
204
|
+
...singletonServices,
|
|
205
|
+
...sessionServices,
|
|
206
|
+
channel,
|
|
207
|
+
userSession,
|
|
208
|
+
},
|
|
209
|
+
interaction,
|
|
210
|
+
requiresAuth
|
|
181
211
|
)
|
|
212
|
+
|
|
213
|
+
if (channelConfig.onDisconnect && meta.disconnect) {
|
|
214
|
+
try {
|
|
215
|
+
await runChannelLifecycleWithMiddleware({
|
|
216
|
+
channelConfig,
|
|
217
|
+
meta: meta.disconnect,
|
|
218
|
+
lifecycleConfig: channelConfig.onDisconnect,
|
|
219
|
+
lifecycleType: 'disconnect',
|
|
220
|
+
services: getAllServices(false),
|
|
221
|
+
channel,
|
|
222
|
+
})
|
|
223
|
+
} catch (e: any) {
|
|
224
|
+
singletonServices.logger.error(
|
|
225
|
+
`Error handling onDisconnect: ${e.message || e}`
|
|
226
|
+
)
|
|
227
|
+
}
|
|
182
228
|
}
|
|
183
229
|
await params.channelStore.removeChannels([channel.channelId])
|
|
184
230
|
if (sessionServices) {
|
|
@@ -199,6 +245,10 @@ export const runChannelMessage = async (
|
|
|
199
245
|
openingData,
|
|
200
246
|
channelName,
|
|
201
247
|
})
|
|
248
|
+
const userSession = new PikkuUserSessionService(
|
|
249
|
+
params.channelStore,
|
|
250
|
+
params.channelId
|
|
251
|
+
)
|
|
202
252
|
if (params.createSessionServices) {
|
|
203
253
|
sessionServices = await params.createSessionServices(
|
|
204
254
|
singletonServices,
|
|
@@ -206,14 +256,32 @@ export const runChannelMessage = async (
|
|
|
206
256
|
session
|
|
207
257
|
)
|
|
208
258
|
}
|
|
259
|
+
|
|
260
|
+
const interaction: PikkuInteraction = { channel }
|
|
261
|
+
const getAllServices = () =>
|
|
262
|
+
rpcService.injectRPCService(
|
|
263
|
+
{
|
|
264
|
+
...singletonServices,
|
|
265
|
+
...sessionServices,
|
|
266
|
+
channel,
|
|
267
|
+
userSession,
|
|
268
|
+
},
|
|
269
|
+
interaction
|
|
270
|
+
)
|
|
271
|
+
|
|
209
272
|
let response: unknown
|
|
210
273
|
try {
|
|
211
274
|
const onMessage = processMessageHandlers(
|
|
212
|
-
|
|
275
|
+
getAllServices(),
|
|
213
276
|
channelConfig,
|
|
214
277
|
channelHandler
|
|
215
278
|
)
|
|
216
279
|
response = await onMessage(data)
|
|
280
|
+
} catch (e: any) {
|
|
281
|
+
singletonServices.logger.error(
|
|
282
|
+
`Error processing message: ${e.message || e}`
|
|
283
|
+
)
|
|
284
|
+
return { error: e.message || 'Unknown error' }
|
|
217
285
|
} finally {
|
|
218
286
|
if (sessionServices) {
|
|
219
287
|
await closeSessionServices(singletonServices.logger, sessionServices)
|
|
@@ -30,7 +30,7 @@ export async function executeCLIViaChannel({
|
|
|
30
30
|
const allCLIMeta = pikkuState('cli', 'meta') as unknown as CLIMeta | undefined
|
|
31
31
|
if (!allCLIMeta) {
|
|
32
32
|
throw new Error(
|
|
33
|
-
'[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/errors/pku342 for more information.'
|
|
33
|
+
'[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/docs/pikku-cli/errors/pku342 for more information.'
|
|
34
34
|
)
|
|
35
35
|
}
|
|
36
36
|
const programMeta = allCLIMeta.programs[programName]
|
|
@@ -92,6 +92,18 @@ export async function executeCLIViaChannel({
|
|
|
92
92
|
const commandRoute = pikkuWS.getRoute('command')
|
|
93
93
|
|
|
94
94
|
const responseHandler = (response: any) => {
|
|
95
|
+
// Check if this is a control message
|
|
96
|
+
if (
|
|
97
|
+
response?.action === 'cli-control' &&
|
|
98
|
+
response?.event === 'complete'
|
|
99
|
+
) {
|
|
100
|
+
// Server signals command is complete, close the connection client-side
|
|
101
|
+
commandRoute.unsubscribe(commandId, responseHandler)
|
|
102
|
+
pikkuWS.ws.close()
|
|
103
|
+
resolve(undefined)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
// Call renderer for any output
|
|
96
108
|
renderer(null as any, response, undefined)
|
|
97
109
|
}
|