@pikku/core 0.9.11 → 0.9.12-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/function/function-runner.d.ts +13 -8
  3. package/dist/function/function-runner.js +49 -37
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.js +3 -1
  6. package/dist/middleware-runner.d.ts +61 -40
  7. package/dist/middleware-runner.js +118 -66
  8. package/dist/permissions.js +1 -0
  9. package/dist/pikku-state.d.ts +26 -1
  10. package/dist/pikku-state.js +10 -1
  11. package/dist/services/content-service.d.ts +2 -3
  12. package/dist/services/index.d.ts +0 -1
  13. package/dist/services/index.js +0 -1
  14. package/dist/services/local-content.d.ts +2 -4
  15. package/dist/types/core.types.d.ts +51 -2
  16. package/dist/types/core.types.js +21 -0
  17. package/dist/wirings/channel/channel-handler.d.ts +2 -2
  18. package/dist/wirings/channel/channel-handler.js +12 -9
  19. package/dist/wirings/channel/channel-runner.d.ts +0 -2
  20. package/dist/wirings/channel/channel-runner.js +2 -3
  21. package/dist/wirings/channel/channel.types.d.ts +5 -3
  22. package/dist/wirings/channel/local/local-channel-runner.js +12 -12
  23. package/dist/wirings/channel/serverless/serverless-channel-runner.js +7 -8
  24. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +12 -0
  25. package/dist/wirings/cli/channel/cli-channel-runner.js +80 -0
  26. package/dist/wirings/cli/channel/index.d.ts +1 -0
  27. package/dist/wirings/cli/channel/index.js +1 -0
  28. package/dist/wirings/cli/cli-runner.d.ts +32 -0
  29. package/dist/wirings/cli/cli-runner.js +328 -0
  30. package/dist/wirings/cli/cli.types.d.ts +177 -0
  31. package/dist/wirings/cli/cli.types.js +1 -0
  32. package/dist/wirings/cli/command-parser.d.ts +19 -0
  33. package/dist/wirings/cli/command-parser.js +373 -0
  34. package/dist/wirings/cli/index.d.ts +5 -0
  35. package/dist/wirings/cli/index.js +5 -0
  36. package/dist/wirings/http/http-runner.d.ts +29 -10
  37. package/dist/wirings/http/http-runner.js +103 -131
  38. package/dist/wirings/http/http.types.d.ts +10 -10
  39. package/dist/wirings/http/index.d.ts +1 -1
  40. package/dist/wirings/http/index.js +1 -1
  41. package/dist/wirings/http/pikku-fetch-http-response.js +3 -1
  42. package/dist/wirings/http/routers/http-router.d.ts +0 -2
  43. package/dist/wirings/http/routers/path-to-regex.d.ts +1 -1
  44. package/dist/wirings/http/routers/path-to-regex.js +5 -34
  45. package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
  46. package/dist/wirings/mcp/mcp-runner.js +30 -34
  47. package/dist/wirings/mcp/mcp.types.d.ts +11 -8
  48. package/dist/wirings/queue/queue-runner.d.ts +2 -2
  49. package/dist/wirings/queue/queue-runner.js +25 -27
  50. package/dist/wirings/queue/queue.types.d.ts +6 -5
  51. package/dist/wirings/rpc/index.d.ts +1 -1
  52. package/dist/wirings/rpc/index.js +1 -1
  53. package/dist/wirings/rpc/rpc-runner.d.ts +6 -18
  54. package/dist/wirings/rpc/rpc-runner.js +13 -8
  55. package/dist/wirings/scheduler/scheduler-runner.d.ts +2 -2
  56. package/dist/wirings/scheduler/scheduler-runner.js +37 -35
  57. package/dist/wirings/scheduler/scheduler.types.d.ts +4 -3
  58. package/package.json +4 -3
  59. package/src/function/function-runner.test.ts +73 -23
  60. package/src/function/function-runner.ts +74 -55
  61. package/src/index.ts +8 -1
  62. package/src/middleware-runner.test.ts +24 -16
  63. package/src/middleware-runner.ts +136 -83
  64. package/src/permissions.ts +1 -0
  65. package/src/pikku-state.ts +47 -2
  66. package/src/services/content-service.ts +5 -4
  67. package/src/services/index.ts +0 -1
  68. package/src/services/local-content.ts +9 -4
  69. package/src/types/core.types.ts +74 -3
  70. package/src/wirings/channel/channel-handler.ts +9 -13
  71. package/src/wirings/channel/channel-runner.ts +2 -6
  72. package/src/wirings/channel/channel.types.ts +5 -1
  73. package/src/wirings/channel/local/local-channel-runner.ts +25 -15
  74. package/src/wirings/channel/serverless/serverless-channel-runner.ts +15 -17
  75. package/src/wirings/cli/channel/cli-channel-runner.ts +118 -0
  76. package/src/wirings/cli/channel/index.ts +1 -0
  77. package/src/wirings/cli/cli-runner.test.ts +382 -0
  78. package/src/wirings/cli/cli-runner.ts +503 -0
  79. package/src/wirings/cli/cli.types.ts +320 -0
  80. package/src/wirings/cli/command-parser.test.ts +440 -0
  81. package/src/wirings/cli/command-parser.ts +470 -0
  82. package/src/wirings/cli/index.ts +12 -0
  83. package/src/wirings/http/http-runner.test.ts +8 -7
  84. package/src/wirings/http/http-runner.ts +126 -159
  85. package/src/wirings/http/http.types.ts +56 -11
  86. package/src/wirings/http/index.ts +1 -1
  87. package/src/wirings/http/pikku-fetch-http-response.ts +3 -1
  88. package/src/wirings/http/routers/http-router.ts +0 -2
  89. package/src/wirings/http/routers/path-to-regex.test.ts +4 -5
  90. package/src/wirings/http/routers/path-to-regex.ts +6 -43
  91. package/src/wirings/mcp/mcp-runner.ts +56 -55
  92. package/src/wirings/mcp/mcp.types.ts +23 -8
  93. package/src/wirings/queue/queue-runner.ts +44 -47
  94. package/src/wirings/queue/queue.types.ts +10 -6
  95. package/src/wirings/rpc/index.ts +1 -1
  96. package/src/wirings/rpc/rpc-runner.ts +27 -9
  97. package/src/wirings/scheduler/scheduler-runner.ts +57 -56
  98. package/src/wirings/scheduler/scheduler.types.ts +9 -2
  99. package/tsconfig.tsbuildinfo +1 -1
