@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
@@ -10,7 +10,6 @@ export const resetPikkuState = () => {
10
10
  },
11
11
  http: {
12
12
  permissions: new Map(),
13
- middleware: new Map(),
14
13
  routes: new Map(),
15
14
  meta: {},
16
15
  },
@@ -34,6 +33,16 @@ export const resetPikkuState = () => {
34
33
  prompts: new Map(),
35
34
  promptsMeta: {},
36
35
  },
36
+ cli: {
37
+ meta: {},
38
+ programs: {},
39
+ },
40
+ middleware: {
41
+ tagGroup: {},
42
+ httpGroup: {},
43
+ tagGroupMeta: {},
44
+ httpGroupMeta: {},
45
+ },
37
46
  misc: {
38
47
  errors: globalThis.pikkuState?.misc?.errors || new Map(),
39
48
  schemas: new Map(),
@@ -1,4 +1,3 @@
1
- import { ReadStream } from 'fs';
2
1
  export interface ContentService {
3
2
  /**
4
3
  * Signs a content key to generate a secure, time-limited access URL.
@@ -36,7 +35,7 @@ export interface ContentService {
36
35
  * @param stream - A readable stream of the file contents.
37
36
  * @returns A boolean indicating success.
38
37
  */
39
- writeFile(assetKey: string, stream: ReadStream): Promise<boolean>;
38
+ writeFile(assetKey: string, stream: ReadableStream | NodeJS.ReadableStream): Promise<boolean>;
40
39
  /**
41
40
  * Copies a file from a local absolute path into storage under a new asset key.
42
41
  * @param assetKey - The destination key.
@@ -49,5 +48,5 @@ export interface ContentService {
49
48
  * @param assetKey - The key of the file to read.
50
49
  * @returns A readable file stream.
51
50
  */
52
- readFile(assetKey: string): Promise<ReadStream>;
51
+ readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>;
53
52
  }
@@ -11,4 +11,3 @@ export * from './user-session-service.js';
11
11
  export * from './local-secrets.js';
12
12
  export * from './local-variables.js';
13
13
  export * from './logger-console.js';
14
- export * from './local-content.js';
@@ -13,4 +13,3 @@ export * from './user-session-service.js';
13
13
  export * from './local-secrets.js';
14
14
  export * from './local-variables.js';
15
15
  export * from './logger-console.js';
16
- export * from './local-content.js';
@@ -1,6 +1,4 @@
1
- import { ReadStream } from 'fs';
2
1
  import { ContentService, Logger } from '@pikku/core/services';
3
- import { Readable } from 'stream';
4
2
  export interface LocalContentConfig {
5
3
  localFileUploadPath: string;
6
4
  uploadUrlPrefix: string;
@@ -19,9 +17,9 @@ export declare class LocalContent implements ContentService {
19
17
  uploadUrl: string;
20
18
  assetKey: string;
21
19
  }>;
22
- writeFile(assetKey: string, stream: Readable): Promise<boolean>;
20
+ writeFile(assetKey: string, stream: ReadableStream | NodeJS.ReadableStream): Promise<boolean>;
23
21
  copyFile(assetKey: string, fromAbsolutePath: string): Promise<boolean>;
24
- readFile(assetKey: string): Promise<ReadStream>;
22
+ readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>;
25
23
  deleteFile(assetKey: string): Promise<boolean>;
26
24
  private createDirectoryForFile;
27
25
  }
@@ -8,18 +8,37 @@ import { PikkuRPC } from '../wirings/rpc/rpc-types.js';
8
8
  import { PikkuMCP } from '../wirings/mcp/mcp.types.js';
9
9
  import { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js';
10
10
  import { PikkuQueue } from '../wirings/queue/queue.types.js';
11
+ import { PikkuCLI } from '../wirings/cli/cli.types.js';
11
12
  export declare enum PikkuWiringTypes {
12
13
  http = "http",
13
14
  scheduler = "scheduler",
14
15
  channel = "channel",
15
16
  rpc = "rpc",
16
17
  queue = "queue",
17
- mcp = "mcp"
18
+ mcp = "mcp",
19
+ cli = "cli"
18
20
  }
19
21
  export interface FunctionServicesMeta {
20
22
  optimized: boolean;
21
23
  services: string[];
22
24
  }
25
+ /**
26
+ * Metadata for middleware at any level
27
+ * - type: 'http' = HTTP route middleware group (references httpGroup in pikkuState)
28
+ * - type: 'tag' = Tag-based middleware group (references tagGroup in pikkuState)
29
+ * - type: 'wire' = Wire-level individual middleware
30
+ */
31
+ export type MiddlewareMetadata = {
32
+ type: 'http';
33
+ route: string;
34
+ } | {
35
+ type: 'tag';
36
+ tag: string;
37
+ } | {
38
+ type: 'wire';
39
+ name: string;
40
+ inline?: boolean;
41
+ };
23
42
  export type FunctionRuntimeMeta = {
24
43
  pikkuFuncName: string;
25
44
  inputSchemaName: string | null;
@@ -34,6 +53,7 @@ export type FunctionMeta = FunctionRuntimeMeta & Partial<{
34
53
  tags: string[];
35
54
  docs: PikkuDocs;
36
55
  isDirectFunction: boolean;
56
+ middleware: MiddlewareMetadata[];
37
57
  }>;
38
58
  export type FunctionsRuntimeMeta = Record<string, FunctionRuntimeMeta>;
39
59
  export type FunctionsMeta = Record<string, FunctionMeta>;
@@ -99,6 +119,7 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
99
119
  channel: PikkuChannel<unknown, Out>;
100
120
  scheduledTask: PikkuScheduledTask;
101
121
  queue: PikkuQueue;
122
+ cli: PikkuCLI;
102
123
  }>;
103
124
  /**
104
125
  * A function that can wrap an interaction and be called before or after
@@ -106,10 +127,38 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
106
127
  export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices & {
107
128
  userSession: UserSessionService<UserSession>;
108
129
  }, interactions: PikkuInteraction, next: () => Promise<void>) => Promise<void>;
130
+ /**
131
+ * A factory function that takes input and returns middleware
132
+ * Used when middleware needs configuration/input parameters
133
+ */
134
+ export type CorePikkuMiddlewareFactory<In = any, SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (input: In) => CorePikkuMiddleware<SingletonServices, UserSession>;
135
+ /**
136
+ * A group of middleware (combination of regular middleware and factories)
137
+ * Used with addMiddleware() and addHTTPMiddleware() to group related middleware together
138
+ */
139
+ export type CorePikkuMiddlewareGroup<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = Array<CorePikkuMiddleware<SingletonServices, UserSession> | CorePikkuMiddlewareFactory<any, SingletonServices, UserSession>>;
109
140
  /**
110
141
  * Factory function for creating middleware with tree-shaking support
111
142
  */
112
143
  export declare const pikkuMiddleware: <SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession>(middleware: CorePikkuMiddleware<SingletonServices, UserSession>) => CorePikkuMiddleware<SingletonServices, UserSession>;
144
+ /**
145
+ * Factory function for creating middleware factories
146
+ * Use this when your middleware needs configuration/input parameters
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * export const logMiddleware = pikkuMiddlewareFactory<LogOptions>(({
151
+ * message,
152
+ * level = 'info'
153
+ * }) => {
154
+ * return pikkuMiddleware(async ({ logger }, _interaction, next) => {
155
+ * logger[level](message)
156
+ * await next()
157
+ * })
158
+ * })
159
+ * ```
160
+ */
161
+ export declare const pikkuMiddlewareFactory: <In = any>(factory: CorePikkuMiddlewareFactory<In>) => CorePikkuMiddlewareFactory<In>;
113
162
  /**
114
163
  * Represents the core services used by Pikku, including singleton services and the request/response interaction.
115
164
  */
@@ -128,7 +177,7 @@ export type CreateSessionServices<SingletonServices extends CoreSingletonService
128
177
  /**
129
178
  * Defines a function type for creating config.
130
179
  */
131
- export type CreateConfig<Config extends CoreConfig> = (variables?: VariablesService) => Promise<Config>;
180
+ export type CreateConfig<Config extends CoreConfig, RemainingArgs extends any[] = unknown[]> = (variables?: VariablesService, ...args: RemainingArgs) => Promise<Config>;
132
181
  /**
133
182
  * Represents the documentation for a route, including summary, description, tags, and errors.
134
183
  */
@@ -6,6 +6,7 @@ export var PikkuWiringTypes;
6
6
  PikkuWiringTypes["rpc"] = "rpc";
7
7
  PikkuWiringTypes["queue"] = "queue";
8
8
  PikkuWiringTypes["mcp"] = "mcp";
9
+ PikkuWiringTypes["cli"] = "cli";
9
10
  })(PikkuWiringTypes || (PikkuWiringTypes = {}));
