@pikku/core 0.7.0 → 0.7.1
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 +6 -0
- package/dist/channel/channel-runner.js +1 -1
- package/dist/channel/serverless/serverless-channel-runner.js +1 -1
- package/dist/http/http-runner.js +4 -2
- package/dist/http/log-http-routes.js +1 -1
- package/lcov.info +4585 -0
- package/package.json +1 -1
- package/src/channel/channel-handler.ts +165 -0
- package/src/channel/channel-runner.ts +153 -0
- package/src/channel/channel-store.ts +29 -0
- package/src/channel/channel.types.ts +133 -0
- package/src/channel/eventhub-service.ts +30 -0
- package/src/channel/eventhub-store.ts +8 -0
- package/src/channel/index.ts +8 -0
- package/src/channel/local/index.ts +3 -0
- package/src/channel/local/local-channel-handler.ts +50 -0
- package/src/channel/local/local-channel-runner.test.ts +157 -0
- package/src/channel/local/local-channel-runner.ts +139 -0
- package/src/channel/local/local-eventhub-service.test.ts +95 -0
- package/src/channel/local/local-eventhub-service.ts +95 -0
- package/src/channel/log-channels.ts +20 -0
- package/src/channel/pikku-abstract-channel-handler.test.ts +52 -0
- package/src/channel/pikku-abstract-channel-handler.ts +38 -0
- package/src/channel/serverless/index.ts +1 -0
- package/src/channel/serverless/serverless-channel-runner.ts +220 -0
- package/src/errors/error-handler.ts +61 -0
- package/src/errors/error.test.ts +54 -0
- package/src/errors/errors.ts +339 -0
- package/src/errors/index.ts +2 -0
- package/src/function/function-runner.ts +91 -0
- package/src/function/functions.types.ts +77 -0
- package/src/function/index.ts +2 -0
- package/src/handle-error.ts +73 -0
- package/src/http/http-runner.test.ts +140 -0
- package/src/http/http-runner.ts +493 -0
- package/src/http/http.types.ts +250 -0
- package/src/http/incomingmessage-to-request-convertor.ts +0 -0
- package/src/http/index.ts +8 -0
- package/src/http/log-http-routes.ts +22 -0
- package/src/http/pikku-fetch-http-request.test.ts +237 -0
- package/src/http/pikku-fetch-http-request.ts +163 -0
- package/src/http/pikku-fetch-http-response.test.ts +82 -0
- package/src/http/pikku-fetch-http-response.ts +138 -0
- package/src/index.ts +20 -0
- package/src/middleware/auth-apikey.ts +66 -0
- package/src/middleware/auth-bearer.ts +66 -0
- package/src/middleware/auth-cookie.ts +103 -0
- package/src/middleware/index.ts +3 -0
- package/src/middleware/timeout.ts +13 -0
- package/src/middleware-runner.ts +43 -0
- package/src/permissions.test.ts +58 -0
- package/src/permissions.ts +43 -0
- package/src/pikku-function.ts +1 -0
- package/src/pikku-request.ts +23 -0
- package/src/pikku-response.ts +5 -0
- package/src/pikku-state.ts +87 -0
- package/src/scheduler/index.ts +5 -0
- package/src/scheduler/log-schedulers.ts +20 -0
- package/src/scheduler/scheduler-runner.ts +105 -0
- package/src/scheduler/scheduler.types.ts +33 -0
- package/src/schema.test.ts +57 -0
- package/src/schema.ts +99 -0
- package/src/services/content-service.ts +68 -0
- package/src/services/index.ts +18 -0
- package/src/services/jwt-service.ts +30 -0
- package/src/services/local-content.ts +105 -0
- package/src/services/local-secrets.ts +43 -0
- package/src/services/local-variables.ts +17 -0
- package/src/services/logger-console.ts +97 -0
- package/src/services/logger.ts +57 -0
- package/src/services/schema-service.ts +26 -0
- package/src/services/secret-service.ts +17 -0
- package/src/services/user-session-service.ts +53 -0
- package/src/services/variables-service.ts +6 -0
- package/src/time-utils.test.ts +56 -0
- package/src/time-utils.ts +32 -0
- package/src/types/core.types.ts +178 -0
- package/src/utils.ts +40 -0
- package/tsconfig.json +13 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/http/http-route-runner.d.ts +0 -90
- package/dist/http/http-route-runner.js +0 -325
- package/dist/http/http-routes.types.d.ts +0 -179
- package/dist/http/http-routes.types.js +0 -1
- package/dist/http/pikku-http-request.d.ts +0 -56
- package/dist/http/pikku-http-request.js +0 -95
- package/dist/http/pikku-http-response.d.ts +0 -13
- package/dist/http/pikku-http-response.js +0 -60
- package/dist/parse-relative-time-offset.d.ts +0 -12
- package/dist/parse-relative-time-offset.js +0 -20
- package/dist/pikku-func.d.ts +0 -12
- package/dist/pikku-func.js +0 -39
- package/dist/types/functions.types.d.ts +0 -37
- package/dist/types/functions.types.js +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CoreServices,
|
|
3
|
+
JSONValue,
|
|
4
|
+
CoreUserSession,
|
|
5
|
+
} from '../types/core.types.js'
|
|
6
|
+
import {
|
|
7
|
+
ChannelMessageMeta,
|
|
8
|
+
CoreAPIChannel,
|
|
9
|
+
PikkuChannelHandler,
|
|
10
|
+
} from './channel.types.js'
|
|
11
|
+
import { pikkuState } from '../pikku-state.js'
|
|
12
|
+
import { runPikkuFunc } from '../function/function-runner.js'
|
|
13
|
+
|
|
14
|
+
const getRouteMeta = (
|
|
15
|
+
channelName: string,
|
|
16
|
+
routingProperty?: string,
|
|
17
|
+
routerValue?: string
|
|
18
|
+
): ChannelMessageMeta => {
|
|
19
|
+
const channelMeta = pikkuState('channel', 'meta')[channelName]
|
|
20
|
+
if (!channelMeta) {
|
|
21
|
+
throw new Error(`Channel ${channelName} not found`)
|
|
22
|
+
}
|
|
23
|
+
if (!routingProperty) {
|
|
24
|
+
if (!channelMeta.message) {
|
|
25
|
+
throw new Error(`Channel ${channelName} has no default message route`)
|
|
26
|
+
}
|
|
27
|
+
return channelMeta.message
|
|
28
|
+
}
|
|
29
|
+
if (!routerValue) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Channel ${channelName} requires a router value for ${routingProperty}`
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
const route = channelMeta.messageRoutes[routingProperty]?.[routerValue]
|
|
35
|
+
if (!route) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Channel ${channelName} has no route for ${routingProperty}:${routerValue}`
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
return route
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const validateAuth = (
|
|
44
|
+
requiresSession: boolean,
|
|
45
|
+
session: CoreUserSession | undefined,
|
|
46
|
+
onMessage: any
|
|
47
|
+
) => {
|
|
48
|
+
const auth =
|
|
49
|
+
typeof onMessage === 'function'
|
|
50
|
+
? requiresSession
|
|
51
|
+
: onMessage.auth === undefined
|
|
52
|
+
? requiresSession
|
|
53
|
+
: onMessage.auth
|
|
54
|
+
|
|
55
|
+
if (auth && !session) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const processMessageHandlers = (
|
|
62
|
+
services: CoreServices,
|
|
63
|
+
session: CoreUserSession | undefined,
|
|
64
|
+
channelConfig: CoreAPIChannel<any, any>,
|
|
65
|
+
channelHandler: PikkuChannelHandler
|
|
66
|
+
) => {
|
|
67
|
+
const logger = services.logger
|
|
68
|
+
const requiresSession = channelConfig.auth !== false
|
|
69
|
+
|
|
70
|
+
const processMessage = async (
|
|
71
|
+
data: JSONValue,
|
|
72
|
+
onMessage: any,
|
|
73
|
+
session: CoreUserSession | undefined,
|
|
74
|
+
routingProperty?: string,
|
|
75
|
+
routerValue?: string
|
|
76
|
+
): Promise<unknown> => {
|
|
77
|
+
if (!validateAuth(requiresSession, channelHandler, onMessage)) {
|
|
78
|
+
const routeMessage = routingProperty
|
|
79
|
+
? `route '${routingProperty}:${routerValue}'`
|
|
80
|
+
: 'the default message route'
|
|
81
|
+
logger.error(
|
|
82
|
+
`Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}`
|
|
83
|
+
)
|
|
84
|
+
// TODO: Send error message back breaks typescript, but should be implemented somehow
|
|
85
|
+
channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { pikkuFuncName } = getRouteMeta(
|
|
90
|
+
channelConfig.name,
|
|
91
|
+
routingProperty,
|
|
92
|
+
routerValue
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const permissions =
|
|
96
|
+
typeof onMessage === 'function' ? {} : onMessage.permissions
|
|
97
|
+
|
|
98
|
+
return await runPikkuFunc(pikkuFuncName, {
|
|
99
|
+
singletonServices: services,
|
|
100
|
+
getAllServices: () => ({
|
|
101
|
+
...services,
|
|
102
|
+
channel: channelHandler.getChannel(),
|
|
103
|
+
}),
|
|
104
|
+
data,
|
|
105
|
+
session,
|
|
106
|
+
permissions,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const onMessage = async (rawData): Promise<unknown> => {
|
|
111
|
+
let result: unknown
|
|
112
|
+
let processed = false
|
|
113
|
+
|
|
114
|
+
// Route-specific handling
|
|
115
|
+
if (typeof rawData === 'string' && channelConfig.onMessageRoute) {
|
|
116
|
+
try {
|
|
117
|
+
const messageData = JSON.parse(rawData)
|
|
118
|
+
const entries = Object.entries(channelConfig.onMessageRoute)
|
|
119
|
+
for (const [routingProperty, routes] of entries) {
|
|
120
|
+
const routerValue = messageData[routingProperty]
|
|
121
|
+
if (routerValue && routes[routerValue]) {
|
|
122
|
+
processed = true
|
|
123
|
+
result = await processMessage(
|
|
124
|
+
messageData,
|
|
125
|
+
routes[routerValue],
|
|
126
|
+
session,
|
|
127
|
+
routingProperty,
|
|
128
|
+
routerValue
|
|
129
|
+
)
|
|
130
|
+
break
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Default handler if no routes matched but json data was parsed
|
|
135
|
+
if (!processed && channelConfig.onMessage) {
|
|
136
|
+
processed = true
|
|
137
|
+
result = await processMessage(
|
|
138
|
+
messageData,
|
|
139
|
+
channelConfig.onMessage,
|
|
140
|
+
session
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Most likely a json error.. ignore
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Default handler if no routes matched and json data wasn't parsed
|
|
149
|
+
if (!processed && channelConfig.onMessage) {
|
|
150
|
+
processed = true
|
|
151
|
+
result = await processMessage(rawData, channelConfig.onMessage, session)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!processed) {
|
|
155
|
+
logger.error(
|
|
156
|
+
`No handler found for message in channel ${channelConfig.name} for ${JSON.stringify(rawData)}`
|
|
157
|
+
)
|
|
158
|
+
logger.error(`Channel ${JSON.stringify(channelConfig)}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return onMessage
|
|
165
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { NotFoundError } from '../errors/errors.js'
|
|
2
|
+
import { addFunction } from '../function/function-runner.js'
|
|
3
|
+
import { pikkuState } from '../pikku-state.js'
|
|
4
|
+
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
|
|
5
|
+
import { UserSessionService } from '../services/user-session-service.js'
|
|
6
|
+
import {
|
|
7
|
+
ChannelMeta,
|
|
8
|
+
CoreAPIChannel,
|
|
9
|
+
RunChannelOptions,
|
|
10
|
+
RunChannelParams,
|
|
11
|
+
} from './channel.types.js'
|
|
12
|
+
import { match } from 'path-to-regexp'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Adds a channel and registers all functions referenced in it using the
|
|
16
|
+
* function names already stored in the channel metadata
|
|
17
|
+
*/
|
|
18
|
+
export const addChannel = <
|
|
19
|
+
In,
|
|
20
|
+
Channel extends string,
|
|
21
|
+
ChannelFunction,
|
|
22
|
+
ChannelFunctionSessionless,
|
|
23
|
+
APIPermission,
|
|
24
|
+
>(
|
|
25
|
+
channel: CoreAPIChannel<In, Channel, ChannelFunction, APIPermission>
|
|
26
|
+
) => {
|
|
27
|
+
// Get the channel metadata
|
|
28
|
+
const channelsMeta = pikkuState('channel', 'meta')
|
|
29
|
+
const channelMeta = channelsMeta[channel.name]
|
|
30
|
+
if (!channelMeta) {
|
|
31
|
+
throw new Error(`Channel metadata not found for channel: ${channel.name}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pikkuState('channel', 'channels').set(channel.name, channel as any)
|
|
35
|
+
|
|
36
|
+
// Register onConnect function if provided
|
|
37
|
+
if (channel.onConnect && channelMeta.connectPikkuFuncName) {
|
|
38
|
+
addFunction(channelMeta.connectPikkuFuncName, channel.onConnect as any)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Register onDisconnect function if provided
|
|
42
|
+
if (channel.onDisconnect && channelMeta.disconnectPikkuFuncName) {
|
|
43
|
+
addFunction(
|
|
44
|
+
channelMeta.disconnectPikkuFuncName,
|
|
45
|
+
channel.onDisconnect as any
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Register onMessage function if provided
|
|
50
|
+
if (channel.onMessage && channelMeta.message?.pikkuFuncName) {
|
|
51
|
+
const messageFunc =
|
|
52
|
+
typeof channel.onMessage === 'function'
|
|
53
|
+
? channel.onMessage
|
|
54
|
+
: channel.onMessage.func
|
|
55
|
+
addFunction(channelMeta.message.pikkuFuncName, messageFunc as any)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Register functions in onMessageRoute
|
|
59
|
+
if (channel.onMessageRoute && channelMeta.messageRoutes) {
|
|
60
|
+
// Iterate through each channel in onMessageRoute
|
|
61
|
+
Object.entries(channel.onMessageRoute).forEach(([channelKey, routes]) => {
|
|
62
|
+
const channelRoutes = channelMeta.messageRoutes[channelKey]
|
|
63
|
+
if (!channelRoutes) return
|
|
64
|
+
|
|
65
|
+
// Iterate through each route in the channel
|
|
66
|
+
Object.entries(routes).forEach(([routeKey, handler]) => {
|
|
67
|
+
const routeMeta = channelRoutes[routeKey]
|
|
68
|
+
if (!routeMeta) return
|
|
69
|
+
|
|
70
|
+
// Extract the function from the handler
|
|
71
|
+
const routeFunc = typeof handler === 'function' ? handler : handler.func
|
|
72
|
+
|
|
73
|
+
// Register the function using the pikku name from metadata
|
|
74
|
+
addFunction(routeMeta.pikkuFuncName, routeFunc as any)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Store the channel configuration
|
|
80
|
+
pikkuState('channel', 'channels').set(channel.name, channel as any)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const getMatchingChannelConfig = (request: string) => {
|
|
84
|
+
const channels = pikkuState('channel', 'channels')
|
|
85
|
+
const channelsMeta = pikkuState('channel', 'meta')
|
|
86
|
+
for (const channelConfig of channels.values()) {
|
|
87
|
+
const cleanedRoute = channelConfig.route.replace(/^\/\//, '/')
|
|
88
|
+
const cleanedRequest = request.replace(/^\/\//, '/')
|
|
89
|
+
const matchFunc = match(cleanedRoute, {
|
|
90
|
+
decode: decodeURIComponent,
|
|
91
|
+
})
|
|
92
|
+
const matchedPath = matchFunc(cleanedRequest)
|
|
93
|
+
if (matchedPath) {
|
|
94
|
+
const channelMeta = channelsMeta[channelConfig.name]
|
|
95
|
+
if (!channelMeta) {
|
|
96
|
+
throw new Error(`Channel ${channelConfig.name} not found in metadata`)
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
matchedPath,
|
|
100
|
+
params: matchedPath.params,
|
|
101
|
+
channelConfig,
|
|
102
|
+
schemaName: channelMeta.input,
|
|
103
|
+
meta: channelMeta,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const openChannel = async ({
|
|
111
|
+
route,
|
|
112
|
+
singletonServices,
|
|
113
|
+
coerceDataFromSchema = true,
|
|
114
|
+
request,
|
|
115
|
+
}: Pick<CoreAPIChannel<unknown, string>, 'route'> &
|
|
116
|
+
RunChannelParams<unknown> & {
|
|
117
|
+
userSession: UserSessionService<any>
|
|
118
|
+
} & RunChannelOptions): Promise<{
|
|
119
|
+
openingData: unknown
|
|
120
|
+
channelConfig: CoreAPIChannel<unknown, any>
|
|
121
|
+
meta: ChannelMeta
|
|
122
|
+
}> => {
|
|
123
|
+
const matchingChannel = getMatchingChannelConfig(route)
|
|
124
|
+
if (!matchingChannel) {
|
|
125
|
+
singletonServices.logger.info(`Channel not found: ${route}`)
|
|
126
|
+
throw new NotFoundError(`Channel not found: ${route}`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { params, channelConfig, schemaName, meta } = matchingChannel
|
|
130
|
+
|
|
131
|
+
const requiresSession = channelConfig.auth !== false
|
|
132
|
+
request?.setParams(params)
|
|
133
|
+
|
|
134
|
+
singletonServices.logger.info(
|
|
135
|
+
`Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
let openingData: any | undefined
|
|
139
|
+
if (request) {
|
|
140
|
+
openingData = await request.data()
|
|
141
|
+
if (coerceDataFromSchema && schemaName) {
|
|
142
|
+
coerceTopLevelDataFromSchema(schemaName, openingData)
|
|
143
|
+
}
|
|
144
|
+
await validateSchema(
|
|
145
|
+
singletonServices.logger,
|
|
146
|
+
singletonServices.schema,
|
|
147
|
+
schemaName,
|
|
148
|
+
openingData
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { openingData, channelConfig, meta }
|
|
153
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CoreUserSession } from '../types/core.types.js'
|
|
2
|
+
|
|
3
|
+
export type Channel<ChannelType = unknown, OpeningData = unknown> = {
|
|
4
|
+
channelId: string
|
|
5
|
+
channelName: string
|
|
6
|
+
channelObject?: ChannelType
|
|
7
|
+
openingData?: OpeningData
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export abstract class ChannelStore<
|
|
11
|
+
ChannelType = unknown,
|
|
12
|
+
OpeningData = unknown,
|
|
13
|
+
UserSession extends CoreUserSession = CoreUserSession,
|
|
14
|
+
TypedChannel = Channel<ChannelType, OpeningData>,
|
|
15
|
+
> {
|
|
16
|
+
public abstract addChannel(
|
|
17
|
+
channel: Channel<ChannelType, OpeningData>
|
|
18
|
+
): Promise<void> | void
|
|
19
|
+
public abstract removeChannels(channelId: string[]): Promise<void> | void
|
|
20
|
+
public abstract setUserSession(
|
|
21
|
+
channelId: string,
|
|
22
|
+
userSession: UserSession | null
|
|
23
|
+
): Promise<void> | void
|
|
24
|
+
public abstract getChannelAndSession(
|
|
25
|
+
channelId: string
|
|
26
|
+
):
|
|
27
|
+
| Promise<TypedChannel & { session: UserSession }>
|
|
28
|
+
| (TypedChannel & { session: UserSession })
|
|
29
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { PikkuError } from '../errors/error-handler.js'
|
|
2
|
+
import {
|
|
3
|
+
HTTPFunctionMetaInputTypes,
|
|
4
|
+
PikkuHTTPRequest,
|
|
5
|
+
PikkuHTTPResponse,
|
|
6
|
+
} from '../http/http.types.js'
|
|
7
|
+
import {
|
|
8
|
+
APIDocs,
|
|
9
|
+
CoreSingletonServices,
|
|
10
|
+
CreateSessionServices,
|
|
11
|
+
PikkuMiddleware,
|
|
12
|
+
} from '../types/core.types.js'
|
|
13
|
+
import {
|
|
14
|
+
CoreAPIFunction,
|
|
15
|
+
CoreAPIFunctionSessionless,
|
|
16
|
+
CoreAPIPermission,
|
|
17
|
+
} from '../function/functions.types.js'
|
|
18
|
+
|
|
19
|
+
export type RunChannelOptions = Partial<{
|
|
20
|
+
skipUserSession: boolean
|
|
21
|
+
respondWith404: boolean
|
|
22
|
+
coerceDataFromSchema: boolean
|
|
23
|
+
logWarningsForStatusCodes: number[]
|
|
24
|
+
bubbleErrors: boolean
|
|
25
|
+
}>
|
|
26
|
+
|
|
27
|
+
export type RunChannelParams<ChannelData> = {
|
|
28
|
+
channelId: string
|
|
29
|
+
singletonServices: CoreSingletonServices
|
|
30
|
+
request?: PikkuHTTPRequest<ChannelData>
|
|
31
|
+
response?: PikkuHTTPResponse
|
|
32
|
+
createSessionServices?: CreateSessionServices
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ChannelMessageMeta {
|
|
36
|
+
pikkuFuncName: string
|
|
37
|
+
inputs: string[] | null
|
|
38
|
+
outputs: string[] | null
|
|
39
|
+
docs?: APIDocs
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ChannelMeta {
|
|
43
|
+
name: string
|
|
44
|
+
route: string
|
|
45
|
+
params?: string[]
|
|
46
|
+
query?: string[]
|
|
47
|
+
input: string | null
|
|
48
|
+
inputTypes?: HTTPFunctionMetaInputTypes
|
|
49
|
+
connect: boolean
|
|
50
|
+
connectPikkuFuncName: string | null
|
|
51
|
+
disconnect: boolean
|
|
52
|
+
disconnectPikkuFuncName: string | null
|
|
53
|
+
message: ChannelMessageMeta | null
|
|
54
|
+
messageRoutes: Record<string, Record<string, ChannelMessageMeta>>
|
|
55
|
+
docs?: APIDocs
|
|
56
|
+
tags?: string[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ChannelsMeta = Record<string, ChannelMeta>
|
|
60
|
+
|
|
61
|
+
export type CoreAPIChannel<
|
|
62
|
+
ChannelData,
|
|
63
|
+
Channel extends string,
|
|
64
|
+
ChannelConnect =
|
|
65
|
+
| CoreAPIFunction<void, unknown, ChannelData>
|
|
66
|
+
| CoreAPIFunctionSessionless<void, unknown, ChannelData>,
|
|
67
|
+
ChannelDisconnect =
|
|
68
|
+
| CoreAPIFunction<void, void, ChannelData>
|
|
69
|
+
| CoreAPIFunctionSessionless<void, void, ChannelData>,
|
|
70
|
+
ChannelFunctionMessage =
|
|
71
|
+
| CoreAPIFunction<unknown, unknown, ChannelData>
|
|
72
|
+
| CoreAPIFunctionSessionless<unknown, unknown, ChannelData>,
|
|
73
|
+
APIPermission = CoreAPIPermission<ChannelData>,
|
|
74
|
+
> = {
|
|
75
|
+
name: string
|
|
76
|
+
route: Channel
|
|
77
|
+
onConnect?: ChannelConnect
|
|
78
|
+
onDisconnect?: ChannelDisconnect
|
|
79
|
+
onMessage?:
|
|
80
|
+
| {
|
|
81
|
+
func: ChannelFunctionMessage
|
|
82
|
+
permissions?: Record<string, APIPermission[] | APIPermission>
|
|
83
|
+
auth?: boolean
|
|
84
|
+
}
|
|
85
|
+
| ChannelFunctionMessage
|
|
86
|
+
onMessageRoute?: Record<
|
|
87
|
+
string,
|
|
88
|
+
Record<
|
|
89
|
+
string,
|
|
90
|
+
| ChannelFunctionMessage
|
|
91
|
+
| {
|
|
92
|
+
func: ChannelFunctionMessage
|
|
93
|
+
permissions?: Record<string, APIPermission[] | APIPermission>
|
|
94
|
+
auth?: boolean
|
|
95
|
+
}
|
|
96
|
+
>
|
|
97
|
+
>
|
|
98
|
+
middleware?: PikkuMiddleware[]
|
|
99
|
+
permissions?: Record<string, APIPermission[] | APIPermission>
|
|
100
|
+
auth?: boolean
|
|
101
|
+
docs?: Partial<{
|
|
102
|
+
description: string
|
|
103
|
+
response: string
|
|
104
|
+
errors: Array<typeof PikkuError>
|
|
105
|
+
tags: string[]
|
|
106
|
+
}>
|
|
107
|
+
tags?: string[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface PikkuChannel<OpeningData, Out> {
|
|
111
|
+
// The channel identifier
|
|
112
|
+
channelId: string
|
|
113
|
+
// The data the channel was created with. This could be query parameters
|
|
114
|
+
// or parameters in the url.
|
|
115
|
+
openingData: OpeningData
|
|
116
|
+
// The data to send. This will fail is the stream has been closed.
|
|
117
|
+
send: (data: Out, isBinary?: boolean) => Promise<void> | void
|
|
118
|
+
// This will close the channel.
|
|
119
|
+
close: () => Promise<void> | void
|
|
120
|
+
// The current state of the channel
|
|
121
|
+
state: 'initial' | 'open' | 'closed'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface PikkuChannelHandler<OpeningData = unknown, Out = unknown> {
|
|
125
|
+
send(message: Out, isBinary?: boolean): Promise<void> | void
|
|
126
|
+
getChannel(): PikkuChannel<OpeningData, Out>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export type PikkuChannelHandlerFactory<OpeningData = unknown, Out = unknown> = (
|
|
130
|
+
channelId: string,
|
|
131
|
+
channelName: string,
|
|
132
|
+
openingData: OpeningData
|
|
133
|
+
) => PikkuChannelHandler<OpeningData, Out>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface defining the methods of a EventHub Service.
|
|
3
|
+
*/
|
|
4
|
+
export interface EventHubService<Out = unknown> {
|
|
5
|
+
/**
|
|
6
|
+
* Subscribes a connection to a specific topic.
|
|
7
|
+
* @param topic - The topic to subscribe to.
|
|
8
|
+
* @param channelId - The unique ID of the connection to subscribe.
|
|
9
|
+
*/
|
|
10
|
+
subscribe(topic: string, channelId: string): Promise<void> | void
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Unsubscribes a connection from a specific topic.
|
|
14
|
+
* @param topic - The topic to unsubscribe from.
|
|
15
|
+
* @param channelId - The unique ID of the connection to unsubscribe.
|
|
16
|
+
*/
|
|
17
|
+
unsubscribe(topic: string, channelId: string): Promise<void> | void
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sends data to all connections subscribed to a topic.
|
|
21
|
+
* @param topic - The topic to send data to.
|
|
22
|
+
* @param data - The data to send to the subscribers.
|
|
23
|
+
*/
|
|
24
|
+
publish(
|
|
25
|
+
topic: string,
|
|
26
|
+
channelId: string | null,
|
|
27
|
+
data: Out,
|
|
28
|
+
isBinary?: boolean
|
|
29
|
+
): Promise<void> | void
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export abstract class EventHubStore {
|
|
2
|
+
public abstract getChannelIdsForTopic(topic: string): Promise<string[]>
|
|
3
|
+
public abstract subscribe(topic: string, channelId: string): Promise<boolean>
|
|
4
|
+
public abstract unsubscribe(
|
|
5
|
+
topic: string,
|
|
6
|
+
channelId: string
|
|
7
|
+
): Promise<boolean>
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './channel-runner.js'
|
|
2
|
+
export * from './channel.types.js'
|
|
3
|
+
export * from './log-channels.js'
|
|
4
|
+
export * from './pikku-abstract-channel-handler.js'
|
|
5
|
+
export * from './eventhub-service.js'
|
|
6
|
+
export * from './eventhub-service.js'
|
|
7
|
+
export * from './channel-store.js'
|
|
8
|
+
export * from './eventhub-store.js'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PikkuAbstractChannelHandler } from '../pikku-abstract-channel-handler.js'
|
|
2
|
+
|
|
3
|
+
export class PikkuLocalChannelHandler<
|
|
4
|
+
OpeningData = unknown,
|
|
5
|
+
Out = unknown,
|
|
6
|
+
> extends PikkuAbstractChannelHandler<OpeningData, Out> {
|
|
7
|
+
private onMessageCallback?: (message: unknown) => void
|
|
8
|
+
private openCallBack?: () => void
|
|
9
|
+
private closeCallback?: () => void
|
|
10
|
+
private sendCallback?: (message: Out, isBinary?: boolean) => void
|
|
11
|
+
|
|
12
|
+
public registerOnOpen(callback: () => void): void {
|
|
13
|
+
this.openCallBack = callback
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public open() {
|
|
17
|
+
this.getChannel().state = 'open'
|
|
18
|
+
if (this.openCallBack) {
|
|
19
|
+
this.openCallBack()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public registerOnMessage(callback: (data: any) => Promise<unknown>): void {
|
|
24
|
+
this.onMessageCallback = callback
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async message(data: unknown): Promise<unknown> {
|
|
28
|
+
return this.onMessageCallback?.(data)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public registerOnClose(callback: () => void): void {
|
|
32
|
+
this.closeCallback = callback
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public close() {
|
|
36
|
+
super.close()
|
|
37
|
+
this.closeCallback?.()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public registerOnSend(send: (message: Out) => void) {
|
|
41
|
+
this.sendCallback = send
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public send(message: Out, isBinary?: boolean): void {
|
|
45
|
+
if (!this.sendCallback) {
|
|
46
|
+
throw new Error('No send callback registered')
|
|
47
|
+
}
|
|
48
|
+
return this.sendCallback?.(message, isBinary)
|
|
49
|
+
}
|
|
50
|
+
}
|