@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
|
+
## 0.11.0
|
|
2
|
+
|
|
3
|
+
### Minor Changes
|
|
4
|
+
|
|
5
|
+
- Add workflow orchestration engine with step execution and retries
|
|
6
|
+
- Add scheduler service abstraction
|
|
7
|
+
- Remove file-based channel and eventhub stores
|
|
8
|
+
|
|
9
|
+
|
|
1
10
|
# @pikku/core
|
|
2
11
|
|
|
12
|
+
## 0.10.2
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- ea652dc: Refactor channel middleware handling and add lifecycle middleware support
|
|
17
|
+
|
|
18
|
+
**Breaking Changes:**
|
|
19
|
+
|
|
20
|
+
- Improved middleware resolution for channel message handlers to properly combine channel-level and message-level middleware
|
|
21
|
+
- Fixed cache key collisions when multiple message handlers use the same function
|
|
22
|
+
|
|
23
|
+
**New Features:**
|
|
24
|
+
|
|
25
|
+
- Add `runChannelLifecycleWithMiddleware` helper in `channel-common.ts` for consistent lifecycle function execution
|
|
26
|
+
- Support middleware on `onConnect` and `onDisconnect` lifecycle functions
|
|
27
|
+
- Channel-level middleware now properly applies to all messages in the channel
|
|
28
|
+
|
|
29
|
+
**Bug Fixes:**
|
|
30
|
+
|
|
31
|
+
- Fix middleware ordering: channel middleware → message middleware → inherited middleware
|
|
32
|
+
- Fix cache key generation to include routing information (prevents cache collisions)
|
|
33
|
+
- Properly detect wrapper objects vs direct function configs for message handlers
|
|
34
|
+
|
|
35
|
+
- 4349ec5: Add file-based storage implementations for serverless environments
|
|
36
|
+
|
|
37
|
+
**New Services:**
|
|
38
|
+
|
|
39
|
+
- Add `FileChannelStore` for file-based channel storage (suitable for AWS Lambda /tmp)
|
|
40
|
+
- Add `FileEventHubStore` for file-based event hub subscriptions
|
|
41
|
+
- Export new services in package.json for use in serverless runtimes
|
|
42
|
+
|
|
43
|
+
**Bug Fixes:**
|
|
44
|
+
|
|
45
|
+
- Fix serverless channel runner to handle disconnect gracefully when channel is already cleaned up
|
|
46
|
+
- Fix MCP runner to pass `mcp` service to functions and use correct function type
|
|
47
|
+
|
|
48
|
+
- 44d71a8: fix: fixing inspector ensuring pikkuConfig is set
|
|
49
|
+
|
|
3
50
|
## 0.10.1
|
|
4
51
|
|
|
5
52
|
### Patch Changes
|
|
@@ -29,7 +29,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { getAllServices,
|
|
|
29
29
|
];
|
|
30
30
|
// Helper function to run permissions and execute the function
|
|
31
31
|
const executeFunction = async () => {
|
|
32
|
-
const session = userSession?.get();
|
|
32
|
+
const session = await userSession?.get();
|
|
33
33
|
if (wiringAuth === true || funcConfig.auth === true) {
|
|
34
34
|
// This means it was explicitly enabled in either wiring or function and has to be respected
|
|
35
35
|
if (!session) {
|
|
@@ -39,7 +39,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { getAllServices,
|
|
|
39
39
|
if (wiringAuth === undefined && funcConfig.auth === undefined) {
|
|
40
40
|
// We always default to requiring auth unless explicitly disabled
|
|
41
41
|
if (!session) {
|
|
42
|
-
throw new ForbiddenError('Authentication required')
|
|
42
|
+
// throw new ForbiddenError('Authentication required')
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
// Evaluate the data from the lazy function
|
|
@@ -108,6 +108,7 @@ export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> = Re
|
|
|
108
108
|
export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>> = {
|
|
109
109
|
name?: string;
|
|
110
110
|
expose?: boolean;
|
|
111
|
+
internal?: boolean;
|
|
111
112
|
func: PikkuFunction;
|
|
112
113
|
auth?: boolean;
|
|
113
114
|
permissions?: CorePermissionGroup<PikkuPermission>;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,11 +10,13 @@ export * from './services/schema-service.js';
|
|
|
10
10
|
export * from './services/jwt-service.js';
|
|
11
11
|
export * from './services/secret-service.js';
|
|
12
12
|
export * from './services/user-session-service.js';
|
|
13
|
+
export * from './services/scheduler-service.js';
|
|
13
14
|
export * from './wirings/http/index.js';
|
|
14
15
|
export * from './wirings/channel/index.js';
|
|
15
16
|
export * from './wirings/scheduler/index.js';
|
|
16
17
|
export * from './wirings/rpc/index.js';
|
|
17
18
|
export * from './wirings/queue/index.js';
|
|
19
|
+
export * from './wirings/workflow/index.js';
|
|
18
20
|
export * from './wirings/mcp/index.js';
|
|
19
21
|
export * from './wirings/cli/index.js';
|
|
20
22
|
export * from './errors/index.js';
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,13 @@ export * from './services/schema-service.js';
|
|
|
10
10
|
export * from './services/jwt-service.js';
|
|
11
11
|
export * from './services/secret-service.js';
|
|
12
12
|
export * from './services/user-session-service.js';
|
|
13
|
+
export * from './services/scheduler-service.js';
|
|
13
14
|
export * from './wirings/http/index.js';
|
|
14
15
|
export * from './wirings/channel/index.js';
|
|
15
16
|
export * from './wirings/scheduler/index.js';
|
|
16
17
|
export * from './wirings/rpc/index.js';
|
|
17
18
|
export * from './wirings/queue/index.js';
|
|
19
|
+
export * from './wirings/workflow/index.js';
|
|
18
20
|
export * from './wirings/mcp/index.js';
|
|
19
21
|
export * from './wirings/cli/index.js';
|
|
20
22
|
export * from './errors/index.js';
|
|
@@ -20,4 +20,5 @@ export declare const authAPIKey: import("../types/core.types.js").CorePikkuMiddl
|
|
|
20
20
|
}, import("../types/core.types.js").CoreSingletonServices<{
|
|
21
21
|
logLevel?: import("../index.js").LogLevel;
|
|
22
22
|
secrets?: {};
|
|
23
|
+
workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
|
|
23
24
|
}>, import("../types/core.types.js").CoreUserSession>;
|
|
@@ -36,4 +36,5 @@ export declare const authBearer: import("../types/core.types.js").CorePikkuMiddl
|
|
|
36
36
|
}, import("../types/core.types.js").CoreSingletonServices<{
|
|
37
37
|
logLevel?: import("../index.js").LogLevel;
|
|
38
38
|
secrets?: {};
|
|
39
|
+
workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
|
|
39
40
|
}>, CoreUserSession>;
|
|
@@ -35,4 +35,5 @@ export declare const authCookie: import("../types/core.types.js").CorePikkuMiddl
|
|
|
35
35
|
}, import("../types/core.types.js").CoreSingletonServices<{
|
|
36
36
|
logLevel?: import("../index.js").LogLevel;
|
|
37
37
|
secrets?: {};
|
|
38
|
+
workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
|
|
38
39
|
}>, import("../types/core.types.js").CoreUserSession>;
|
|
@@ -3,4 +3,5 @@ export declare const timeout: () => import("../types/core.types.js").CorePikkuMi
|
|
|
3
3
|
}, import("../types/core.types.js").CoreSingletonServices<{
|
|
4
4
|
logLevel?: import("../index.js").LogLevel;
|
|
5
5
|
secrets?: {};
|
|
6
|
+
workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
|
|
6
7
|
}>, import("../types/core.types.js").CoreUserSession>;
|
package/dist/permissions.js
CHANGED
|
@@ -154,6 +154,7 @@ const combinedPermissionsCache = {
|
|
|
154
154
|
[PikkuWiringTypes.scheduler]: {},
|
|
155
155
|
[PikkuWiringTypes.mcp]: {},
|
|
156
156
|
[PikkuWiringTypes.cli]: {},
|
|
157
|
+
[PikkuWiringTypes.workflow]: {},
|
|
157
158
|
};
|
|
158
159
|
/**
|
|
159
160
|
* Combines wiring-specific permissions with function-level permissions.
|
package/dist/pikku-state.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ import { FunctionsMeta, CorePikkuMiddleware, CorePikkuMiddlewareGroup, FunctionS
|
|
|
4
4
|
import { CoreScheduledTask, ScheduledTasksMeta } from './wirings/scheduler/scheduler.types.js';
|
|
5
5
|
import { ErrorDetails, PikkuError } from './errors/error-handler.js';
|
|
6
6
|
import { CorePermissionGroup, CorePikkuFunctionConfig, CorePikkuPermission } from './function/functions.types.js';
|
|
7
|
-
import {
|
|
7
|
+
import { QueueWorkersMeta, CoreQueueWorker } from './wirings/queue/queue.types.js';
|
|
8
8
|
import { CoreMCPResource, CoreMCPTool, CoreMCPPrompt, MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from './wirings/mcp/mcp.types.js';
|
|
9
9
|
import { CLIMeta, CLIProgramState } from './wirings/cli/cli.types.js';
|
|
10
|
+
import { CoreWorkflow, WorkflowsMeta } from './wirings/workflow/workflow.types.js';
|
|
10
11
|
interface PikkuState {
|
|
11
12
|
function: {
|
|
12
13
|
meta: FunctionsMeta;
|
|
@@ -35,7 +36,11 @@ interface PikkuState {
|
|
|
35
36
|
};
|
|
36
37
|
queue: {
|
|
37
38
|
registrations: Map<string, CoreQueueWorker>;
|
|
38
|
-
meta:
|
|
39
|
+
meta: QueueWorkersMeta;
|
|
40
|
+
};
|
|
41
|
+
workflows: {
|
|
42
|
+
registrations: Map<string, CoreWorkflow>;
|
|
43
|
+
meta: WorkflowsMeta;
|
|
39
44
|
};
|
|
40
45
|
mcp: {
|
|
41
46
|
resources: Map<string, CoreMCPResource>;
|
package/dist/pikku-state.js
CHANGED
package/dist/services/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './secret-service.js';
|
|
|
8
8
|
export * from './variables-service.js';
|
|
9
9
|
export * from './schema-service.js';
|
|
10
10
|
export * from './user-session-service.js';
|
|
11
|
+
export * from './scheduler-service.js';
|
|
11
12
|
export * from './local-secrets.js';
|
|
12
13
|
export * from './local-variables.js';
|
|
13
14
|
export * from './logger-console.js';
|
package/dist/services/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from './secret-service.js';
|
|
|
9
9
|
export * from './variables-service.js';
|
|
10
10
|
export * from './schema-service.js';
|
|
11
11
|
export * from './user-session-service.js';
|
|
12
|
+
export * from './scheduler-service.js';
|
|
12
13
|
// Local implementations
|
|
13
14
|
export * from './local-secrets.js';
|
|
14
15
|
export * from './local-variables.js';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal metadata for listing scheduled tasks
|
|
4
|
+
*/
|
|
5
|
+
export interface ScheduledTaskSummary {
|
|
6
|
+
/** Unique task ID */
|
|
7
|
+
taskId: string;
|
|
8
|
+
/** RPC function name to invoke */
|
|
9
|
+
rpcName: string;
|
|
10
|
+
/** Scheduled execution time */
|
|
11
|
+
scheduledFor: Date;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Full metadata for a scheduled task retrieved from the queue
|
|
15
|
+
*/
|
|
16
|
+
export interface ScheduledTaskInfo extends ScheduledTaskSummary {
|
|
17
|
+
/** Data to pass to the RPC function */
|
|
18
|
+
data?: any;
|
|
19
|
+
/** User session */
|
|
20
|
+
session?: CoreUserSession;
|
|
21
|
+
/** Task status */
|
|
22
|
+
status?: 'scheduled' | 'active' | 'completed' | 'failed';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Abstract scheduler service for persistent scheduled task management
|
|
26
|
+
* Implementations use queue backends (pg-boss, BullMQ) for distributed scheduling
|
|
27
|
+
*/
|
|
28
|
+
export declare abstract class SchedulerService {
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the scheduler service
|
|
31
|
+
*/
|
|
32
|
+
abstract init(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Schedule a one-off delayed RPC call
|
|
35
|
+
* @param delay - Delay before execution (number in milliseconds or duration string like "5h", "30m")
|
|
36
|
+
* @param rpcName - RPC function name to invoke
|
|
37
|
+
* @param data - Data to pass to the RPC function
|
|
38
|
+
* @param session - Optional user session
|
|
39
|
+
* @returns Task ID
|
|
40
|
+
*/
|
|
41
|
+
abstract scheduleRPC(delay: number | string, rpcName: string, data?: any, session?: CoreUserSession): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Unschedule a task by ID
|
|
44
|
+
* @param taskId - Task ID
|
|
45
|
+
* @returns Success status
|
|
46
|
+
*/
|
|
47
|
+
abstract unschedule(taskId: string): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Get a scheduled task by ID with full details
|
|
50
|
+
* @param taskId - Task ID
|
|
51
|
+
* @returns Task info or null if not found
|
|
52
|
+
*/
|
|
53
|
+
abstract getTask(taskId: string): Promise<ScheduledTaskInfo | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Get all scheduled tasks with minimal info
|
|
56
|
+
* @returns Array of task summaries with taskId, rpcName, and schedule
|
|
57
|
+
*/
|
|
58
|
+
abstract getAllTasks(): Promise<ScheduledTaskSummary[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Close any open connections
|
|
61
|
+
*/
|
|
62
|
+
abstract close(): Promise<void>;
|
|
63
|
+
}
|
package/dist/time-utils.d.ts
CHANGED
|
@@ -9,4 +9,18 @@ export declare const getRelativeTimeOffset: ({ value, unit, }: RelativeTimeInput
|
|
|
9
9
|
* e.g. value = -1 and unit = 'day' => 1 day ago => now - 86400 seconds
|
|
10
10
|
*/
|
|
11
11
|
export declare const getRelativeTimeOffsetFromNow: (relativeTime: RelativeTimeInput) => Date;
|
|
12
|
+
/**
|
|
13
|
+
* Get duration in milliseconds from a string or number
|
|
14
|
+
* @param duration
|
|
15
|
+
* @returns
|
|
16
|
+
*/
|
|
17
|
+
export declare const getDurationInMilliseconds: (duration: string | number) => number;
|
|
18
|
+
/**
|
|
19
|
+
* Parse a duration string to milliseconds
|
|
20
|
+
* Supports formats like: '2s', '5s', '5sec', '5seconds', '5m', '5min', '5minutes', '1h', '1hour', '2d', '2day', '1w', '1week'
|
|
21
|
+
*
|
|
22
|
+
* @param duration - Duration string (e.g., '2s', '5min', '2hours', '1day')
|
|
23
|
+
* @returns Duration in milliseconds
|
|
24
|
+
*/
|
|
25
|
+
export declare const parseDurationString: (duration: string) => number;
|
|
12
26
|
export {};
|
package/dist/time-utils.js
CHANGED
|
@@ -16,3 +16,65 @@ export const getRelativeTimeOffset = ({ value, unit, }) => {
|
|
|
16
16
|
export const getRelativeTimeOffsetFromNow = (relativeTime) => {
|
|
17
17
|
return new Date(Date.now() + getRelativeTimeOffset(relativeTime));
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Get duration in milliseconds from a string or number
|
|
21
|
+
* @param duration
|
|
22
|
+
* @returns
|
|
23
|
+
*/
|
|
24
|
+
export const getDurationInMilliseconds = (duration) => {
|
|
25
|
+
if (typeof duration === 'number') {
|
|
26
|
+
return duration;
|
|
27
|
+
}
|
|
28
|
+
return parseDurationString(duration);
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Parse a duration string to milliseconds
|
|
32
|
+
* Supports formats like: '2s', '5s', '5sec', '5seconds', '5m', '5min', '5minutes', '1h', '1hour', '2d', '2day', '1w', '1week'
|
|
33
|
+
*
|
|
34
|
+
* @param duration - Duration string (e.g., '2s', '5min', '2hours', '1day')
|
|
35
|
+
* @returns Duration in milliseconds
|
|
36
|
+
*/
|
|
37
|
+
export const parseDurationString = (duration) => {
|
|
38
|
+
const match = duration.match(/^(\d+)(ms|milliseconds?|s|sec|seconds?|m|min|minutes?|h|hour|hours?|d|day|days?|w|week|weeks?|y|year|years?)$/);
|
|
39
|
+
if (!match) {
|
|
40
|
+
throw new Error(`Invalid duration format: ${duration}. Use formats like '2s', '5s', '5min', '1hour', '2days', '1week'`);
|
|
41
|
+
}
|
|
42
|
+
const value = parseInt(match[1], 10);
|
|
43
|
+
const unitStr = match[2];
|
|
44
|
+
// Handle milliseconds specially
|
|
45
|
+
if (unitStr === 'ms' ||
|
|
46
|
+
unitStr === 'millisecond' ||
|
|
47
|
+
unitStr === 'milliseconds') {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
// Map string variations to TimeUnit
|
|
51
|
+
let unit;
|
|
52
|
+
if (unitStr === 's' ||
|
|
53
|
+
unitStr === 'sec' ||
|
|
54
|
+
unitStr === 'second' ||
|
|
55
|
+
unitStr === 'seconds') {
|
|
56
|
+
unit = 'second';
|
|
57
|
+
}
|
|
58
|
+
else if (unitStr === 'm' ||
|
|
59
|
+
unitStr === 'min' ||
|
|
60
|
+
unitStr === 'minute' ||
|
|
61
|
+
unitStr === 'minutes') {
|
|
62
|
+
unit = 'minute';
|
|
63
|
+
}
|
|
64
|
+
else if (unitStr === 'h' || unitStr === 'hour' || unitStr === 'hours') {
|
|
65
|
+
unit = 'hour';
|
|
66
|
+
}
|
|
67
|
+
else if (unitStr === 'd' || unitStr === 'day' || unitStr === 'days') {
|
|
68
|
+
unit = 'day';
|
|
69
|
+
}
|
|
70
|
+
else if (unitStr === 'w' || unitStr === 'week' || unitStr === 'weeks') {
|
|
71
|
+
unit = 'week';
|
|
72
|
+
}
|
|
73
|
+
else if (unitStr === 'y' || unitStr === 'year' || unitStr === 'years') {
|
|
74
|
+
unit = 'year';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
throw new Error(`Unknown time unit: ${unitStr}`);
|
|
78
|
+
}
|
|
79
|
+
return getRelativeTimeOffset({ value, unit });
|
|
80
|
+
};
|
|
@@ -8,8 +8,10 @@ import { PikkuChannel } from '../wirings/channel/channel.types.js';
|
|
|
8
8
|
import { PikkuRPC } from '../wirings/rpc/rpc-types.js';
|
|
9
9
|
import { PikkuMCP } from '../wirings/mcp/mcp.types.js';
|
|
10
10
|
import { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js';
|
|
11
|
-
import { PikkuQueue } from '../wirings/queue/queue.types.js';
|
|
11
|
+
import { PikkuQueue, QueueService } from '../wirings/queue/queue.types.js';
|
|
12
12
|
import { PikkuCLI } from '../wirings/cli/cli.types.js';
|
|
13
|
+
import { PikkuWorkflowInteraction, WorkflowService, WorkflowServiceConfig, WorkflowStepInteraction } from '../wirings/workflow/workflow.types.js';
|
|
14
|
+
import { SchedulerService } from '../services/scheduler-service.js';
|
|
13
15
|
export declare enum PikkuWiringTypes {
|
|
14
16
|
http = "http",
|
|
15
17
|
scheduler = "scheduler",
|
|
@@ -17,7 +19,8 @@ export declare enum PikkuWiringTypes {
|
|
|
17
19
|
rpc = "rpc",
|
|
18
20
|
queue = "queue",
|
|
19
21
|
mcp = "mcp",
|
|
20
|
-
cli = "cli"
|
|
22
|
+
cli = "cli",
|
|
23
|
+
workflow = "workflow"
|
|
21
24
|
}
|
|
22
25
|
export interface FunctionServicesMeta {
|
|
23
26
|
optimized: boolean;
|
|
@@ -62,6 +65,7 @@ export type FunctionRuntimeMeta = {
|
|
|
62
65
|
inputSchemaName: string | null;
|
|
63
66
|
outputSchemaName: string | null;
|
|
64
67
|
expose?: boolean;
|
|
68
|
+
internal?: boolean;
|
|
65
69
|
};
|
|
66
70
|
export type FunctionMeta = FunctionRuntimeMeta & Partial<{
|
|
67
71
|
name: string;
|
|
@@ -109,6 +113,7 @@ export type CoreConfig<Config extends Record<string, unknown> = {}> = {
|
|
|
109
113
|
logLevel?: LogLevel;
|
|
110
114
|
/** Secrets used by the application (optional). */
|
|
111
115
|
secrets?: {};
|
|
116
|
+
workflow?: WorkflowServiceConfig;
|
|
112
117
|
} & Config;
|
|
113
118
|
/**
|
|
114
119
|
* Represents a core user session, which can be extended for more specific session information.
|
|
@@ -129,6 +134,12 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
129
134
|
logger: Logger;
|
|
130
135
|
/** The variable service to be used */
|
|
131
136
|
variables: VariablesService;
|
|
137
|
+
/** The workflow orchestrator service */
|
|
138
|
+
workflowService?: WorkflowService;
|
|
139
|
+
/** The queue service */
|
|
140
|
+
queueService?: QueueService;
|
|
141
|
+
/** The scheduler service */
|
|
142
|
+
schedulerService?: SchedulerService;
|
|
132
143
|
}
|
|
133
144
|
/**
|
|
134
145
|
* Represents different forms of interaction within Pikku and the outside world.
|
|
@@ -141,6 +152,8 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
|
|
|
141
152
|
scheduledTask: PikkuScheduledTask;
|
|
142
153
|
queue: PikkuQueue;
|
|
143
154
|
cli: PikkuCLI;
|
|
155
|
+
workflow: PikkuWorkflowInteraction;
|
|
156
|
+
workflowStep: WorkflowStepInteraction;
|
|
144
157
|
}>;
|
|
145
158
|
/**
|
|
146
159
|
* A function that can wrap an interaction and be called before or after
|
|
@@ -244,3 +257,12 @@ export type PikkuDocs = {
|
|
|
244
257
|
tags?: string[];
|
|
245
258
|
errors?: string[];
|
|
246
259
|
};
|
|
260
|
+
/**
|
|
261
|
+
* Serialized error for storage
|
|
262
|
+
*/
|
|
263
|
+
export interface SerializedError {
|
|
264
|
+
message: string;
|
|
265
|
+
stack?: string;
|
|
266
|
+
code?: string;
|
|
267
|
+
[key: string]: any;
|
|
268
|
+
}
|
package/dist/types/core.types.js
CHANGED
|
@@ -7,6 +7,7 @@ export var PikkuWiringTypes;
|
|
|
7
7
|
PikkuWiringTypes["queue"] = "queue";
|
|
8
8
|
PikkuWiringTypes["mcp"] = "mcp";
|
|
9
9
|
PikkuWiringTypes["cli"] = "cli";
|
|
10
|
+
PikkuWiringTypes["workflow"] = "workflow";
|
|
10
11
|
})(PikkuWiringTypes || (PikkuWiringTypes = {}));
|
|
11
12
|
/**
|
|
12
13
|
* Factory function for creating middleware with tree-shaking support
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CoreServices } from '../../types/core.types.js';
|
|
2
|
+
import { CoreChannel, ChannelMessageMeta } from './channel.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runs a channel lifecycle function (onConnect or onDisconnect) with proper middleware handling.
|
|
5
|
+
*
|
|
6
|
+
* This function:
|
|
7
|
+
* 1. Extracts inline middleware from the lifecycle config if present
|
|
8
|
+
* 2. Combines metadata middleware with inline middleware using combineMiddleware()
|
|
9
|
+
* 3. Runs the lifecycle function with middleware support
|
|
10
|
+
*
|
|
11
|
+
* @param channelConfig - The channel configuration
|
|
12
|
+
* @param meta - Metadata for the lifecycle function (connect or disconnect)
|
|
13
|
+
* @param lifecycleConfig - The onConnect or onDisconnect config (can be function or object with middleware)
|
|
14
|
+
* @param lifecycleType - Type of lifecycle for cache key ('connect' or 'disconnect')
|
|
15
|
+
* @param services - All services (singleton + session)
|
|
16
|
+
* @param channel - The channel instance
|
|
17
|
+
* @param data - Optional data to pass to the lifecycle function (for onConnect)
|
|
18
|
+
* @returns Promise<unknown> - Result from the lifecycle function (if any)
|
|
19
|
+
*/
|
|
20
|
+
export declare const runChannelLifecycleWithMiddleware: ({ channelConfig, meta, lifecycleConfig, lifecycleType, services, channel, data, }: {
|
|
21
|
+
channelConfig: CoreChannel<unknown, any>;
|
|
22
|
+
meta: ChannelMessageMeta;
|
|
23
|
+
lifecycleConfig: any;
|
|
24
|
+
lifecycleType: "connect" | "disconnect";
|
|
25
|
+
services: CoreServices;
|
|
26
|
+
channel: any;
|
|
27
|
+
data?: unknown;
|
|
28
|
+
}) => Promise<unknown>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PikkuWiringTypes, } from '../../types/core.types.js';
|
|
2
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
3
|
+
import { runPikkuFuncDirectly } from '../../function/function-runner.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs a channel lifecycle function (onConnect or onDisconnect) with proper middleware handling.
|
|
6
|
+
*
|
|
7
|
+
* This function:
|
|
8
|
+
* 1. Extracts inline middleware from the lifecycle config if present
|
|
9
|
+
* 2. Combines metadata middleware with inline middleware using combineMiddleware()
|
|
10
|
+
* 3. Runs the lifecycle function with middleware support
|
|
11
|
+
*
|
|
12
|
+
* @param channelConfig - The channel configuration
|
|
13
|
+
* @param meta - Metadata for the lifecycle function (connect or disconnect)
|
|
14
|
+
* @param lifecycleConfig - The onConnect or onDisconnect config (can be function or object with middleware)
|
|
15
|
+
* @param lifecycleType - Type of lifecycle for cache key ('connect' or 'disconnect')
|
|
16
|
+
* @param services - All services (singleton + session)
|
|
17
|
+
* @param channel - The channel instance
|
|
18
|
+
* @param data - Optional data to pass to the lifecycle function (for onConnect)
|
|
19
|
+
* @returns Promise<unknown> - Result from the lifecycle function (if any)
|
|
20
|
+
*/
|
|
21
|
+
export const runChannelLifecycleWithMiddleware = async ({ channelConfig, meta, lifecycleConfig, lifecycleType, services, channel, data, }) => {
|
|
22
|
+
// Extract middleware if lifecycle config is an object
|
|
23
|
+
const lifecycleMiddleware = typeof lifecycleConfig === 'object' && 'middleware' in lifecycleConfig
|
|
24
|
+
? lifecycleConfig.middleware || []
|
|
25
|
+
: [];
|
|
26
|
+
// Use combineMiddleware to properly resolve metadata + inline middleware
|
|
27
|
+
const allMiddleware = combineMiddleware(PikkuWiringTypes.channel, `${channelConfig.name}:${lifecycleType}`, {
|
|
28
|
+
wireInheritedMiddleware: meta.middleware,
|
|
29
|
+
wireMiddleware: lifecycleMiddleware,
|
|
30
|
+
});
|
|
31
|
+
// Run the lifecycle function
|
|
32
|
+
const runLifecycle = async () => {
|
|
33
|
+
return await runPikkuFuncDirectly(meta.pikkuFuncName, { ...services, channel }, data);
|
|
34
|
+
};
|
|
35
|
+
// Run with middleware if any
|
|
36
|
+
if (allMiddleware.length > 0) {
|
|
37
|
+
return await runMiddleware(services, { channel }, allMiddleware, runLifecycle);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return await runLifecycle();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -45,10 +45,31 @@ export const processMessageHandlers = (services, channelConfig, channelHandler)
|
|
|
45
45
|
channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
const { pikkuFuncName, middleware:
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
48
|
+
const { pikkuFuncName, middleware: routeInheritedMiddleware, permissions: inheritedPermissions, } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
|
|
49
|
+
// Get wire middleware: channel-level middleware + message-specific middleware
|
|
50
|
+
const channelWireMiddleware = channelConfig.middleware || [];
|
|
51
|
+
// Check if onMessage is a wrapper object vs direct function config:
|
|
52
|
+
// - Direct config: onMessage.func is a plain Function
|
|
53
|
+
// - Wrapper: onMessage.func is a CorePikkuFunctionConfig (has onMessage.func.func)
|
|
54
|
+
// - Simple wrapper: onMessage has both func (plain Function) and middleware properties
|
|
55
|
+
const isWrapper = onMessage &&
|
|
56
|
+
typeof onMessage === 'object' &&
|
|
57
|
+
'func' in onMessage &&
|
|
58
|
+
((typeof onMessage.func === 'object' && 'func' in onMessage.func) ||
|
|
59
|
+
'middleware' in onMessage);
|
|
60
|
+
const messageWireMiddleware = isWrapper ? onMessage.middleware || [] : [];
|
|
61
|
+
// Combine channel middleware with message middleware (actual functions)
|
|
62
|
+
// Channel middleware runs first, then message middleware
|
|
63
|
+
const wireMiddleware = [...channelWireMiddleware, ...messageWireMiddleware];
|
|
64
|
+
// Inherited middleware comes from metadata (tag groups, non-inline wire)
|
|
65
|
+
const inheritedMiddleware = routeInheritedMiddleware || [];
|
|
66
|
+
const wirePermissions = isWrapper ? onMessage.permissions : undefined;
|
|
67
|
+
// Create unique cache key that includes routing info to avoid cache collisions
|
|
68
|
+
// when multiple message handlers use the same function
|
|
69
|
+
const cacheKey = routingProperty
|
|
70
|
+
? `${channelConfig.name}:${routingProperty}:${routerValue}`
|
|
71
|
+
: `${channelConfig.name}:default`;
|
|
72
|
+
return await runPikkuFunc(PikkuWiringTypes.channel, cacheKey, pikkuFuncName, {
|
|
52
73
|
singletonServices: services,
|
|
53
74
|
getAllServices: () => ({
|
|
54
75
|
...services,
|
|
@@ -64,19 +85,28 @@ export const processMessageHandlers = (services, channelConfig, channelHandler)
|
|
|
64
85
|
interaction: { channel: channelHandler.getChannel() },
|
|
65
86
|
});
|
|
66
87
|
};
|
|
67
|
-
|
|
88
|
+
return async (rawData) => {
|
|
68
89
|
let result;
|
|
69
90
|
let processed = false;
|
|
70
91
|
// Route-specific handling
|
|
71
92
|
if (typeof rawData === 'string' && channelConfig.onMessageWiring) {
|
|
93
|
+
let messageData;
|
|
72
94
|
try {
|
|
73
|
-
|
|
95
|
+
messageData = JSON.parse(rawData);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// Most likely a json error.. ignore
|
|
99
|
+
}
|
|
100
|
+
if (messageData) {
|
|
74
101
|
const entries = Object.entries(channelConfig.onMessageWiring);
|
|
75
102
|
for (const [routingProperty, routes] of entries) {
|
|
76
103
|
const routerValue = messageData[routingProperty];
|
|
77
104
|
if (routerValue && routes[routerValue]) {
|
|
105
|
+
const { [routingProperty]: _, ...data } = messageData;
|
|
78
106
|
processed = true;
|
|
79
|
-
result =
|
|
107
|
+
result =
|
|
108
|
+
(await processMessage(data, routes[routerValue], routingProperty, routerValue)) || {};
|
|
109
|
+
result[routingProperty] = routerValue;
|
|
80
110
|
break;
|
|
81
111
|
}
|
|
82
112
|
}
|
|
@@ -86,9 +116,6 @@ export const processMessageHandlers = (services, channelConfig, channelHandler)
|
|
|
86
116
|
result = await processMessage(messageData, channelConfig.onMessage);
|
|
87
117
|
}
|
|
88
118
|
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
// Most likely a json error.. ignore
|
|
91
|
-
}
|
|
92
119
|
}
|
|
93
120
|
// Default handler if no routes matched and json data wasn't parsed
|
|
94
121
|
if (!processed && channelConfig.onMessage) {
|
|
@@ -101,5 +128,4 @@ export const processMessageHandlers = (services, channelConfig, channelHandler)
|
|
|
101
128
|
}
|
|
102
129
|
return result;
|
|
103
130
|
};
|
|
104
|
-
return onMessage;
|
|
105
131
|
};
|
|
@@ -25,7 +25,9 @@ export const wireChannel = (channel) => {
|
|
|
25
25
|
}
|
|
26
26
|
// Register onMessage function if provided
|
|
27
27
|
if (channel.onMessage && channelMeta.message?.pikkuFuncName) {
|
|
28
|
-
addFunction(channelMeta.message.pikkuFuncName, channel.onMessage
|
|
28
|
+
addFunction(channelMeta.message.pikkuFuncName, channel.onMessage.func instanceof Function
|
|
29
|
+
? channel.onMessage
|
|
30
|
+
: channel.onMessage.func);
|
|
29
31
|
}
|
|
30
32
|
// Register functions in onMessageWiring
|
|
31
33
|
if (channel.onMessageWiring && channelMeta.messageWirings) {
|
|
@@ -40,15 +42,18 @@ export const wireChannel = (channel) => {
|
|
|
40
42
|
if (!wiringMeta)
|
|
41
43
|
return;
|
|
42
44
|
// Register the function using the pikku name from metadata
|
|
43
|
-
|
|
45
|
+
// It could be a FuncConfig or a wiring override with a funcConfig
|
|
46
|
+
addFunction(wiringMeta.pikkuFuncName, handler.func instanceof Function
|
|
47
|
+
? handler
|
|
48
|
+
: handler.func);
|
|
44
49
|
});
|
|
45
50
|
});
|
|
46
51
|
}
|
|
47
52
|
// Store the channel configuration
|
|
48
53
|
pikkuState('channel', 'channels').set(channel.name, channel);
|
|
49
54
|
};
|
|
50
|
-
const getMatchingChannelConfig = (
|
|
51
|
-
const matchedPath = httpRouter.match('get',
|
|
55
|
+
const getMatchingChannelConfig = (path) => {
|
|
56
|
+
const matchedPath = httpRouter.match('get', path);
|
|
52
57
|
if (!matchedPath) {
|
|
53
58
|
return null;
|
|
54
59
|
}
|