@@ -3,7 +3,6 @@ import { addFunction } from '../../function/function-runner.js'
3
3
  import { pikkuState } from '../../pikku-state.js'
4
4
  import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js'
5
5
  import { UserSessionService } from '../../services/user-session-service.js'
6
- import { CorePikkuMiddleware } from '../../types/core.types.js'
7
6
  import { httpRouter } from '../http/routers/http-router.js'
8
7
  import {
9
8
  ChannelMeta,
@@ -117,7 +116,6 @@ const getMatchingChannelConfig = (request: string) => {
117
116
  channelConfig,
118
117
  schemaName: channelMeta.input,
119
118
  meta: channelMeta,
120
- httpMiddleware: matchedPath.middleware,
121
119
  }
122
120
  }
123
121
 
@@ -133,7 +131,6 @@ export const openChannel = async ({
133
131
  openingData: unknown
134
132
  channelConfig: CoreChannel<unknown, any>
135
133
  meta: ChannelMeta
136
- httpMiddleware: CorePikkuMiddleware[] | undefined
137
134
  }> => {
138
135
  const matchingChannel = getMatchingChannelConfig(route)
139
136
  if (!matchingChannel) {
@@ -141,8 +138,7 @@ export const openChannel = async ({
141
138
  throw new NotFoundError(`Channel not found: ${route}`)
142
139
  }
143
140
 
144
- const { params, channelConfig, schemaName, meta, httpMiddleware } =
145
- matchingChannel
141
+ const { params, channelConfig, schemaName, meta } = matchingChannel
146
142
 
147
143
  const requiresSession = channelConfig.auth !== false
148
144
  request?.setParams(params)
@@ -165,5 +161,5 @@ export const openChannel = async ({
165
161
  )
166
162
  }
167
163
 
168
- return { openingData, channelConfig, meta, httpMiddleware }
164
+ return { openingData, channelConfig, meta }
169
165
  }
@@ -9,6 +9,7 @@ import {
9
9
  CoreSingletonServices,
10
10
  CreateSessionServices,
11
11
  CorePikkuMiddleware,
12
+ MiddlewareMetadata,
12
13
  } from '../../types/core.types.js'
13
14
  import {
14
15
  CorePikkuFunction,
@@ -35,6 +36,7 @@ export type RunChannelParams<ChannelData> = {
35
36
  export interface ChannelMessageMeta {
36
37
  pikkuFuncName: string
37
38
  docs?: PikkuDocs
39
+ middleware?: MiddlewareMetadata[]
38
40
  }
39
41
 
40
42
  export interface ChannelMeta {
@@ -50,6 +52,7 @@ export interface ChannelMeta {
50
52
  messageWirings: Record<string, Record<string, ChannelMessageMeta>>
51
53
  docs?: PikkuDocs
52
54
  tags?: string[]
55
+ middleware?: MiddlewareMetadata[] // Pre-resolved middleware chain (tag + explicit)
53
56
  }
54
57
 
55
58
  export type ChannelsMeta = Record<string, ChannelMeta>
@@ -67,6 +70,7 @@ export type CoreChannel<
67
70
  | CorePikkuFunction<unknown, unknown, ChannelData>
68
71
  | CorePikkuFunctionSessionless<unknown, unknown, ChannelData>,
69
72
  PikkuPermission = CorePikkuPermission<ChannelData>,
73
+ PikkuMiddleware = CorePikkuMiddleware,
70
74
  > = {
71
75
  name: string
72
76
  route: Channel
@@ -91,7 +95,7 @@ export type CoreChannel<
91
95
  }
92
96
  >
93
97
  >
94
- middleware?: CorePikkuMiddleware[]
98
+ middleware?: PikkuMiddleware[]
95
99
  permissions?: Record<string, PikkuPermission[] | PikkuPermission>
96
100
  auth?: boolean
97
101
  docs?: Partial<{
@@ -8,7 +8,11 @@ import {
8
8
  RunChannelParams,
9
9
  } from '../channel.types.js'
10
10
  import { PikkuLocalChannelHandler } from './local-channel-handler.js'
11
- import { PikkuWiringTypes, SessionServices } from '../../../types/core.types.js'
11
+ import {
12
+ PikkuInteraction,
13
+ PikkuWiringTypes,
14
+ SessionServices,
15
+ } from '../../../types/core.types.js'
12
16
  import { handleHTTPError } from '../../../handle-error.js'
13
17
  import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
14
18
  import { PikkuUserSessionService } from '../../../services/user-session-service.js'
@@ -42,9 +46,9 @@ export const runLocalChannel = async ({
42
46
  route = http?.request?.path()
43
47
  }
44
48
 
45
- let openingData, channelConfig, meta, httpMiddleware
49
+ let openingData, channelConfig, meta
46
50
  try {
47
- ;({ openingData, channelConfig, meta, httpMiddleware } = await openChannel({
51
+ ;({ openingData, channelConfig, meta } = await openChannel({
48
52
  channelId,
49
53
  createSessionServices,
50
54
  respondWith404,
@@ -86,17 +90,24 @@ export const runLocalChannel = async ({
86
90
  )
87
91
  }
88
92
 
89
- const allServices = rpcService.injectRPCService({
90
- ...singletonServices,
91
- ...sessionServices,
92
- userSession: userSession,
93
- })
93
+ const getAllServices = (channel: any, requiresAuth?: boolean) =>
94
+ rpcService.injectRPCService(
95
+ {
96
+ ...singletonServices,
97
+ ...sessionServices,
98
+ userSession,
99
+ },
100
+ interaction,
101
+ requiresAuth
102
+ )
103
+
104
+ const interaction: PikkuInteraction = { channel }
94
105
 
95
106
  channelHandler.registerOnOpen(() => {
96
107
  if (channelConfig.onConnect && meta.connect) {
97
108
  runPikkuFuncDirectly(
98
109
  meta.connect.pikkuFuncName,
99
- { ...allServices, channel },
110
+ getAllServices(channel, false),
100
111
  openingData
101
112
  )
102
113
  }
@@ -106,10 +117,11 @@ export const runLocalChannel = async ({
106
117
  if (channelConfig.onDisconnect && meta.disconnect) {
107
118
  runPikkuFuncDirectly(
108
119
  meta.disconnect.pikkuFuncName,
109
- { ...allServices, channel },
120
+ getAllServices(channel, false),
110
121
  openingData
111
122
  )
112
123
  }
124
+
113
125
  if (sessionServices) {
114
126
  await closeSessionServices(singletonServices.logger, sessionServices)
115
127
  }
@@ -117,8 +129,7 @@ export const runLocalChannel = async ({
117
129
 
118
130
  channelHandler.registerOnMessage(
119
131
  processMessageHandlers(
120
- allServices,
121
- session,
132
+ getAllServices(channel),
122
133
  channelConfig as any,
123
134
  channelHandler
124
135
  )
@@ -147,9 +158,8 @@ export const runLocalChannel = async ({
147
158
  },
148
159
  { http },
149
160
  combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
150
- wiringMiddleware: channelConfig.middleware,
151
- wiringTags: channelConfig.tags,
152
- httpMiddleware,
161
+ wireInheritedMiddleware: meta.middleware,
162
+ wireMiddleware: channelConfig.middleware,
153
163
  }),
154
164
  main
155
165
  )
@@ -85,16 +85,15 @@ export const runChannelConnect = async ({
85
85
 
86
86
  const userSession = new PikkuUserSessionService(channelStore, channelId)
87
87
 
88
- const { channelConfig, openingData, meta, httpMiddleware } =
89
- await openChannel({
90
- channelId,
91
- createSessionServices,
92
- request,
93
- route,
94
- singletonServices,
95
- coerceDataFromSchema,
96
- userSession,
97
- })
88
+ const { channelConfig, openingData, meta } = await openChannel({
89
+ channelId,
90
+ createSessionServices,
91
+ request,
92
+ route,
93
+ singletonServices,
94
+ coerceDataFromSchema,
95
+ userSession,
96
+ })
98
97
 
99
98
  const main = async () => {
100
99
  try {
@@ -148,9 +147,8 @@ export const runChannelConnect = async ({
148
147
  },
149
148
  { http },
150
149
  combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
151
- wiringMiddleware: channelConfig.middleware,
152
- wiringTags: channelConfig.tags,
153
- httpMiddleware,
150
+ wireInheritedMiddleware: meta.middleware,
151
+ wireMiddleware: channelConfig.middleware,
154
152
  }),
155
153
  main
156
154
  )
@@ -171,7 +169,7 @@ export const runChannelDisconnect = async ({
171
169
  if (!sessionServices && params.createSessionServices) {
172
170
  sessionServices = await params.createSessionServices(
173
171
  singletonServices,
174
- {},
172
+ { channel },
175
173
  session
176
174
  )
177
175
  }
@@ -195,7 +193,8 @@ export const runChannelMessage = async (
195
193
  let sessionServices: SessionServices | undefined
196
194
  const { openingData, channelName, session } =
197
195
  await params.channelStore.getChannelAndSession(params.channelId)
198
- const { channelHandler, channelConfig } = getVariablesForChannel({
196
+
197
+ const { channel, channelHandler, channelConfig } = getVariablesForChannel({
199
198
  ...params,
200
199
  openingData,
201
200
  channelName,
@@ -203,7 +202,7 @@ export const runChannelMessage = async (
203
202
  if (params.createSessionServices) {
204
203
  sessionServices = await params.createSessionServices(
205
204
  singletonServices,
206
- {},
205
+ { channel },
207
206
  session
208
207
  )
209
208
  }
@@ -211,7 +210,6 @@ export const runChannelMessage = async (
211
210
  try {
212
211
  const onMessage = processMessageHandlers(
213
212
  { ...singletonServices, ...sessionServices },
214
- session,
215
213
  channelConfig,
216
214
  channelHandler
217
215
  )
@@ -0,0 +1,118 @@
1
+ import { pikkuState } from '../../../pikku-state.js'
2
+ import { CorePikkuCLIRender } from '../cli.types.js'
3
+ import { generateCommandHelp, parseCLIArguments } from '../command-parser.js'
4
+
5
+ /**
6
+ * Default JSON renderer for CLI output
7
+ */
8
+ const defaultJSONRenderer: CorePikkuCLIRender<any> = (_services, data) => {
9
+ console.log(JSON.stringify(data, null, 2))
10
+ }
11
+
12
+ /**
13
+ * Execute a CLI program via WebSocket channel
14
+ * This is the main entry point for CLI-over-Channel clients
15
+ */
16
+ export async function executeCLIViaChannel({
17
+ programName,
18
+ pikkuWS,
19
+ args = process.argv.slice(2),
20
+ renderers = {},
21
+ defaultRenderer,
22
+ }: {
23
+ programName: string
24
+ pikkuWS: any // CorePikkuWebsocket instance
25
+ args?: string[]
26
+ renderers?: Record<string, CorePikkuCLIRender<any>>
27
+ defaultRenderer?: CorePikkuCLIRender<any>
28
+ }): Promise<void> {
29
+ // Get CLI metadata from state
30
+ const allCLIMeta = pikkuState('cli', 'meta') || {}
31
+ const programMeta = allCLIMeta[programName]
32
+
33
+ if (!programMeta) {
34
+ console.error(`Error: CLI program "${programName}" not found`)
35
+ process.exit(1)
36
+ }
37
+
38
+ // Parse arguments for this specific program
39
+ const parsed = parseCLIArguments(args, programName, allCLIMeta)
40
+
41
+ // Handle help (check after parsing to support subcommand help)
42
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
43
+ const helpText = generateCommandHelp(
44
+ programName,
45
+ allCLIMeta,
46
+ parsed.commandPath
47
+ )
48
+ console.log(helpText)
49
+ return
50
+ }
51
+
52
+ if (parsed.errors.length > 0) {
53
+ // Check if any error is about an unknown command
54
+ const hasUnknownCommand = parsed.errors.some(
55
+ (error) =>
56
+ error.startsWith('Unknown command:') ||
57
+ error.startsWith('Command not found:')
58
+ )
59
+
60
+ if (hasUnknownCommand) {
61
+ // Show help instead of error for unknown commands
62
+ const helpText = generateCommandHelp(
63
+ programName,
64
+ allCLIMeta,
65
+ parsed.commandPath
66
+ )
67
+ console.log(helpText)
68
+ process.exit(1)
69
+ } else {
70
+ // Show errors for other types of errors
71
+ console.error('Errors:')
72
+ parsed.errors.forEach((error) => console.error(` ${error}`))
73
+ process.exit(1)
74
+ }
75
+ }
76
+
77
+ // Merge positionals and options into single data object
78
+ const data = { ...parsed.positionals, ...parsed.options }
79
+
80
+ // Get the renderer for this command
81
+ const commandId = parsed.commandPath.join('.')
82
+ const renderer =
83
+ renderers[commandId] || defaultRenderer || defaultJSONRenderer
84
+
85
+ return new Promise((resolve, reject) => {
86
+ // Subscribe to responses for this command
87
+ const commandRoute = pikkuWS.getRoute('command')
88
+
89
+ const responseHandler = (response: any) => {
90
+ // Call renderer for any output
91
+ renderer(null as any, response, undefined)
92
+ }
93
+
94
+ commandRoute.subscribe(commandId, responseHandler)
95
+
96
+ // Handle connection close
97
+ pikkuWS.ws.addEventListener('close', () => {
98
+ commandRoute.unsubscribe(commandId, responseHandler)
99
+ resolve(undefined)
100
+ })
101
+
102
+ pikkuWS.ws.addEventListener('error', (error: any) => {
103
+ commandRoute.unsubscribe(commandId, responseHandler)
104
+ reject(error)
105
+ })
106
+
107
+ // Send the command once connected
108
+ if (pikkuWS.ws.readyState === 1) {
109
+ // Already open
110
+ commandRoute.send(commandId, data)
111
+ } else {
112
+ // Wait for open
113
+ pikkuWS.ws.addEventListener('open', () => {
114
+ commandRoute.send(commandId, data)
115
+ })
116
+ }
117
+ })
118
+ }
@@ -0,0 +1 @@
1
+ export * from './cli-channel-runner.js'