@pikku/core 0.6.9

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 (167) hide show
  1. package/CHANGELOG.md +339 -0
  2. package/README.md +5 -0
  3. package/dist/channel/channel-handler.d.ts +3 -0
  4. package/dist/channel/channel-handler.js +97 -0
  5. package/dist/channel/channel-runner.d.ts +26 -0
  6. package/dist/channel/channel-runner.js +86 -0
  7. package/dist/channel/channel-store.d.ts +14 -0
  8. package/dist/channel/channel-store.js +2 -0
  9. package/dist/channel/channel.types.d.ts +106 -0
  10. package/dist/channel/channel.types.js +1 -0
  11. package/dist/channel/eventhub-forwarder.d.ts +17 -0
  12. package/dist/channel/eventhub-forwarder.js +1 -0
  13. package/dist/channel/eventhub-service.d.ts +23 -0
  14. package/dist/channel/eventhub-service.js +1 -0
  15. package/dist/channel/eventhub-store.d.ts +5 -0
  16. package/dist/channel/eventhub-store.js +2 -0
  17. package/dist/channel/index.d.ts +9 -0
  18. package/dist/channel/index.js +9 -0
  19. package/dist/channel/local/index.d.ts +3 -0
  20. package/dist/channel/local/index.js +3 -0
  21. package/dist/channel/local/local-channel-handler.d.ts +17 -0
  22. package/dist/channel/local/local-channel-handler.js +41 -0
  23. package/dist/channel/local/local-channel-runner.d.ts +3 -0
  24. package/dist/channel/local/local-channel-runner.js +46 -0
  25. package/dist/channel/local/local-eventhub-service.d.ts +44 -0
  26. package/dist/channel/local/local-eventhub-service.js +81 -0
  27. package/dist/channel/log-channels.d.ts +6 -0
  28. package/dist/channel/log-channels.js +17 -0
  29. package/dist/channel/pikku-abstract-channel-handler.d.ts +15 -0
  30. package/dist/channel/pikku-abstract-channel-handler.js +33 -0
  31. package/dist/channel/serverless/index.d.ts +1 -0
  32. package/dist/channel/serverless/index.js +1 -0
  33. package/dist/channel/serverless/serverless-channel-runner.d.ts +10 -0
  34. package/dist/channel/serverless/serverless-channel-runner.js +98 -0
  35. package/dist/errors/error-handler.d.ts +39 -0
  36. package/dist/errors/error-handler.js +54 -0
  37. package/dist/errors/errors.d.ts +190 -0
  38. package/dist/errors/errors.js +315 -0
  39. package/dist/errors/index.d.ts +2 -0
  40. package/dist/errors/index.js +2 -0
  41. package/dist/http/http-route-runner.d.ts +30 -0
  42. package/dist/http/http-route-runner.js +208 -0
  43. package/dist/http/http-routes.types.d.ts +127 -0
  44. package/dist/http/http-routes.types.js +1 -0
  45. package/dist/http/http-session-service.d.ts +15 -0
  46. package/dist/http/http-session-service.js +1 -0
  47. package/dist/http/index.d.ts +6 -0
  48. package/dist/http/index.js +5 -0
  49. package/dist/http/log-http-routes.d.ts +6 -0
  50. package/dist/http/log-http-routes.js +17 -0
  51. package/dist/http/pikku-http-abstract-request.d.ts +57 -0
  52. package/dist/http/pikku-http-abstract-request.js +69 -0
  53. package/dist/http/pikku-http-abstract-response.d.ts +58 -0
  54. package/dist/http/pikku-http-abstract-response.js +54 -0
  55. package/dist/http/pikku-http-session-service.d.ts +45 -0
  56. package/dist/http/pikku-http-session-service.js +92 -0
  57. package/dist/index.d.ts +14 -0
  58. package/dist/index.js +14 -0
  59. package/dist/permissions.d.ts +10 -0
  60. package/dist/permissions.js +32 -0
  61. package/dist/pikku-request.d.ts +14 -0
  62. package/dist/pikku-request.js +21 -0
  63. package/dist/pikku-response.d.ts +6 -0
  64. package/dist/pikku-response.js +6 -0
  65. package/dist/scheduler/index.d.ts +3 -0
  66. package/dist/scheduler/index.js +3 -0
  67. package/dist/scheduler/log-schedulers.d.ts +6 -0
  68. package/dist/scheduler/log-schedulers.js +17 -0
  69. package/dist/scheduler/scheduler-runner.d.ts +21 -0
  70. package/dist/scheduler/scheduler-runner.js +73 -0
  71. package/dist/scheduler/scheduler.types.d.ts +21 -0
  72. package/dist/scheduler/scheduler.types.js +1 -0
  73. package/dist/schema.d.ts +26 -0
  74. package/dist/schema.js +100 -0
  75. package/dist/services/content-service.d.ts +55 -0
  76. package/dist/services/content-service.js +1 -0
  77. package/dist/services/index.d.ts +12 -0
  78. package/dist/services/index.js +14 -0
  79. package/dist/services/jwt-service.d.ts +29 -0
  80. package/dist/services/jwt-service.js +1 -0
  81. package/dist/services/local-content.d.ts +33 -0
  82. package/dist/services/local-content.js +6 -0
  83. package/dist/services/local-secrets.d.ts +26 -0
  84. package/dist/services/local-secrets.js +39 -0
  85. package/dist/services/local-variables.d.ts +7 -0
  86. package/dist/services/local-variables.js +12 -0
  87. package/dist/services/logger-console.d.ts +52 -0
  88. package/dist/services/logger-console.js +82 -0
  89. package/dist/services/logger.d.ts +48 -0
  90. package/dist/services/logger.js +9 -0
  91. package/dist/services/schema-service.d.ts +24 -0
  92. package/dist/services/schema-service.js +1 -0
  93. package/dist/services/secret-service.d.ts +17 -0
  94. package/dist/services/secret-service.js +1 -0
  95. package/dist/services/user-session-service.d.ts +6 -0
  96. package/dist/services/user-session-service.js +1 -0
  97. package/dist/services/variables-service.d.ts +4 -0
  98. package/dist/services/variables-service.js +1 -0
  99. package/dist/types/core.types.d.ts +109 -0
  100. package/dist/types/core.types.js +1 -0
  101. package/dist/types/functions.types.d.ts +27 -0
  102. package/dist/types/functions.types.js +1 -0
  103. package/dist/utils.d.ts +2 -0
  104. package/dist/utils.js +15 -0
  105. package/package.json +48 -0
  106. package/run-tests.sh +53 -0
  107. package/src/channel/channel-handler.ts +179 -0
  108. package/src/channel/channel-runner.ts +156 -0
  109. package/src/channel/channel-store.ts +32 -0
  110. package/src/channel/channel.types.ts +202 -0
  111. package/src/channel/eventhub-forwarder.ts +25 -0
  112. package/src/channel/eventhub-service.ts +30 -0
  113. package/src/channel/eventhub-store.ts +8 -0
  114. package/src/channel/index.ts +9 -0
  115. package/src/channel/local/index.ts +3 -0
  116. package/src/channel/local/local-channel-handler.ts +56 -0
  117. package/src/channel/local/local-channel-runner.test.ts +128 -0
  118. package/src/channel/local/local-channel-runner.ts +94 -0
  119. package/src/channel/local/local-eventhub-service.test.ts +95 -0
  120. package/src/channel/local/local-eventhub-service.ts +95 -0
  121. package/src/channel/log-channels.ts +20 -0
  122. package/src/channel/pikku-abstract-channel-handler.test.ts +77 -0
  123. package/src/channel/pikku-abstract-channel-handler.ts +44 -0
  124. package/src/channel/serverless/index.ts +1 -0
  125. package/src/channel/serverless/serverless-channel-runner.ts +190 -0
  126. package/src/errors/error-handler.ts +74 -0
  127. package/src/errors/error.test.ts +54 -0
  128. package/src/errors/errors.ts +340 -0
  129. package/src/errors/index.ts +2 -0
  130. package/src/http/http-route-runner.test.ts +193 -0
  131. package/src/http/http-route-runner.ts +365 -0
  132. package/src/http/http-routes.types.ts +189 -0
  133. package/src/http/http-session-service.ts +19 -0
  134. package/src/http/index.ts +16 -0
  135. package/src/http/log-http-routes.ts +20 -0
  136. package/src/http/pikku-http-abstract-request.ts +95 -0
  137. package/src/http/pikku-http-abstract-response.ts +79 -0
  138. package/src/http/pikku-http-session-service.test.ts +106 -0
  139. package/src/http/pikku-http-session-service.ts +135 -0
  140. package/src/index.ts +15 -0
  141. package/src/permissions.test.ts +58 -0
  142. package/src/permissions.ts +45 -0
  143. package/src/pikku-request.ts +19 -0
  144. package/src/pikku-response.ts +5 -0
  145. package/src/scheduler/index.ts +11 -0
  146. package/src/scheduler/log-schedulers.ts +20 -0
  147. package/src/scheduler/scheduler-runner.ts +132 -0
  148. package/src/scheduler/scheduler.types.ts +27 -0
  149. package/src/schema.test.ts +57 -0
  150. package/src/schema.ts +124 -0
  151. package/src/services/content-service.ts +69 -0
  152. package/src/services/index.ts +16 -0
  153. package/src/services/jwt-service.ts +36 -0
  154. package/src/services/local-content.ts +39 -0
  155. package/src/services/local-secrets.ts +43 -0
  156. package/src/services/local-variables.ts +17 -0
  157. package/src/services/logger-console.ts +97 -0
  158. package/src/services/logger.ts +57 -0
  159. package/src/services/schema-service.ts +26 -0
  160. package/src/services/secret-service.ts +17 -0
  161. package/src/services/user-session-service.ts +7 -0
  162. package/src/services/variables-service.ts +6 -0
  163. package/src/types/core.types.ts +162 -0
  164. package/src/types/functions.types.ts +44 -0
  165. package/src/utils.ts +21 -0
  166. package/tsconfig.json +13 -0
  167. package/tsconfig.tsbuildinfo +1 -0