10
11
  /**
11
12
  * Factory function for creating middleware with tree-shaking support
@@ -13,3 +14,23 @@ export var PikkuWiringTypes;
13
14
  export const pikkuMiddleware = (middleware) => {
14
15
  return middleware;
15
16
  };
17
+ /**
18
+ * Factory function for creating middleware factories
19
+ * Use this when your middleware needs configuration/input parameters
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * export const logMiddleware = pikkuMiddlewareFactory<LogOptions>(({
24
+ * message,
25
+ * level = 'info'
26
+ * }) => {
27
+ * return pikkuMiddleware(async ({ logger }, _interaction, next) => {
28
+ * logger[level](message)
29
+ * await next()
30
+ * })
31
+ * })
32
+ * ```
33
+ */
34
+ export const pikkuMiddlewareFactory = (factory) => {
35
+ return factory;
36
+ };
@@ -1,3 +1,3 @@
1
- import { CoreServices, CoreUserSession } from '../../types/core.types.js';
1
+ import { CoreServices } from '../../types/core.types.js';
2
2
  import { CoreChannel, PikkuChannelHandler } from './channel.types.js';
3
- export declare const processMessageHandlers: (services: CoreServices, session: CoreUserSession | undefined, channelConfig: CoreChannel<any, any>, channelHandler: PikkuChannelHandler) => (rawData: any) => Promise<unknown>;
3
+ export declare const processMessageHandlers: (services: CoreServices, channelConfig: CoreChannel<any, any>, channelHandler: PikkuChannelHandler) => (rawData: any) => Promise<unknown>;
@@ -32,10 +32,10 @@ const validateAuth = (requiresSession, session, onMessage) => {
32
32
  }
