@pikku/core 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/dist/function/function-runner.js +2 -2
- package/dist/services/file-channel-store.d.ts +19 -0
- package/dist/services/file-channel-store.js +69 -0
- package/dist/services/file-eventhub-store.d.ts +17 -0
- package/dist/services/file-eventhub-store.js +71 -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 +9 -0
- package/dist/wirings/cli/cli-runner.d.ts +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 +1 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +11 -0
- package/dist/wirings/rpc/rpc-runner.js +1 -1
- package/package.json +3 -3
- package/src/function/function-runner.ts +2 -2
- package/src/services/file-channel-store.ts +86 -0
- package/src/services/file-eventhub-store.ts +90 -0
- 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 +12 -0
- package/src/wirings/cli/cli-runner.ts +1 -1
- 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 +1 -0
- package/src/wirings/rpc/rpc-runner.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @pikku/core
|
|
2
2
|
|
|
3
|
+
## 0.10.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ea652dc: Refactor channel middleware handling and add lifecycle middleware support
|
|
8
|
+
|
|
9
|
+
**Breaking Changes:**
|
|
10
|
+
|
|
11
|
+
- Improved middleware resolution for channel message handlers to properly combine channel-level and message-level middleware
|
|
12
|
+
- Fixed cache key collisions when multiple message handlers use the same function
|
|
13
|
+
|
|
14
|
+
**New Features:**
|
|
15
|
+
|
|
16
|
+
- Add `runChannelLifecycleWithMiddleware` helper in `channel-common.ts` for consistent lifecycle function execution
|
|
17
|
+
- Support middleware on `onConnect` and `onDisconnect` lifecycle functions
|
|
18
|
+
- Channel-level middleware now properly applies to all messages in the channel
|
|
19
|
+
|
|
20
|
+
**Bug Fixes:**
|
|
21
|
+
|
|
22
|
+
- Fix middleware ordering: channel middleware → message middleware → inherited middleware
|
|
23
|
+
- Fix cache key generation to include routing information (prevents cache collisions)
|
|
24
|
+
- Properly detect wrapper objects vs direct function configs for message handlers
|
|
25
|
+
|
|
26
|
+
- 4349ec5: Add file-based storage implementations for serverless environments
|
|
27
|
+
|
|
28
|
+
**New Services:**
|
|
29
|
+
|
|
30
|
+
- Add `FileChannelStore` for file-based channel storage (suitable for AWS Lambda /tmp)
|
|
31
|
+
- Add `FileEventHubStore` for file-based event hub subscriptions
|
|
32
|
+
- Export new services in package.json for use in serverless runtimes
|
|
33
|
+
|
|
34
|
+
**Bug Fixes:**
|
|
35
|
+
|
|
36
|
+
- Fix serverless channel runner to handle disconnect gracefully when channel is already cleaned up
|
|
37
|
+
- Fix MCP runner to pass `mcp` service to functions and use correct function type
|
|
38
|
+
|
|
39
|
+
- 44d71a8: fix: fixing inspector ensuring pikkuConfig is set
|
|
40
|
+
|
|
41
|
+
## 0.10.1
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- 778267e: fix: fixing inspector ensuring pikkuConfig is set
|
|
46
|
+
|
|
3
47
|
## 0.9.0
|
|
4
48
|
|
|
5
49
|
## 0.10.0
|
|
@@ -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
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CoreUserSession } from '../types/core.types.js';
|
|
2
|
+
import { Channel, ChannelStore } from '../wirings/channel/channel-store.js';
|
|
3
|
+
/**
|
|
4
|
+
* File-based implementation of ChannelStore for serverless/multi-process environments.
|
|
5
|
+
* Stores channels as JSON files in a directory. Suitable for AWS Lambda /tmp storage
|
|
6
|
+
* or other shared filesystem scenarios.
|
|
7
|
+
*/
|
|
8
|
+
export declare class FileChannelStore extends ChannelStore {
|
|
9
|
+
private storageDir;
|
|
10
|
+
constructor(storageDir?: string);
|
|
11
|
+
private ensureDir;
|
|
12
|
+
private getChannelPath;
|
|
13
|
+
addChannel({ channelId, channelName, channelObject, openingData, }: Channel): Promise<void>;
|
|
14
|
+
removeChannels(channelIds: string[]): Promise<void>;
|
|
15
|
+
setUserSession(channelId: string, session: CoreUserSession | null): Promise<void>;
|
|
16
|
+
getChannelAndSession(channelId: string): Promise<Channel & {
|
|
17
|
+
session: CoreUserSession;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { ChannelStore } from '../wirings/channel/channel-store.js';
|
|
4
|
+
/**
|
|
5
|
+
* File-based implementation of ChannelStore for serverless/multi-process environments.
|
|
6
|
+
* Stores channels as JSON files in a directory. Suitable for AWS Lambda /tmp storage
|
|
7
|
+
* or other shared filesystem scenarios.
|
|
8
|
+
*/
|
|
9
|
+
export class FileChannelStore extends ChannelStore {
|
|
10
|
+
storageDir;
|
|
11
|
+
constructor(storageDir = '/tmp/pikku-channels') {
|
|
12
|
+
super();
|
|
13
|
+
this.storageDir = storageDir;
|
|
14
|
+
}
|
|
15
|
+
async ensureDir() {
|
|
16
|
+
try {
|
|
17
|
+
await fs.mkdir(this.storageDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
// Directory might already exist, ignore error
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
getChannelPath(channelId) {
|
|
24
|
+
return join(this.storageDir, `${channelId}.json`);
|
|
25
|
+
}
|
|
26
|
+
async addChannel({ channelId, channelName, channelObject, openingData, }) {
|
|
27
|
+
await this.ensureDir();
|
|
28
|
+
const data = {
|
|
29
|
+
channelId,
|
|
30
|
+
channelName,
|
|
31
|
+
channelObject,
|
|
32
|
+
openingData,
|
|
33
|
+
session: null,
|
|
34
|
+
};
|
|
35
|
+
await fs.writeFile(this.getChannelPath(channelId), JSON.stringify(data));
|
|
36
|
+
}
|
|
37
|
+
async removeChannels(channelIds) {
|
|
38
|
+
await Promise.all(channelIds.map(async (channelId) => {
|
|
39
|
+
try {
|
|
40
|
+
await fs.unlink(this.getChannelPath(channelId));
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
// File might not exist, ignore error
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
async setUserSession(channelId, session) {
|
|
48
|
+
const channelPath = this.getChannelPath(channelId);
|
|
49
|
+
try {
|
|
50
|
+
const content = await fs.readFile(channelPath, 'utf-8');
|
|
51
|
+
const channel = JSON.parse(content);
|
|
52
|
+
channel.session = session;
|
|
53
|
+
await fs.writeFile(channelPath, JSON.stringify(channel));
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw new Error(`Channel ${channelId} not found`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async getChannelAndSession(channelId) {
|
|
60
|
+
const channelPath = this.getChannelPath(channelId);
|
|
61
|
+
try {
|
|
62
|
+
const content = await fs.readFile(channelPath, 'utf-8');
|
|
63
|
+
return JSON.parse(content);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
throw new Error(`Channel ${channelId} not found`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { EventHubStore } from '../wirings/channel/eventhub-store.js';
|
|
2
|
+
/**
|
|
3
|
+
* File-based implementation of EventHubStore for serverless/multi-process environments.
|
|
4
|
+
* Stores subscriptions as JSON files in a directory. Suitable for AWS Lambda /tmp storage
|
|
5
|
+
* or other shared filesystem scenarios.
|
|
6
|
+
*/
|
|
7
|
+
export declare class FileEventHubStore<EventTopics extends Record<string, any> = {}> extends EventHubStore<EventTopics> {
|
|
8
|
+
private storageDir;
|
|
9
|
+
constructor(storageDir?: string);
|
|
10
|
+
private ensureDir;
|
|
11
|
+
private getTopicPath;
|
|
12
|
+
private readSubscriptions;
|
|
13
|
+
private writeSubscriptions;
|
|
14
|
+
getChannelIdsForTopic(topic: string): Promise<string[]>;
|
|
15
|
+
subscribe<T extends keyof EventTopics>(topic: T, channelId: string): Promise<boolean>;
|
|
16
|
+
unsubscribe<T extends keyof EventTopics>(topic: T, channelId: string): Promise<boolean>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { EventHubStore } from '../wirings/channel/eventhub-store.js';
|
|
4
|
+
/**
|
|
5
|
+
* File-based implementation of EventHubStore for serverless/multi-process environments.
|
|
6
|
+
* Stores subscriptions as JSON files in a directory. Suitable for AWS Lambda /tmp storage
|
|
7
|
+
* or other shared filesystem scenarios.
|
|
8
|
+
*/
|
|
9
|
+
export class FileEventHubStore extends EventHubStore {
|
|
10
|
+
storageDir;
|
|
11
|
+
constructor(storageDir = '/tmp/pikku-eventhub') {
|
|
12
|
+
super();
|
|
13
|
+
this.storageDir = storageDir;
|
|
14
|
+
}
|
|
15
|
+
async ensureDir() {
|
|
16
|
+
try {
|
|
17
|
+
await fs.mkdir(this.storageDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
// Directory might already exist, ignore error
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
getTopicPath(topic) {
|
|
24
|
+
// Sanitize topic name for filesystem
|
|
25
|
+
const safeTopic = topic.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
26
|
+
return join(this.storageDir, `${safeTopic}.json`);
|
|
27
|
+
}
|
|
28
|
+
async readSubscriptions(topic) {
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(this.getTopicPath(topic), 'utf-8');
|
|
31
|
+
const channelIds = JSON.parse(content);
|
|
32
|
+
return new Set(channelIds);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return new Set();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async writeSubscriptions(topic, channelIds) {
|
|
39
|
+
await this.ensureDir();
|
|
40
|
+
await fs.writeFile(this.getTopicPath(topic), JSON.stringify(Array.from(channelIds)));
|
|
41
|
+
}
|
|
42
|
+
async getChannelIdsForTopic(topic) {
|
|
43
|
+
const channelIds = await this.readSubscriptions(topic);
|
|
44
|
+
return Array.from(channelIds);
|
|
45
|
+
}
|
|
46
|
+
async subscribe(topic, channelId) {
|
|
47
|
+
const topicKey = topic;
|
|
48
|
+
const channelIds = await this.readSubscriptions(topicKey);
|
|
49
|
+
channelIds.add(channelId);
|
|
50
|
+
await this.writeSubscriptions(topicKey, channelIds);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
async unsubscribe(topic, channelId) {
|
|
54
|
+
const topicKey = topic;
|
|
55
|
+
const channelIds = await this.readSubscriptions(topicKey);
|
|
56
|
+
channelIds.delete(channelId);
|
|
57
|
+
if (channelIds.size === 0) {
|
|
58
|
+
// Remove the file if no subscriptions left
|
|
59
|
+
try {
|
|
60
|
+
await fs.unlink(this.getTopicPath(topicKey));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
// File might not exist, ignore error
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
await this.writeSubscriptions(topicKey, channelIds);
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -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
|
}
|
|
@@ -42,8 +42,14 @@ export type ChannelsMeta = Record<string, ChannelMeta>;
|
|
|
42
42
|
export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = CorePikkuFunctionConfig<CorePikkuFunction<void, unknown, ChannelData> | CorePikkuFunctionSessionless<void, unknown, ChannelData>, CorePikkuPermission<void>, CorePikkuMiddleware>, ChannelDisconnect = CorePikkuFunctionConfig<CorePikkuFunction<void, void, ChannelData> | CorePikkuFunctionSessionless<void, void, ChannelData>, CorePikkuPermission<void>, CorePikkuMiddleware>, ChannelFunctionMessage = CorePikkuFunctionConfig<CorePikkuFunction<unknown, unknown, ChannelData> | CorePikkuFunctionSessionless<unknown, unknown, ChannelData>, CorePikkuPermission<unknown>, CorePikkuMiddleware>, PikkuPermission = CorePikkuPermission<ChannelData>, PikkuMiddleware = CorePikkuMiddleware> = {
|
|
43
43
|
name: string;
|
|
44
44
|
route: Channel;
|
|
45
|
-
onConnect?: ChannelConnect
|
|
46
|
-
|
|
45
|
+
onConnect?: ChannelConnect | {
|
|
46
|
+
func?: ChannelConnect;
|
|
47
|
+
middleware?: PikkuMiddleware[];
|
|
48
|
+
};
|
|
49
|
+
onDisconnect?: ChannelDisconnect | {
|
|
50
|
+
func?: ChannelDisconnect;
|
|
51
|
+
middleware?: PikkuMiddleware[];
|
|
52
|
+
};
|
|
47
53
|
onMessage?: ChannelFunctionMessage;
|
|
48
54
|
onMessageWiring?: Record<string, Record<string, ChannelFunctionMessage | {
|
|
49
55
|
func: ChannelFunctionMessage;
|
|
@@ -3,12 +3,10 @@ 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';
|
|
7
6
|
import { handleHTTPError } from '../../../handle-error.js';
|
|
8
|
-
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
9
7
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js';
|
|
10
|
-
import { runPikkuFuncDirectly } from '../../../function/function-runner.js';
|
|
11
8
|
import { rpcService } from '../../rpc/rpc-runner.js';
|
|
9
|
+
import { runChannelLifecycleWithMiddleware } from '../channel-common.js';
|
|
12
10
|
export const runLocalChannel = async ({ singletonServices, channelId, request, response, route, createSessionServices, skipUserSession = false, respondWith404 = true, coerceDataFromSchema = true, logWarningsForStatusCodes = [], bubbleErrors = false, }) => {
|
|
13
11
|
let sessionServices;
|
|
14
12
|
let channelHandler;
|
|
@@ -49,23 +47,64 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
|
|
|
49
47
|
const getAllServices = (channel, requiresAuth) => rpcService.injectRPCService({
|
|
50
48
|
...singletonServices,
|
|
51
49
|
...sessionServices,
|
|
50
|
+
channel,
|
|
52
51
|
userSession,
|
|
53
52
|
}, interaction, requiresAuth);
|
|
54
53
|
const interaction = { channel };
|
|
55
|
-
channelHandler.registerOnOpen(() => {
|
|
54
|
+
channelHandler.registerOnOpen(async () => {
|
|
56
55
|
if (channelConfig.onConnect && meta.connect) {
|
|
57
|
-
|
|
56
|
+
try {
|
|
57
|
+
const result = await runChannelLifecycleWithMiddleware({
|
|
58
|
+
channelConfig,
|
|
59
|
+
meta: meta.connect,
|
|
60
|
+
lifecycleConfig: channelConfig.onConnect,
|
|
61
|
+
lifecycleType: 'connect',
|
|
62
|
+
services: getAllServices(channel, false),
|
|
63
|
+
channel,
|
|
64
|
+
data: openingData,
|
|
65
|
+
});
|
|
66
|
+
if (result !== undefined) {
|
|
67
|
+
await channel.send(result);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
singletonServices.logger.error(`Error handling onConnect: ${e}`);
|
|
72
|
+
channel.send({ error: e.message || 'Unknown error' });
|
|
73
|
+
}
|
|
58
74
|
}
|
|
59
75
|
});
|
|
60
76
|
channelHandler.registerOnClose(async () => {
|
|
61
77
|
if (channelConfig.onDisconnect && meta.disconnect) {
|
|
62
|
-
|
|
78
|
+
try {
|
|
79
|
+
await runChannelLifecycleWithMiddleware({
|
|
80
|
+
channelConfig,
|
|
81
|
+
meta: meta.disconnect,
|
|
82
|
+
lifecycleConfig: channelConfig.onDisconnect,
|
|
83
|
+
lifecycleType: 'disconnect',
|
|
84
|
+
services: getAllServices(channel, false),
|
|
85
|
+
channel,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
singletonServices.logger.error(`Error handling onDisconnect: ${e}`);
|
|
90
|
+
channel.send({ error: e.message || 'Unknown error' });
|
|
91
|
+
}
|
|
63
92
|
}
|
|
64
93
|
if (sessionServices) {
|
|
65
94
|
await closeSessionServices(singletonServices.logger, sessionServices);
|
|
66
95
|
}
|
|
67
96
|
});
|
|
68
|
-
|
|
97
|
+
const onMessage = processMessageHandlers(getAllServices(channel), channelConfig, channelHandler);
|
|
98
|
+
channelHandler.registerOnMessage(async (data) => {
|
|
99
|
+
try {
|
|
100
|
+
const result = await onMessage(data);
|
|
101
|
+
await channel.send(result);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
singletonServices.logger.error(`Error handling message: ${e.message}`);
|
|
105
|
+
channel.send({ error: e.message || 'Unknown error' });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
69
108
|
}
|
|
70
109
|
catch (e) {
|
|
71
110
|
handleHTTPError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
|
|
@@ -76,12 +115,6 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
|
|
|
76
115
|
}
|
|
77
116
|
}
|
|
78
117
|
};
|
|
79
|
-
await
|
|
80
|
-
...singletonServices,
|
|
81
|
-
userSession,
|
|
82
|
-
}, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
83
|
-
wireInheritedMiddleware: meta.middleware,
|
|
84
|
-
wireMiddleware: channelConfig.middleware,
|
|
85
|
-
}), main);
|
|
118
|
+
await main();
|
|
86
119
|
return channelHandler;
|
|
87
120
|
};
|