package/run-tests.sh ADDED
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+
3
+ # Enable nullglob to handle cases where no files match the pattern
4
+ shopt -s nullglob
5
+
6
+ # Initialize variables for options
7
+ watch_mode=false
8
+ coverage_mode=false
9
+
10
+ # Parse command-line options
11
+ while [[ $# -gt 0 ]]; do
12
+ case $1 in
13
+ --watch)
14
+ watch_mode=true
15
+ shift
16
+ ;;
17
+ --coverage)
18
+ coverage_mode=true
19
+ shift
20
+ ;;
21
+ *)
22
+ echo "Unknown option: $1"
23
+ exit 1
24
+ ;;
25
+ esac
26
+ done
27
+
28
+ # Define the pattern to match your test files
29
+ pattern="src/*.test.ts"
30
+
31
+ # Expand the pattern into an array of files
32
+ files=($(find src -type f -name "*.test.ts"))
33
+
34
+ # Check if any files matched the pattern
35
+ if [ ${#files[@]} -eq 0 ]; then
36
+ echo "No test files found matching pattern: $pattern"
37
+ exit 0
38
+ fi
39
+
40
+ # Construct the node command
41
+ node_cmd="node --import tsx --test"
42
+
43
+ # Append options based on flags
44
+ if [ "$watch_mode" = true ]; then
45
+ node_cmd="$node_cmd --watch"
46
+ fi
47
+
48
+ if [ "$coverage_mode" = true ]; then
49
+ node_cmd="$node_cmd --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info"
50
+ fi
51
+
52
+ # Execute the node command with the expanded list of files
53
+ $node_cmd "${files[@]}"
@@ -0,0 +1,179 @@
1
+ import {
2
+ CoreSingletonServices,
3
+ CoreServices,
4
+ CoreUserSession,
5
+ JSONValue,
6
+ } from '../types/core.types.js'
7
+ import { CoreAPIChannel, PikkuChannelHandler } from './channel.types.js'
8
+ import { verifyPermissions } from '../permissions.js'
9
+ import { getChannels } from './channel-runner.js'
10
+
11
+ const validateSchema = (
12
+ logger: CoreSingletonServices['logger'],
13
+ data: JSONValue,
14
+ channelName: string,
15
+ routingProperty?: string,
16
+ routerValue?: string
17
+ ) => {
18
+ const { channelsMeta } = getChannels()
19
+ for (const channelMeta of channelsMeta) {
20
+ if (routingProperty && routerValue) {
21
+ const channelRoute =
22
+ channelMeta.messageRoutes[routingProperty]?.[routerValue]
23
+ if (channelRoute) {
24
+ const schemaNames = channelRoute.inputs
25
+ if (schemaNames) {
26
+ // TODO
27
+ // loadSchema(schemaNames, logger)
28
+ // validateJson(schemaNames, data)
29
+ }
30
+ return
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ const validateAuth = (
37
+ requiresSession: boolean,
38
+ channelHandler: PikkuChannelHandler,
39
+ onMessage: any
40
+ ) => {
41
+ const auth =
42
+ typeof onMessage === 'function'
43
+ ? requiresSession
44
+ : onMessage.auth === undefined
45
+ ? requiresSession
46
+ : onMessage.auth
47
+
48
+ if (auth && !channelHandler.getChannel().userSession) {
49
+ return false
50
+ }
51
+ return true
52
+ }
53
+
54
+ const validatePermissions = async (
55
+ services: CoreServices,
56
+ channelHandler: PikkuChannelHandler,
57
+ onMessage: any,
58
+ data: unknown
59
+ ) => {
60
+ const permissions =
61
+ typeof onMessage === 'function' ? {} : onMessage.permissions
62
+ return await verifyPermissions(
63
+ permissions,
64
+ services,
65
+ data,
66
+ channelHandler.getChannel().userSession
67
+ )
68
+ }
69
+
70
+ const runFunction = async (
71
+ services: CoreServices,
72
+ channelHandler: PikkuChannelHandler,
73
+ onMessage: any,
74
+ data: unknown
75
+ ) => {
76
+ const func: any = typeof onMessage === 'function' ? onMessage : onMessage.func
77
+ return await func(services, channelHandler.getChannel(), data)
78
+ }
79
+
80
+ export const processMessageHandlers = (
81
+ services: CoreServices,
82
+ channelConfig: CoreAPIChannel<any, any>,
83
+ channelHandler: PikkuChannelHandler<CoreUserSession, unknown>
84
+ ) => {
85
+ const logger = services.logger
86
+ const requiresSession = channelConfig.auth !== false
87
+
88
+ const processMessage = async (
89
+ data: JSONValue,
90
+ onMessage: any,
91
+ routingProperty?: string,
92
+ routerValue?: string
93
+ ): Promise<unknown> => {
94
+ if (!validateAuth(requiresSession, channelHandler, onMessage)) {
95
+ const routeMessage = routingProperty
96
+ ? `route '${routingProperty}:${routerValue}'`
97
+ : 'the default message route'
98
+ logger.error(
99
+ `Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}`
100
+ )
101
+ // TODO: Send error message back breaks typescript, but should be implemented somehow
102
+ channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
103
+ return
104
+ }
105
+
106
+ validateSchema(
107
+ services.logger,
108
+ data,
109
+ channelConfig.name,
110
+ routingProperty,
111
+ routerValue
112
+ )
113
+
114
+ const hasPermission = await validatePermissions(
115
+ services,
116
+ channelHandler,
117
+ onMessage,
118
+ data
119
+ )
120
+ if (!hasPermission) {
121
+ logger.error(
122
+ `Channel ${channelConfig.name} requires permissions for ${routingProperty || 'default message route'}`
123
+ )
124
+ }
125
+
126
+ return await runFunction(services, channelHandler, onMessage, data)
127
+ }
128
+
129
+ const onMessage = async (rawData): Promise<unknown> => {
130
+ let result: unknown
131
+ let processed = false
132
+
133
+ // Route-specific handling
134
+ if (typeof rawData === 'string' && channelConfig.onMessageRoute) {
135
+ try {
136
+ const messageData = JSON.parse(rawData)
137
+ const entries = Object.entries(channelConfig.onMessageRoute)
138
+ for (const [routingProperty, routes] of entries) {
139
+ const routerValue = messageData[routingProperty]
140
+ if (routerValue && routes[routerValue]) {
141
+ processed = true
142
+ result = await processMessage(
143
+ messageData,
144
+ routes[routerValue],
145
+ routingProperty,
146
+ routerValue
147
+ )
148
+ break
149
+ }
150
+ }
151
+
152
+ // Default handler if no routes matched but json data was parsed
153
+ if (!processed && channelConfig.onMessage) {
154
+ processed = true
155
+ result = await processMessage(messageData, channelConfig.onMessage)
156
+ }
157
+ } catch (error) {
158
+ // Most likely a json error.. ignore
159
+ }
160
+ }
161
+
162
+ // Default handler if no routes matched and json data wasn't parsed
163
+ if (!processed && channelConfig.onMessage) {
164
+ processed = true
165
+ result = await processMessage(rawData, channelConfig.onMessage)
166
+ }
167
+
168
+ if (!processed) {
169
+ logger.error(
170
+ `No handler found for message in channel ${channelConfig.name} for ${rawData}`
171
+ )
172
+ logger.error(`Channel ${channelConfig}`)
173
+ }
174
+
175
+ return result
176
+ }
177
+
178
+ return onMessage
179
+ }
@@ -0,0 +1,156 @@
1
+ import { NotFoundError } from '../errors/errors.js'
2
+ import { loadUserSession } from '../http/http-route-runner.js'
3
+ import { validateAndCoerce } from '../schema.js'
4
+ import { CoreUserSession } from '../types/core.types.js'
5
+ import {
6
+ CoreAPIChannel,
7
+ ChannelsMeta,
8
+ CoreAPIChannels,
9
+ RunChannelOptions,
10
+ RunChannelParams,
11
+ } from './channel.types.js'
12
+ import { match } from 'path-to-regexp'
13
+
14
+ if (!globalThis.pikku?.channels) {
15
+ globalThis.pikku = globalThis.pikku || {}
16
+ globalThis.pikku.channels = []
17
+ globalThis.pikku.channelsMeta = []
18
+ }
19
+
20
+ const channels = (data?: any): CoreAPIChannels => {
21
+ if (data) {
22
+ globalThis.pikku.channels = data
23
+ }
24
+ return globalThis.pikku.channels
25
+ }
26
+
27
+ const channelsMeta = (data?: any): ChannelsMeta => {
28
+ if (data) {
29
+ globalThis.pikku.channelsMeta = data
30
+ }
31
+ return globalThis.pikku.channelsMeta
32
+ }
33
+
34
+ /**
35
+ * Returns all the registered routes and associated metadata.
36
+ * @internal
37
+ */
38
+ export const getChannels = () => {
39
+ return {
40
+ channels: channels(),
41
+ channelsMeta: channelsMeta(),
42
+ }
43
+ }
44
+
45
+ export const addChannel = <
46
+ In,
47
+ Channel extends string,
48
+ ChannelFunction,
49
+ ChannelFunctionSessionless,
50
+ APIPermission,
51
+ >(
52
+ channel: CoreAPIChannel<
53
+ In,
54
+ Channel,
55
+ ChannelFunction,
56
+ ChannelFunctionSessionless,
57
+ APIPermission
58
+ >
59
+ ) => {
60
+ channels().push(channel as any)
61
+ }
62
+
63
+ /**
64
+ * @ignore
65
+ */
66
+ export const setChannelsMeta = (_channelsMeta: ChannelsMeta) => {
67
+ channelsMeta(_channelsMeta)
68
+ }
69
+
70
+ export const getMatchingChannelConfig = (request: string) => {
71
+ const { channels, channelsMeta } = getChannels()
72
+ for (const channelConfig of channels) {
73
+ const cleanedRoute = channelConfig.route.replace(/^\/\//, '/')
74
+ const cleanedRequest = request.replace(/^\/\//, '/')
75
+ const matchFunc = match(cleanedRoute, {
76
+ decode: decodeURIComponent,
77
+ })
78
+ const matchedPath = matchFunc(cleanedRequest)
79
+ if (matchedPath) {
80
+ const schemaName = channelsMeta.find(
81
+ (channelMeta) => channelMeta.route === channelConfig.route
82
+ )?.input
83
+ return {
84
+ matchedPath,
85
+ params: matchedPath.params,
86
+ channelConfig,
87
+ schemaName,
88
+ }
89
+ }
90
+ }
91
+
92
+ return null
93
+ }
94
+
95
+ export const openChannel = async ({
96
+ route,
97
+ singletonServices,
98
+ skipUserSession = false,
99
+ coerceToArray = false,
100
+ http,
101
+ }: Pick<CoreAPIChannel<unknown, string>, 'route'> &
102
+ Omit<RunChannelParams<unknown>, 'response' | 'request'> &
103
+ RunChannelOptions): Promise<{
104
+ userSession?: CoreUserSession
105
+ openingData: unknown
106
+ channelConfig: CoreAPIChannel<unknown, any>
107
+ }> => {
108
+ const matchingChannel = getMatchingChannelConfig(route)
109
+ if (!matchingChannel) {
110
+ singletonServices.logger.info(`Channel not found: ${route}`)
111
+ throw new NotFoundError(`Channel not found: ${route}`)
112
+ }
113
+
114
+ const { matchedPath, params, channelConfig, schemaName } = matchingChannel
115
+
116
+ const requiresSession = channelConfig.auth !== false
117
+ http?.request?.setParams(params)
118
+
119
+ singletonServices.logger.info(
120
+ `Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`
121
+ )
122
+
123
+ const userSession = await loadUserSession(
124
+ skipUserSession,
125
+ // We may require a session, but we don't actually need it
126
+ // on connect since channels can authenticate later given
127
+ // how websocket sessions work (cookie or queryParam based)
128
+ false,
129
+ http,
130
+ matchedPath,
131
+ channelConfig,
132
+ singletonServices.logger,
133
+ singletonServices.httpSessionService
134
+ )
135
+
136
+ if (singletonServices.enforceChannelAccess) {
137
+ await singletonServices.enforceChannelAccess(
138
+ matchingChannel.channelConfig,
139
+ userSession
140
+ )
141
+ }
142
+
143
+ let openingData: any | undefined
144
+ if (http?.request) {
145
+ openingData = await http.request.getData()
146
+ validateAndCoerce(
147
+ singletonServices.logger,
148
+ singletonServices.schemaService,
149
+ schemaName,
150
+ openingData,
151
+ coerceToArray
152
+ )
153
+ }
154
+
155
+ return { userSession, openingData, channelConfig }
156
+ }
@@ -0,0 +1,32 @@
1
+ import { CoreUserSession } from '../types/core.types.js'
2
+
3
+ export type Channel<
4
+ ChannelType = unknown,
5
+ OpeningData = unknown,
6
+ UserSession extends CoreUserSession = CoreUserSession,
7
+ > = {
8
+ channelId: string
9
+ channelName: string
10
+ channelObject?: ChannelType
11
+ openingData?: OpeningData
12
+ userSession?: UserSession
13
+ }
14
+
15
+ export abstract class ChannelStore<
16
+ ChannelType = unknown,
17
+ OpeningData = unknown,
18
+ UserSession extends CoreUserSession = CoreUserSession,
19
+ TypedChannel = Channel<ChannelType, OpeningData, UserSession>,
20
+ > {
21
+ public abstract addChannel(
22
+ channel: Channel<ChannelType, OpeningData, UserSession>
23
+ ): Promise<void> | void
24
+ public abstract removeChannels(channelId: string[]): Promise<void> | void
25
+ public abstract setUserSession(
26
+ channelId: string,
27
+ userSession: any
28
+ ): Promise<void> | void
29
+ public abstract getChannel(
30
+ channelId: string
31
+ ): Promise<TypedChannel> | TypedChannel
32
+ }
@@ -0,0 +1,202 @@
1
+ import { EError } from '../errors/error-handler.js'
2
+ import {
3
+ HTTPFunctionMetaInputTypes,
4
+ PikkuHTTP,
5
+ } from '../http/http-routes.types.js'
6
+ import { PikkuHTTPAbstractRequest } from '../http/pikku-http-abstract-request.js'
7
+ import { PikkuHTTPAbstractResponse } from '../http/pikku-http-abstract-response.js'
8
+ import {
9
+ APIDocs,
10
+ CoreServices,
11
+ CoreSingletonServices,
12
+ CoreUserSession,
13
+ CreateSessionServices,
14
+ MakeRequired,
15
+ } from '../types/core.types.js'
16
+ import { CoreAPIPermission } from '../types/functions.types.js'
17
+ import { PikkuRequest } from '../pikku-request.js'
18
+ import { PikkuResponse } from '../pikku-response.js'
19
+
20
+ export type RunChannelOptions = Partial<{
21
+ skipUserSession: boolean
22
+ respondWith404: boolean
23
+ coerceToArray: boolean
24
+ logWarningsForStatusCodes: number[]
25
+ bubbleErrors: boolean
26
+ }>
27
+
28
+ export type RunChannelParams<ChannelData> = {
29
+ channelId: string
30
+ singletonServices: MakeRequired<CoreSingletonServices, 'eventHub'>
31
+ request?: PikkuRequest<ChannelData> | PikkuHTTPAbstractRequest<ChannelData>
32
+ response?: PikkuResponse | PikkuHTTPAbstractResponse
33
+ http?: PikkuHTTP
34
+ createSessionServices?: CreateSessionServices
35
+ }
36
+
37
+ export interface HandlerMeta {}
38
+
39
+ export interface ChannelMeta {
40
+ name: string
41
+ route: string
42
+ params?: string[]
43
+ query?: string[]
44
+ input: string | null
45
+ inputTypes?: HTTPFunctionMetaInputTypes
46
+ connect: boolean
47
+ disconnect: boolean
48
+ message: { inputs: string[] | null; outputs: string[] | null } | null
49
+ messageRoutes: Record<
50
+ string,
51
+ Record<
52
+ string,
53
+ {
54
+ inputs: string[] | null
55
+ outputs: string[] | null
56
+ }
57
+ >
58
+ >
59
+ docs?: APIDocs
60
+ }
61
+
62
+ export type ChannelsMeta = ChannelMeta[]
63
+
64
+ export type CoreChannelConnection<
65
+ ChannelData,
66
+ Out = unknown,
67
+ Services extends CoreServices = CoreServices,
68
+ Session extends CoreUserSession = CoreUserSession,
69
+ > = (
70
+ services: MakeRequired<Services, 'eventHub'>,
71
+ channel: PikkuChannel<Session, ChannelData, Out>
72
+ ) => Promise<void>
73
+
74
+ export type CoreChannelDisconnection<
75
+ ChannelData,
76
+ Services extends CoreServices = CoreServices,
77
+ Session extends CoreUserSession = CoreUserSession,
78
+ > = (
79
+ services: MakeRequired<Services, 'eventHub'>,
80
+ channel: PikkuChannel<Session, ChannelData, never>
81
+ ) => Promise<void>
82
+
83
+ /**
84
+ * Represents a core channel function that performs an operation using core services and a user session.
85
+ *
86
+ * @template In - The input type.
87
+ * @template Services - The services type, defaults to `CoreServices`.
88
+ * @template Session - The session type, defaults to `CoreUserSession`.
89
+ */
90
+ export type CoreChannelMessage<
91
+ In,
92
+ Out,
93
+ ChannelData,
94
+ Services extends CoreServices = CoreServices,
95
+ Session extends CoreUserSession = CoreUserSession,
96
+ > = (
97
+ services: MakeRequired<Services, 'eventHub'>,
98
+ channel: PikkuChannel<Session, ChannelData, Out>,
99
+ data: In
100
+ ) => Promise<void | Out>
101
+
102
+ export type CoreAPIChannelMessage<
103
+ ChannelFunctionMessage = CoreChannelMessage<unknown, unknown, unknown>,
104
+ > = {
105
+ func: ChannelFunctionMessage
106
+ route: string
107
+ }
108
+
109
+ export type CoreAPIChannel<
110
+ ChannelData,
111
+ Channel extends string,
112
+ ChannelFunctionConnection = CoreChannelConnection<ChannelData>,
113
+ ChannelFunctionDisconnection = CoreChannelConnection<ChannelData>,
114
+ ChannelFunctionDefaultMessage = CoreChannelMessage<unknown, unknown, unknown>,
115
+ ChannelFunctionMessageRoute = CoreChannelMessage<unknown, unknown, unknown>,
116
+ APIPermission = CoreAPIPermission<ChannelData>,
117
+ > = {
118
+ name: string
119
+ route: Channel
120
+ onConnect?: ChannelFunctionConnection
121
+ onDisconnect?: ChannelFunctionDisconnection
122
+ onMessage?:
123
+ | {
124
+ func: ChannelFunctionDefaultMessage
125
+ permissions?: Record<string, APIPermission[] | APIPermission>
126
+ auth?: boolean
127
+ }
128
+ | ChannelFunctionDefaultMessage
129
+ onMessageRoute?: Record<
130
+ string,
131
+ Record<
132
+ string,
133
+ | ChannelFunctionMessageRoute
134
+ | {
135
+ func: ChannelFunctionMessageRoute
136
+ permissions?: Record<string, APIPermission[] | APIPermission>
137
+ auth?: boolean
138
+ }
139
+ >
140
+ >
141
+ permissions?: Record<string, APIPermission[] | APIPermission>
142
+ auth?: boolean
143
+ docs?: Partial<{
144
+ description: string
145
+ response: string
146
+ errors: Array<typeof EError>
147
+ tags: string[]
148
+ }>
149
+ }
150
+
151
+ export type CoreAPIChannels = CoreAPIChannel<any, string>[]
152
+
153
+ export interface PikkuChannel<UserSession, OpeningData, Out> {
154
+ // The channel identifier
155
+ channelId: string
156
+ // The user session, if available
157
+ userSession?: UserSession
158
+ // Update the user session, useful if you deal with auth on the
159
+ // stream side
160
+ setUserSession: (userSession: UserSession) => Promise<void> | void
161
+ // The data the channel was created with. This could be query parameters
162
+ // or parameters in the url.
163
+ openingData: OpeningData
164
+ // The data to send. This will fail is the stream has been closed.
165
+ send: (data: Out, isBinary?: boolean) => Promise<void> | void
166
+ // This will close the channel.
167
+ close: () => Promise<void> | void
168
+ // The current state of the channel
169
+ state: 'initial' | 'open' | 'closed'
170
+ }
171
+
172
+ export interface PikkuChannelHandler<
173
+ UserSession extends CoreUserSession = CoreUserSession,
174
+ OpeningData = unknown,
175
+ Out = unknown,
176
+ > {
177
+ setUserSession(session: UserSession): Promise<void> | void
178
+ send(message: Out, isBinary?: boolean): Promise<void> | void
179
+ getChannel(): PikkuChannel<UserSession, OpeningData, Out>
180
+ }
181
+
182
+ export type PikkuChannelHandlerFactory<
183
+ OpeningData = unknown,
184
+ UserSession extends CoreUserSession = CoreUserSession,
185
+ Out = unknown,
186
+ > = (
187
+ channelId: string,
188
+ channelName: string,
189
+ openingData: OpeningData,
190
+ userSession: UserSession | undefined
191
+ ) => PikkuChannelHandler<UserSession, OpeningData, Out>
192
+
193
+ /**
194
+ * Enfore access to a channel.
195
+ * @param route - The channel to verify access for.
196
+ * @param session - The user session.
197
+ * @returns A promise that resolves if access is granted.
198
+ */
199
+ export type enforceChannelAccess = (
200
+ channel: CoreAPIChannel<unknown, any>,
201
+ session?: CoreUserSession
202
+ ) => Promise<void> | void
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Interface defining the methods of a EventHub Service.
3
+ */
4
+ export interface EventHubForwarder {
5
+ /**
6
+ * Sends data to all connections subscribed to a topic.
7
+ * @param topic - The topic to send data to.
8
+ * @param data - The data to send to the subscribers.
9
+ */
10
+ forwardPublish(
11
+ topic: string,
12
+ channelId: string,
13
+ data: unknown,
14
+ isBinary?: boolean
15
+ ): Promise<void>
16
+
17
+ /**
18
+ * Sends data to all connections subscribed to a topic.
19
+ * @param topic - The topic to send data to.
20
+ * @param data - The data to send to the subscribers.
21
+ */
22
+ onForwardedPublishMessage(
23
+ callback: (topic: string, data: unknown, isBinary?: boolean) => void
24
+ ): void
25
+ }
@@ -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,9 @@
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-forwarder.js'
7
+ export * from './eventhub-service.js'
8
+ export * from './channel-store.js'
9
+ export * from './eventhub-store.js'
@@ -0,0 +1,3 @@
1
+ export * from './local-channel-handler.js'
2
+ export * from './local-channel-runner.js'
3
+ export * from './local-eventhub-service.js'