33
33
  return true;
34
34
  };
35
- export const processMessageHandlers = (services, session, channelConfig, channelHandler) => {
35
+ export const processMessageHandlers = (services, channelConfig, channelHandler) => {
36
36
  const logger = services.logger;
37
37
  const requiresSession = channelConfig.auth !== false;
38
- const processMessage = async (data, onMessage, session, routingProperty, routerValue) => {
38
+ const processMessage = async (data, onMessage, routingProperty, routerValue) => {
39
39
  if (!validateAuth(requiresSession, channelHandler, onMessage)) {
40
40
  const routeMessage = routingProperty
41
41
  ? `route '${routingProperty}:${routerValue}'`
@@ -45,19 +45,22 @@ export const processMessageHandlers = (services, session, channelConfig, channel
45
45
  channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`);
46
46
  return;
47
47
  }
48
- const { pikkuFuncName } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
48
+ const { pikkuFuncName, middleware: inheritedMiddleware } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
49
49
  const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
50
50
  const middleware = typeof onMessage === 'function' ? [] : onMessage.middleware;
51
51
  return await runPikkuFunc(PikkuWiringTypes.channel, channelConfig.name, pikkuFuncName, {
52
+ singletonServices: services,
52
53
  getAllServices: () => ({
53
54
  ...services,
54
55
  channel: channelHandler.getChannel(),
55
56
  }),
56
- data,
57
- session,
57
+ data: () => data,
58
+ userSession: services.userSession,
58
59
  permissions,
59
- middleware,
60
+ inheritedMiddleware,
61
+ wireMiddleware: middleware,
60
62
  tags: channelConfig.tags,
63
+ interaction: { channel: channelHandler.getChannel() },
61
64
  });
62
65
  };
63
66
  const onMessage = async (rawData) => {
@@ -72,14 +75,14 @@ export const processMessageHandlers = (services, session, channelConfig, channel
72
75
  const routerValue = messageData[routingProperty];
73
76
  if (routerValue && routes[routerValue]) {
74
77
  processed = true;
75
- result = await processMessage(messageData, routes[routerValue], session, routingProperty, routerValue);
78
+ result = await processMessage(messageData, routes[routerValue], routingProperty, routerValue);
76
79
  break;
77
80
  }
78
81
  }
79
82
  // Default handler if no routes matched but json data was parsed
80
83
  if (!processed && channelConfig.onMessage) {
81
84
  processed = true;
82
- result = await processMessage(messageData, channelConfig.onMessage, session);
85
+ result = await processMessage(messageData, channelConfig.onMessage);
83
86
  }
84
87
  }
85
88
  catch (error) {
@@ -89,7 +92,7 @@ export const processMessageHandlers = (services, session, channelConfig, channel
89
92
  // Default handler if no routes matched and json data wasn't parsed
90
93
  if (!processed && channelConfig.onMessage) {
91
94
  processed = true;
92
- result = await processMessage(rawData, channelConfig.onMessage, session);
95
+ result = await processMessage(rawData, channelConfig.onMessage);
93
96
  }
94
97
  if (!processed) {
95
98
  logger.error(`No handler found for message in channel ${channelConfig.name} for ${JSON.stringify(rawData)}`);
@@ -1,5 +1,4 @@
1
1
  import { UserSessionService } from '../../services/user-session-service.js';
2
- import { CorePikkuMiddleware } from '../../types/core.types.js';
3
2
  import { ChannelMeta, CoreChannel, RunChannelOptions, RunChannelParams } from './channel.types.js';
4
3
  /**
5
4
  * Adds a channel and registers all functions referenced in it using the
@@ -12,5 +11,4 @@ export declare const openChannel: ({ route, singletonServices, coerceDataFromSch
12
11
  openingData: unknown;
13
12
  channelConfig: CoreChannel<unknown, any>;
14
13
  meta: ChannelMeta;
15
- httpMiddleware: CorePikkuMiddleware[] | undefined;
16
14
  }>;
@@ -85,7 +85,6 @@ const getMatchingChannelConfig = (request) => {
85
85
  channelConfig,
86
86
  schemaName: channelMeta.input,
87
87
  meta: channelMeta,
88
- httpMiddleware: matchedPath.middleware,
89
88
  };
90
89
  };
91
90
  export const openChannel = async ({ route, singletonServices, coerceDataFromSchema = true, request, }) => {
@@ -94,7 +93,7 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
94
93
  singletonServices.logger.info(`Channel not found: ${route}`);
95
94
  throw new NotFoundError(`Channel not found: ${route}`);
96
95
  }
97
- const { params, channelConfig, schemaName, meta, httpMiddleware } = matchingChannel;
96
+ const { params, channelConfig, schemaName, meta } = matchingChannel;
98
97
  const requiresSession = channelConfig.auth !== false;
99
98
  request?.setParams(params);
100
99
  singletonServices.logger.info(`Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`);
@@ -106,5 +105,5 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
106
105
  }
107
106
  await validateSchema(singletonServices.logger, singletonServices.schema, schemaName, openingData);
108
107
  }
109
- return { openingData, channelConfig, meta, httpMiddleware };
108
+ return { openingData, channelConfig, meta };
110
109
  };
@@ -1,6 +1,6 @@
1
1
  import { PikkuError } from '../../errors/error-handler.js';
2
2
  import { HTTPFunctionMetaInputTypes, PikkuHTTPRequest, PikkuHTTPResponse } from '../http/http.types.js';
3
- import { PikkuDocs, CoreSingletonServices, CreateSessionServices, CorePikkuMiddleware } from '../../types/core.types.js';
3
+ import { PikkuDocs, CoreSingletonServices, CreateSessionServices, CorePikkuMiddleware, MiddlewareMetadata } from '../../types/core.types.js';
4
4
  import { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuPermission } from '../../function/functions.types.js';
5
5
  export type RunChannelOptions = Partial<{
6
6
  skipUserSession: boolean;
@@ -19,6 +19,7 @@ export type RunChannelParams<ChannelData> = {
19
19
  export interface ChannelMessageMeta {
20
20
  pikkuFuncName: string;
21
21
  docs?: PikkuDocs;
22
+ middleware?: MiddlewareMetadata[];
22
23
  }
23
24
  export interface ChannelMeta {
24
25
  name: string;
@@ -33,9 +34,10 @@ export interface ChannelMeta {
33
34
  messageWirings: Record<string, Record<string, ChannelMessageMeta>>;
34
35
  docs?: PikkuDocs;
35
36
  tags?: string[];
37
+ middleware?: MiddlewareMetadata[];
36
38
  }
37
39
  export type ChannelsMeta = Record<string, ChannelMeta>;
38
- export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = CorePikkuFunction<void, unknown, ChannelData> | CorePikkuFunctionSessionless<void, unknown, ChannelData>, ChannelDisconnect = CorePikkuFunction<void, void, ChannelData> | CorePikkuFunctionSessionless<void, void, ChannelData>, ChannelFunctionMessage = CorePikkuFunction<unknown, unknown, ChannelData> | CorePikkuFunctionSessionless<unknown, unknown, ChannelData>, PikkuPermission = CorePikkuPermission<ChannelData>> = {
40
+ export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = CorePikkuFunction<void, unknown, ChannelData> | CorePikkuFunctionSessionless<void, unknown, ChannelData>, ChannelDisconnect = CorePikkuFunction<void, void, ChannelData> | CorePikkuFunctionSessionless<void, void, ChannelData>, ChannelFunctionMessage = CorePikkuFunction<unknown, unknown, ChannelData> | CorePikkuFunctionSessionless<unknown, unknown, ChannelData>, PikkuPermission = CorePikkuPermission<ChannelData>, PikkuMiddleware = CorePikkuMiddleware> = {
39
41
  name: string;
40
42
  route: Channel;
41
43
  onConnect?: ChannelConnect;
@@ -50,7 +52,7 @@ export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = Co
50
52
  permissions?: Record<string, PikkuPermission[] | PikkuPermission>;
51
53
  auth?: boolean;
52
54
  }>>;
53
- middleware?: CorePikkuMiddleware[];
55
+ middleware?: PikkuMiddleware[];
54
56
  permissions?: Record<string, PikkuPermission[] | PikkuPermission>;
55
57
  auth?: boolean;
56
58
  docs?: Partial<{
@@ -3,7 +3,7 @@ import { createHTTPInteraction } from '../../http/http-runner.js';
3
3
  import { closeSessionServices } from '../../../utils.js';
4
4
  import { processMessageHandlers } from '../channel-handler.js';
5
5
  import { PikkuLocalChannelHandler } from './local-channel-handler.js';
6
- import { PikkuWiringTypes } from '../../../types/core.types.js';
6
+ import { PikkuWiringTypes, } from '../../../types/core.types.js';
7
7
  import { handleHTTPError } from '../../../handle-error.js';
8
8
  import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
9
9
  import { PikkuUserSessionService } from '../../../services/user-session-service.js';
@@ -18,10 +18,10 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
18
18
  http = createHTTPInteraction(request, response);
19
19
  route = http?.request?.path();
20
20
  }
21
- let openingData, channelConfig, meta, httpMiddleware;
21
+ let openingData, channelConfig, meta;
22
22
  try {
23
23
  ;
24
- ({ openingData, channelConfig, meta, httpMiddleware } = await openChannel({
24
+ ({ openingData, channelConfig, meta } = await openChannel({
25
25
  channelId,
26
26
  createSessionServices,
27
27
  respondWith404,
@@ -46,25 +46,26 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
46
46
  if (createSessionServices) {
47
47
  sessionServices = await createSessionServices(singletonServices, { http, channel }, session);
48
48
  }
49
- const allServices = rpcService.injectRPCService({
49
+ const getAllServices = (channel, requiresAuth) => rpcService.injectRPCService({
50
50
  ...singletonServices,
51
51
  ...sessionServices,
52
- userSession: userSession,
53
- });
52
+ userSession,
53
+ }, interaction, requiresAuth);
54
+ const interaction = { channel };
54
55
  channelHandler.registerOnOpen(() => {
55
56
  if (channelConfig.onConnect && meta.connect) {
56
- runPikkuFuncDirectly(meta.connect.pikkuFuncName, { ...allServices, channel }, openingData);
57
+ runPikkuFuncDirectly(meta.connect.pikkuFuncName, getAllServices(channel, false), openingData);
57
58
  }
58
59
  });
59
60
  channelHandler.registerOnClose(async () => {
60
61
  if (channelConfig.onDisconnect && meta.disconnect) {
61
- runPikkuFuncDirectly(meta.disconnect.pikkuFuncName, { ...allServices, channel }, openingData);
62
+ runPikkuFuncDirectly(meta.disconnect.pikkuFuncName, getAllServices(channel, false), openingData);
62
63
  }
63
64
  if (sessionServices) {
64
65
  await closeSessionServices(singletonServices.logger, sessionServices);
65
66
  }
66
67
  });
67
- channelHandler.registerOnMessage(processMessageHandlers(allServices, session, channelConfig, channelHandler));
68
+ channelHandler.registerOnMessage(processMessageHandlers(getAllServices(channel), channelConfig, channelHandler));
68
69
  }
69
70
  catch (e) {
70
71
  handleHTTPError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
@@ -79,9 +80,8 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
79
80
  ...singletonServices,
80
81
  userSession,
81
82
  }, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
82
- wiringMiddleware: channelConfig.middleware,
83
- wiringTags: channelConfig.tags,
84
- httpMiddleware,
83
+ wireInheritedMiddleware: meta.middleware,
84
+ wireMiddleware: channelConfig.middleware,
85
85
  }), main);
86
86
  return channelHandler;
87
87
  };
@@ -35,7 +35,7 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
35
35
  http = createHTTPInteraction(new PikkuFetchHTTPRequest(request), response);
36
36
  }
37
37
  const userSession = new PikkuUserSessionService(channelStore, channelId);
38
- const { channelConfig, openingData, meta, httpMiddleware } = await openChannel({
38
+ const { channelConfig, openingData, meta } = await openChannel({
39
39
  channelId,
40
40
  createSessionServices,
41
41
  request,
@@ -78,9 +78,8 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
78
78
  ...singletonServices,
79
79
  userSession,
80
80
  }, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
81
- wiringMiddleware: channelConfig.middleware,
82
- wiringTags: channelConfig.tags,
83
- httpMiddleware,
81
+ wireInheritedMiddleware: meta.middleware,
82
+ wireMiddleware: channelConfig.middleware,
84
83
  }), main);
85
84
  };
86
85
  export const runChannelDisconnect = async ({ singletonServices, ...params }) => {
@@ -92,7 +91,7 @@ export const runChannelDisconnect = async ({ singletonServices, ...params }) =>
92
91
  channelName,
93
92
  });
94
93
  if (!sessionServices && params.createSessionServices) {
95
- sessionServices = await params.createSessionServices(singletonServices, {}, session);
94
+ sessionServices = await params.createSessionServices(singletonServices, { channel }, session);
96
95
  }
97
96
  if (channelConfig.onDisconnect && meta.disconnect) {
98
97
  await runPikkuFuncDirectly(meta.disconnect.pikkuFuncName, { ...singletonServices, ...sessionServices, channel }, undefined);
@@ -105,17 +104,17 @@ export const runChannelDisconnect = async ({ singletonServices, ...params }) =>
105
104
  export const runChannelMessage = async ({ singletonServices, ...params }, data) => {
106
105
  let sessionServices;
107
106
  const { openingData, channelName, session } = await params.channelStore.getChannelAndSession(params.channelId);
108
- const { channelHandler, channelConfig } = getVariablesForChannel({
107
+ const { channel, channelHandler, channelConfig } = getVariablesForChannel({
109
108
  ...params,
110
109
  openingData,
111
110
  channelName,
112
111
  });
113
112
  if (params.createSessionServices) {
114
- sessionServices = await params.createSessionServices(singletonServices, {}, session);
113
+ sessionServices = await params.createSessionServices(singletonServices, { channel }, session);
115
114
  }
116
115
  let response;
117
116
  try {
118
- const onMessage = processMessageHandlers({ ...singletonServices, ...sessionServices }, session, channelConfig, channelHandler);
117
+ const onMessage = processMessageHandlers({ ...singletonServices, ...sessionServices }, channelConfig, channelHandler);
119
118
  response = await onMessage(data);
120
119
  }
121
120
  finally {
@@ -0,0 +1,12 @@
1
+ import { CorePikkuCLIRender } from '../cli.types.js';
2
+ /**
3
+ * Execute a CLI program via WebSocket channel
4
+ * This is the main entry point for CLI-over-Channel clients
5
+ */
6
+ export declare function executeCLIViaChannel({ programName, pikkuWS, args, renderers, defaultRenderer, }: {
7
+ programName: string;
8
+ pikkuWS: any;
9
+ args?: string[];
10
+ renderers?: Record<string, CorePikkuCLIRender<any>>;
11
+ defaultRenderer?: CorePikkuCLIRender<any>;
12
+ }): Promise<void>;
@@ -0,0 +1,80 @@
1
+ import { pikkuState } from '../../../pikku-state.js';
2
+ import { generateCommandHelp, parseCLIArguments } from '../command-parser.js';
3
+ /**
4
+ * Default JSON renderer for CLI output
5
+ */
6
+ const defaultJSONRenderer = (_services, data) => {
7
+ console.log(JSON.stringify(data, null, 2));
8
+ };
9
+ /**
10
+ * Execute a CLI program via WebSocket channel
11
+ * This is the main entry point for CLI-over-Channel clients
12
+ */
13
+ export async function executeCLIViaChannel({ programName, pikkuWS, args = process.argv.slice(2), renderers = {}, defaultRenderer, }) {
14
+ // Get CLI metadata from state
15
+ const allCLIMeta = pikkuState('cli', 'meta') || {};
16
+ const programMeta = allCLIMeta[programName];
17
+ if (!programMeta) {
18
+ console.error(`Error: CLI program "${programName}" not found`);
19
+ process.exit(1);
20
+ }
21
+ // Parse arguments for this specific program
22
+ const parsed = parseCLIArguments(args, programName, allCLIMeta);
23
+ // Handle help (check after parsing to support subcommand help)
24
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
25
+ const helpText = generateCommandHelp(programName, allCLIMeta, parsed.commandPath);
26
+ console.log(helpText);
27
+ return;
28
+ }
29
+ if (parsed.errors.length > 0) {
30
+ // Check if any error is about an unknown command
31
+ const hasUnknownCommand = parsed.errors.some((error) => error.startsWith('Unknown command:') ||
32
+ error.startsWith('Command not found:'));
33
+ if (hasUnknownCommand) {
34
+ // Show help instead of error for unknown commands
35
+ const helpText = generateCommandHelp(programName, allCLIMeta, parsed.commandPath);
36
+ console.log(helpText);
37
+ process.exit(1);
38
+ }
39
+ else {
40
+ // Show errors for other types of errors
41
+ console.error('Errors:');
42
+ parsed.errors.forEach((error) => console.error(` ${error}`));
43
+ process.exit(1);
44
+ }
45
+ }
46
+ // Merge positionals and options into single data object
47
+ const data = { ...parsed.positionals, ...parsed.options };
48
+ // Get the renderer for this command
49
+ const commandId = parsed.commandPath.join('.');
50
+ const renderer = renderers[commandId] || defaultRenderer || defaultJSONRenderer;
51
+ return new Promise((resolve, reject) => {
52
+ // Subscribe to responses for this command
53
+ const commandRoute = pikkuWS.getRoute('command');
54
+ const responseHandler = (response) => {
55
+ // Call renderer for any output
56
+ renderer(null, response, undefined);
57
+ };
58
+ commandRoute.subscribe(commandId, responseHandler);
59
+ // Handle connection close
60
+ pikkuWS.ws.addEventListener('close', () => {
61
+ commandRoute.unsubscribe(commandId, responseHandler);
62
+ resolve(undefined);
63
+ });
64
+ pikkuWS.ws.addEventListener('error', (error) => {
65
+ commandRoute.unsubscribe(commandId, responseHandler);
66
+ reject(error);
67
+ });
68
+ // Send the command once connected
69
+ if (pikkuWS.ws.readyState === 1) {
70
+ // Already open
71
+ commandRoute.send(commandId, data);
72
+ }
73
+ else {
74
+ // Wait for open
75
+ pikkuWS.ws.addEventListener('open', () => {
76
+ commandRoute.send(commandId, data);
77
+ });
78
+ }
79
+ });
80
+ }
@@ -0,0 +1 @@
1
+ export * from './cli-channel-runner.js';
@@ -0,0 +1 @@
1
+ export * from './cli-channel-runner.js';
@@ -0,0 +1,32 @@
1
+ import { CorePikkuMiddleware, CoreUserSession } from '../../types/core.types.js';
2
+ import { CoreCLI, CorePikkuCLIRender, CoreCLICommandConfig } from './cli.types.js';
3
+ import type { CoreSingletonServices, CoreServices, CreateSessionServices, CreateConfig, CreateSingletonServices } from '../../types/core.types.js';
4
+ /**
5
+ * Registers a CLI command tree and all its functions
6
+ */
7
+ export declare const wireCLI: <Commands extends Record<string, CoreCLICommandConfig<any, any, any>>, GlobalOptions, PikkuMiddleware extends CorePikkuMiddleware, GlobalOutput>(cli: CoreCLI<Commands, GlobalOptions, PikkuMiddleware, GlobalOutput>) => void;
8
+ /**
9
+ * Executes a CLI command for a specific program
10
+ */
11
+ export declare function runCLICommand({ program, commandPath, data, singletonServices, createSessionServices, }: {
12
+ program: string;
13
+ commandPath: string[];
14
+ data: Record<string, any>;
15
+ singletonServices: CoreSingletonServices;
16
+ createSessionServices?: CreateSessionServices;
17
+ }): Promise<any>;
18
+ /**
19
+ * Factory function for CLI-specific renderers
20
+ */
21
+ export declare const pikkuCLIRender: <Data, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(renderer: (services: Services, data: Data, session?: Session) => void | Promise<void>) => CorePikkuCLIRender<Data, Services, Session>;
22
+ /**
23
+ * Execute a CLI program with the given arguments
24
+ * This is the main entry point for CLI programs
25
+ */
26
+ export declare function executeCLI({ programName, args, createConfig, createSingletonServices, createSessionServices, }: {
27
+ programName: string;
28
+ args?: string[];
29
+ createConfig: CreateConfig<any, any>;
30
+ createSingletonServices: CreateSingletonServices<any, any>;
31
+ createSessionServices?: CreateSessionServices<any, any>;
32
+ }): Promise<void>;