@pikku/core 0.6.26 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/dist/channel/channel-handler.js +16 -38
  3. package/dist/channel/channel-runner.d.ts +7 -8
  4. package/dist/channel/channel-runner.js +58 -7
  5. package/dist/channel/channel.types.d.ts +14 -27
  6. package/dist/channel/local/local-channel-runner.js +9 -4
  7. package/dist/channel/log-channels.js +4 -4
  8. package/dist/channel/serverless/serverless-channel-runner.js +17 -6
  9. package/dist/function/function-runner.d.ts +12 -0
  10. package/dist/function/function-runner.js +38 -0
  11. package/dist/function/functions.types.d.ts +37 -0
  12. package/dist/function/functions.types.js +1 -0
  13. package/dist/function/index.d.ts +2 -0
  14. package/dist/function/index.js +2 -0
  15. package/dist/handle-error.d.ts +1 -1
  16. package/dist/http/http-route-runner.d.ts +3 -3
  17. package/dist/http/http-route-runner.js +60 -35
  18. package/dist/http/http-routes.types.d.ts +38 -7
  19. package/dist/http/http-runner.d.ts +90 -0
  20. package/dist/http/http-runner.js +336 -0
  21. package/dist/http/http.types.d.ts +177 -0
  22. package/dist/http/http.types.js +1 -0
  23. package/dist/http/index.d.ts +2 -2
  24. package/dist/http/index.js +1 -1
  25. package/dist/http/log-http-routes.js +7 -5
  26. package/dist/http/pikku-fetch-http-request.d.ts +1 -1
  27. package/dist/http/pikku-fetch-http-response.d.ts +4 -2
  28. package/dist/http/pikku-fetch-http-response.js +64 -12
  29. package/dist/http/pikku-http-request.d.ts +56 -0
  30. package/dist/http/pikku-http-request.js +95 -0
  31. package/dist/http/pikku-http-response.d.ts +13 -0
  32. package/dist/http/pikku-http-response.js +60 -0
  33. package/dist/index.d.ts +3 -2
  34. package/dist/index.js +3 -2
  35. package/dist/middleware/auth-apikey.js +5 -4
  36. package/dist/middleware/auth-bearer.js +5 -4
  37. package/dist/middleware/auth-cookie.js +10 -9
  38. package/dist/parse-relative-time-offset.d.ts +12 -0
  39. package/dist/parse-relative-time-offset.js +20 -0
  40. package/dist/permissions.d.ts +2 -2
  41. package/dist/pikku-func.d.ts +12 -0
  42. package/dist/pikku-func.js +39 -0
  43. package/dist/pikku-function.d.ts +1 -0
  44. package/dist/pikku-function.js +1 -0
  45. package/dist/pikku-state.d.ts +10 -5
  46. package/dist/pikku-state.js +7 -3
  47. package/dist/scheduler/scheduler-runner.d.ts +1 -1
  48. package/dist/scheduler/scheduler-runner.js +24 -6
  49. package/dist/scheduler/scheduler.types.d.ts +3 -2
  50. package/dist/services/content-service.d.ts +34 -36
  51. package/dist/services/index.d.ts +1 -0
  52. package/dist/services/index.js +1 -0
  53. package/dist/services/local-content.d.ts +4 -2
  54. package/dist/services/local-content.js +19 -9
  55. package/dist/types/core.types.d.ts +15 -1
  56. package/dist/types/functions.types.d.ts +11 -6
  57. package/dist/utils.d.ts +2 -0
  58. package/dist/utils.js +15 -0
  59. package/package.json +2 -1
  60. package/lcov.info +0 -4310
  61. package/src/channel/channel-handler.ts +0 -227
  62. package/src/channel/channel-runner.ts +0 -98
  63. package/src/channel/channel-store.ts +0 -29
  64. package/src/channel/channel.types.ts +0 -171
  65. package/src/channel/eventhub-service.ts +0 -30
  66. package/src/channel/eventhub-store.ts +0 -8
  67. package/src/channel/index.ts +0 -8
  68. package/src/channel/local/index.ts +0 -3
  69. package/src/channel/local/local-channel-handler.ts +0 -50
  70. package/src/channel/local/local-channel-runner.test.ts +0 -158
  71. package/src/channel/local/local-channel-runner.ts +0 -126
  72. package/src/channel/local/local-eventhub-service.test.ts +0 -95
  73. package/src/channel/local/local-eventhub-service.ts +0 -95
  74. package/src/channel/log-channels.ts +0 -20
  75. package/src/channel/pikku-abstract-channel-handler.test.ts +0 -52
  76. package/src/channel/pikku-abstract-channel-handler.ts +0 -38
  77. package/src/channel/serverless/index.ts +0 -1
  78. package/src/channel/serverless/serverless-channel-runner.ts +0 -209
  79. package/src/errors/error-handler.ts +0 -61
  80. package/src/errors/error.test.ts +0 -54
  81. package/src/errors/errors.ts +0 -339
  82. package/src/errors/index.ts +0 -2
  83. package/src/handle-error.ts +0 -73
  84. package/src/http/http-route-runner.test.ts +0 -117
  85. package/src/http/http-route-runner.ts +0 -460
  86. package/src/http/http-routes.types.ts +0 -211
  87. package/src/http/incomingmessage-to-request-convertor.ts +0 -0
  88. package/src/http/index.ts +0 -8
  89. package/src/http/log-http-routes.ts +0 -20
  90. package/src/http/pikku-fetch-http-request.test.ts +0 -237
  91. package/src/http/pikku-fetch-http-request.ts +0 -167
  92. package/src/http/pikku-fetch-http-response.test.ts +0 -82
  93. package/src/http/pikku-fetch-http-response.ts +0 -84
  94. package/src/index.ts +0 -19
  95. package/src/middleware/auth-apikey.ts +0 -65
  96. package/src/middleware/auth-bearer.ts +0 -65
  97. package/src/middleware/auth-cookie.ts +0 -98
  98. package/src/middleware/index.ts +0 -3
  99. package/src/middleware/timeout.ts +0 -13
  100. package/src/middleware-runner.ts +0 -43
  101. package/src/permissions.test.ts +0 -58
  102. package/src/permissions.ts +0 -45
  103. package/src/pikku-request.ts +0 -23
  104. package/src/pikku-response.ts +0 -5
  105. package/src/pikku-state.ts +0 -71
  106. package/src/scheduler/index.ts +0 -5
  107. package/src/scheduler/log-schedulers.ts +0 -20
  108. package/src/scheduler/scheduler-runner.ts +0 -87
  109. package/src/scheduler/scheduler.types.ts +0 -29
  110. package/src/schema.test.ts +0 -57
  111. package/src/schema.ts +0 -99
  112. package/src/services/content-service.ts +0 -69
  113. package/src/services/index.ts +0 -17
  114. package/src/services/jwt-service.ts +0 -30
  115. package/src/services/local-content.ts +0 -91
  116. package/src/services/local-secrets.ts +0 -43
  117. package/src/services/local-variables.ts +0 -17
  118. package/src/services/logger-console.ts +0 -97
  119. package/src/services/logger.ts +0 -57
  120. package/src/services/schema-service.ts +0 -26
  121. package/src/services/secret-service.ts +0 -17
  122. package/src/services/user-session-service.ts +0 -53
  123. package/src/services/variables-service.ts +0 -6
  124. package/src/time-utils.test.ts +0 -56
  125. package/src/time-utils.ts +0 -32
  126. package/src/types/core.types.ts +0 -159
  127. package/src/types/functions.types.ts +0 -63
  128. package/src/utils.ts +0 -21
  129. package/tsconfig.json +0 -13
  130. package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
- ## 0.6
1
+ ## 0.7.0
2
+
3
+ We now use the function first approach internally, which means first all the functions register, and then events call call them.
4
+
5
+ The main breaking changes for the end user are:
6
+
7
+ - We now declare functions using `pikkuFunc<In, Out>(async () => {})
8
+ - We renamed addRoute to addHTTPRoutes
9
+
10
+ We also removed all the different types of functions. Everything is now either an APIFunction of APIFunctionSessionless. The channel (eventHub or any other transport specific service) is now injected in the service itself.
11
+
12
+ ## 0.6.27
13
+
14
+ ### Patch Changes
15
+
16
+ - 8658745: refactor: changing content service to use streams for performance benefits
17
+ - d0968d2: fix: fixing content uploads for s3
2
18
 
3
19
  ## 0.6.26
4
20
 
@@ -1,8 +1,7 @@
1
- import { verifyPermissions } from '../permissions.js';
2
1
  import { pikkuState } from '../pikku-state.js';
2
+ import { runPikkuFunc } from '../function/function-runner.js';
3
3
  const getRouteMeta = (channelName, routingProperty, routerValue) => {
4
- const channelsMeta = pikkuState('channel', 'meta');
5
- const channelMeta = channelsMeta.find((channelMeta) => channelMeta.name === channelName);
4
+ const channelMeta = pikkuState('channel', 'meta')[channelName];
6
5
  if (!channelMeta) {
7
6
  throw new Error(`Channel ${channelName} not found`);
8
7
  }
@@ -21,14 +20,6 @@ const getRouteMeta = (channelName, routingProperty, routerValue) => {
21
20
  }
22
21
  return route;
23
22
  };
24
- const validateSchema = (logger, data, channelRoute) => {
25
- const schemaNames = channelRoute.inputs;
26
- if (schemaNames) {
27
- // TODO
28
- // loadSchema(schemaNames, logger)
29
- // validateJson(schemaNames, data)
30
- }
31
- };
32
23
  const validateAuth = (requiresSession, session, onMessage) => {
33
24
  const auth = typeof onMessage === 'function'
34
25
  ? requiresSession
@@ -40,22 +31,6 @@ const validateAuth = (requiresSession, session, onMessage) => {
40
31
  }
41
32
  return true;
42
33
  };
43
- const validatePermissions = async (services, session, onMessage, data) => {
44
- const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
45
- return await verifyPermissions(permissions, services, data, session);
46
- };
47
- const runFunction = async (services, channelHandler, channelMessageMeta, session, onMessage, data) => {
48
- const func = typeof onMessage === 'function' ? onMessage : onMessage.func;
49
- if (channelMessageMeta.type?.toLowerCase().includes('function')) {
50
- return await func({
51
- ...services,
52
- channel: channelHandler.getChannel(),
53
- }, data, session);
54
- }
55
- else {
56
- return await func(services, channelHandler.getChannel(), data);
57
- }
58
- };
59
34
  export const processMessageHandlers = (services, session, channelConfig, channelHandler) => {
60
35
  const logger = services.logger;
61
36
  const requiresSession = channelConfig.auth !== false;
@@ -69,15 +44,18 @@ export const processMessageHandlers = (services, session, channelConfig, channel
69
44
  channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`);
70
45
  return;
71
46
  }
72
- const routeMeta = getRouteMeta(channelConfig.name, routingProperty, routerValue);
73
- if (routeMeta) {
74
- await validateSchema(services.logger, data, routeMeta);
75
- }
76
- const hasPermission = await validatePermissions(services, channelHandler, onMessage, data);
77
- if (!hasPermission) {
78
- logger.error(`Channel ${channelConfig.name} requires permissions for ${routingProperty || 'default message route'}`);
79
- }
80
- return await runFunction(services, channelHandler, routeMeta, session, onMessage, data);
47
+ const { pikkuFuncName } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
48
+ const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
49
+ return await runPikkuFunc(pikkuFuncName, {
50
+ singletonServices: services,
51
+ getAllServices: () => ({
52
+ ...services,
53
+ channel: channelHandler.getChannel(),
54
+ }),
55
+ data,
56
+ session,
57
+ permissions,
58
+ });
81
59
  };
82
60
  const onMessage = async (rawData) => {
83
61
  let result;
@@ -111,8 +89,8 @@ export const processMessageHandlers = (services, session, channelConfig, channel
111
89
  result = await processMessage(rawData, channelConfig.onMessage, session);
112
90
  }
113
91
  if (!processed) {
114
- logger.error(`No handler found for message in channel ${channelConfig.name} for ${rawData}`);
115
- logger.error(`Channel ${channelConfig}`);
92
+ logger.error(`No handler found for message in channel ${channelConfig.name} for ${JSON.stringify(rawData)}`);
93
+ logger.error(`Channel ${JSON.stringify(channelConfig)}`);
116
94
  }
117
95
  return result;
118
96
  };
@@ -1,15 +1,14 @@
1
1
  import { UserSessionService } from '../services/user-session-service.js';
2
- import { CoreAPIChannel, RunChannelOptions, RunChannelParams } from './channel.types.js';
3
- export declare const addChannel: <In, Channel extends string, ChannelFunction, ChannelFunctionSessionless, APIPermission>(channel: CoreAPIChannel<In, Channel, ChannelFunction, ChannelFunctionSessionless, APIPermission>) => void;
4
- export declare const getMatchingChannelConfig: (request: string) => {
5
- matchedPath: import("path-to-regexp").MatchResult<Partial<Record<string, string | string[]>>>;
6
- params: Partial<Record<string, string | string[]>>;
7
- channelConfig: CoreAPIChannel<any, string, import("./channel.types.js").CoreChannelConnection<any, unknown, import("../index.js").CoreServices>, import("./channel.types.js").CoreChannelDisconnection<any, import("../index.js").CoreServices>, import("./channel.types.js").CoreChannelMessage<unknown, unknown, unknown, import("../index.js").CoreServices> | import("../index.js").CoreAPIFunction<unknown, unknown>, import("./channel.types.js").CoreChannelMessage<unknown, unknown, unknown, import("../index.js").CoreServices> | import("../index.js").CoreAPIFunction<unknown, unknown>, import("../index.js").CoreAPIPermission<any>>;
8
- schemaName: string | null | undefined;
9
- } | null;
2
+ import { ChannelMeta, CoreAPIChannel, RunChannelOptions, RunChannelParams } from './channel.types.js';
3
+ /**
4
+ * Adds a channel and registers all functions referenced in it using the
5
+ * function names already stored in the channel metadata
6
+ */
7
+ export declare const addChannel: <In, Channel extends string, ChannelFunction, ChannelFunctionSessionless, APIPermission>(channel: CoreAPIChannel<In, Channel, ChannelFunction, APIPermission>) => void;
10
8
  export declare const openChannel: ({ route, singletonServices, coerceDataFromSchema, request, }: Pick<CoreAPIChannel<unknown, string>, "route"> & RunChannelParams<unknown> & {
11
9
  userSession: UserSessionService<any>;
12
10
  } & RunChannelOptions) => Promise<{
13
11
  openingData: unknown;
14
12
  channelConfig: CoreAPIChannel<unknown, any>;
13
+ meta: ChannelMeta;
15
14
  }>;
@@ -1,14 +1,61 @@
1
1
  import { NotFoundError } from '../errors/errors.js';
2
+ import { addFunction } from '../function/function-runner.js';
2
3
  import { pikkuState } from '../pikku-state.js';
3
4
  import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
4
5
  import { match } from 'path-to-regexp';
6
+ /**
7
+ * Adds a channel and registers all functions referenced in it using the
8
+ * function names already stored in the channel metadata
9
+ */
5
10
  export const addChannel = (channel) => {
6
- pikkuState('channel', 'channels').push(channel);
11
+ // Get the channel metadata
12
+ const channelsMeta = pikkuState('channel', 'meta');
13
+ const channelMeta = channelsMeta[channel.name];
14
+ if (!channelMeta) {
15
+ throw new Error(`Channel metadata not found for channel: ${channel.name}`);
16
+ }
17
+ pikkuState('channel', 'channels').set(channel.name, channel);
18
+ // Register onConnect function if provided
19
+ if (channel.onConnect && channelMeta.connectPikkuFuncName) {
20
+ addFunction(channelMeta.connectPikkuFuncName, channel.onConnect);
21
+ }
22
+ // Register onDisconnect function if provided
23
+ if (channel.onDisconnect && channelMeta.disconnectPikkuFuncName) {
24
+ addFunction(channelMeta.disconnectPikkuFuncName, channel.onDisconnect);
25
+ }
26
+ // Register onMessage function if provided
27
+ if (channel.onMessage && channelMeta.message?.pikkuFuncName) {
28
+ const messageFunc = typeof channel.onMessage === 'function'
29
+ ? channel.onMessage
30
+ : channel.onMessage.func;
31
+ addFunction(channelMeta.message.pikkuFuncName, messageFunc);
32
+ }
33
+ // Register functions in onMessageRoute
34
+ if (channel.onMessageRoute && channelMeta.messageRoutes) {
35
+ // Iterate through each channel in onMessageRoute
36
+ Object.entries(channel.onMessageRoute).forEach(([channelKey, routes]) => {
37
+ const channelRoutes = channelMeta.messageRoutes[channelKey];
38
+ if (!channelRoutes)
39
+ return;
40
+ // Iterate through each route in the channel
41
+ Object.entries(routes).forEach(([routeKey, handler]) => {
42
+ const routeMeta = channelRoutes[routeKey];
43
+ if (!routeMeta)
44
+ return;
45
+ // Extract the function from the handler
46
+ const routeFunc = typeof handler === 'function' ? handler : handler.func;
47
+ // Register the function using the pikku name from metadata
48
+ addFunction(routeMeta.pikkuFuncName, routeFunc);
49
+ });
50
+ });
51
+ }
52
+ // Store the channel configuration
53
+ pikkuState('channel', 'channels').set(channel.name, channel);
7
54
  };
8
- export const getMatchingChannelConfig = (request) => {
55
+ const getMatchingChannelConfig = (request) => {
9
56
  const channels = pikkuState('channel', 'channels');
10
57
  const channelsMeta = pikkuState('channel', 'meta');
11
- for (const channelConfig of channels) {
58
+ for (const channelConfig of channels.values()) {
12
59
  const cleanedRoute = channelConfig.route.replace(/^\/\//, '/');
13
60
  const cleanedRequest = request.replace(/^\/\//, '/');
14
61
  const matchFunc = match(cleanedRoute, {
@@ -16,12 +63,16 @@ export const getMatchingChannelConfig = (request) => {
16
63
  });
17
64
  const matchedPath = matchFunc(cleanedRequest);
18
65
  if (matchedPath) {
19
- const schemaName = channelsMeta.find((channelMeta) => channelMeta.route === channelConfig.route)?.input;
66
+ const channelMeta = channelsMeta[channelConfig.name];
67
+ if (!channelMeta) {
68
+ throw new Error(`Channel ${channelConfig.name} not found in metadata`);
69
+ }
20
70
  return {
21
71
  matchedPath,
22
72
  params: matchedPath.params,
23
73
  channelConfig,
24
- schemaName,
74
+ schemaName: channelMeta.input,
75
+ meta: channelMeta
25
76
  };
26
77
  }
27
78
  }
@@ -33,7 +84,7 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
33
84
  singletonServices.logger.info(`Channel not found: ${route}`);
34
85
  throw new NotFoundError(`Channel not found: ${route}`);
35
86
  }
36
- const { params, channelConfig, schemaName } = matchingChannel;
87
+ const { params, channelConfig, schemaName, meta } = matchingChannel;
37
88
  const requiresSession = channelConfig.auth !== false;
38
89
  request?.setParams(params);
39
90
  singletonServices.logger.info(`Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`);
@@ -45,5 +96,5 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
45
96
  }
46
97
  await validateSchema(singletonServices.logger, singletonServices.schema, schemaName, openingData);
47
98
  }
48
- return { openingData, channelConfig };
99
+ return { openingData, channelConfig, meta };
49
100
  };
@@ -1,7 +1,7 @@
1
1
  import { PikkuError } from '../errors/error-handler.js';
2
- import { HTTPFunctionMetaInputTypes, PikkuHTTPRequest, PikkuHTTPResponse } from '../http/http-routes.types.js';
3
- import { APIDocs, CoreServices, CoreSingletonServices, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
4
- import { CoreAPIFunction, CoreAPIPermission } from '../types/functions.types.js';
2
+ import { HTTPFunctionMetaInputTypes, PikkuHTTPRequest, PikkuHTTPResponse } from '../http/http.types.js';
3
+ import { APIDocs, CoreSingletonServices, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
4
+ import { CoreAPIFunction, CoreAPIFunctionSessionless, CoreAPIPermission } from '../function/functions.types.js';
5
5
  export type RunChannelOptions = Partial<{
6
6
  skipUserSession: boolean;
7
7
  respondWith404: boolean;
@@ -17,10 +17,10 @@ export type RunChannelParams<ChannelData> = {
17
17
  createSessionServices?: CreateSessionServices;
18
18
  };
19
19
  export interface ChannelMessageMeta {
20
+ pikkuFuncName: string;
20
21
  inputs: string[] | null;
21
22
  outputs: string[] | null;
22
23
  docs?: APIDocs;
23
- type: null | string;
24
24
  }
25
25
  export interface ChannelMeta {
26
26
  name: string;
@@ -30,39 +30,27 @@ export interface ChannelMeta {
30
30
  input: string | null;
31
31
  inputTypes?: HTTPFunctionMetaInputTypes;
32
32
  connect: boolean;
33
+ connectPikkuFuncName: string | null;
33
34
  disconnect: boolean;
35
+ disconnectPikkuFuncName: string | null;
34
36
  message: ChannelMessageMeta | null;
35
37
  messageRoutes: Record<string, Record<string, ChannelMessageMeta>>;
36
38
  docs?: APIDocs;
37
39
  tags?: string[];
38
40
  }
39
- export type ChannelsMeta = ChannelMeta[];
40
- export type CoreChannelConnection<ChannelData, Out = unknown, Services extends CoreServices = CoreServices> = (services: Services, channel: PikkuChannel<ChannelData, Out>) => Promise<void>;
41
- export type CoreChannelDisconnection<ChannelData, Services extends CoreServices = CoreServices> = (services: Services, channel: PikkuChannel<ChannelData, never>) => Promise<void>;
42
- /**
43
- * Represents a core channel function that performs an operation using core services and a user session.
44
- *
45
- * @template In - The input type.
46
- * @template Services - The services type, defaults to `CoreServices`.
47
- * @template Session - The session type, defaults to `CoreUserSession`.
48
- */
49
- export type CoreChannelMessage<In, Out, ChannelData, Services extends CoreServices = CoreServices> = (services: Services, channel: PikkuChannel<ChannelData, Out>, data: In) => Promise<void | Out>;
50
- export type CoreAPIChannelMessage<ChannelFunctionMessage = CoreChannelMessage<unknown, unknown, unknown>> = {
51
- func: ChannelFunctionMessage;
52
- route: string;
53
- };
54
- export type CoreAPIChannel<ChannelData, Channel extends string, ChannelFunctionConnection = CoreChannelConnection<ChannelData>, ChannelFunctionDisconnection = CoreChannelDisconnection<ChannelData>, ChannelFunctionDefaultMessage = CoreChannelMessage<unknown, unknown, unknown> | CoreAPIFunction<unknown, unknown>, ChannelFunctionMessageRoute = CoreChannelMessage<unknown, unknown, unknown> | CoreAPIFunction<unknown, unknown>, APIPermission = CoreAPIPermission<ChannelData>> = {
41
+ export type ChannelsMeta = Record<string, ChannelMeta>;
42
+ export type CoreAPIChannel<ChannelData, Channel extends string, ChannelConnect = CoreAPIFunction<void, unknown, ChannelData> | CoreAPIFunctionSessionless<void, unknown, ChannelData>, ChannelDisconnect = CoreAPIFunction<void, void, ChannelData> | CoreAPIFunctionSessionless<void, void, ChannelData>, ChannelFunctionMessage = CoreAPIFunction<unknown, unknown, ChannelData> | CoreAPIFunctionSessionless<unknown, unknown, ChannelData>, APIPermission = CoreAPIPermission<ChannelData>> = {
55
43
  name: string;
56
44
  route: Channel;
57
- onConnect?: ChannelFunctionConnection;
58
- onDisconnect?: ChannelFunctionDisconnection;
45
+ onConnect?: ChannelConnect;
46
+ onDisconnect?: ChannelDisconnect;
59
47
  onMessage?: {
60
- func: ChannelFunctionDefaultMessage;
48
+ func: ChannelFunctionMessage;
61
49
  permissions?: Record<string, APIPermission[] | APIPermission>;
62
50
  auth?: boolean;
63
- } | ChannelFunctionDefaultMessage;
64
- onMessageRoute?: Record<string, Record<string, ChannelFunctionMessageRoute | {
65
- func: ChannelFunctionMessageRoute;
51
+ } | ChannelFunctionMessage;
52
+ onMessageRoute?: Record<string, Record<string, ChannelFunctionMessage | {
53
+ func: ChannelFunctionMessage;
66
54
  permissions?: Record<string, APIPermission[] | APIPermission>;
67
55
  auth?: boolean;
68
56
  }>>;
@@ -77,7 +65,6 @@ export type CoreAPIChannel<ChannelData, Channel extends string, ChannelFunctionC
77
65
  }>;
78
66
  tags?: string[];
79
67
  };
80
- export type CoreAPIChannels = CoreAPIChannel<any, string>[];
81
68
  export interface PikkuChannel<OpeningData, Out> {
82
69
  channelId: string;
83
70
  openingData: OpeningData;
@@ -1,11 +1,12 @@
1
1
  import { openChannel } from '../channel-runner.js';
2
- import { createHTTPInteraction } from '../../http/http-route-runner.js';
2
+ 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
6
  import { handleError } from '../../handle-error.js';
7
7
  import { runMiddleware } from '../../middleware-runner.js';
8
8
  import { PikkuUserSessionService } from '../../services/user-session-service.js';
9
+ import { runPikkuFuncDirectly } from '../../function/function-runner.js';
9
10
  export const runLocalChannel = async ({ singletonServices, channelId, request, response, route, createSessionServices, skipUserSession = false, respondWith404 = true, coerceDataFromSchema = true, logWarningsForStatusCodes = [], bubbleErrors = false, }) => {
10
11
  let sessionServices;
11
12
  let channelHandler;
@@ -17,7 +18,7 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
17
18
  }
18
19
  const main = async () => {
19
20
  try {
20
- const { openingData, channelConfig } = await openChannel({
21
+ const { openingData, channelConfig, meta } = await openChannel({
21
22
  channelId,
22
23
  createSessionServices,
23
24
  respondWith404,
@@ -41,10 +42,14 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
41
42
  userSession: userSession,
42
43
  };
43
44
  channelHandler.registerOnOpen(() => {
44
- channelConfig.onConnect?.(allServices, channel);
45
+ if (channelConfig.onConnect && meta.connectPikkuFuncName) {
46
+ runPikkuFuncDirectly(meta.connectPikkuFuncName, { ...allServices, channel }, openingData);
47
+ }
45
48
  });
46
49
  channelHandler.registerOnClose(async () => {
47
- channelConfig.onDisconnect?.(allServices, channel);
50
+ if (channelConfig.onDisconnect && meta.disconnectPikkuFuncName) {
51
+ runPikkuFuncDirectly(meta.disconnectPikkuFuncName, { ...allServices, channel }, openingData);
52
+ }
48
53
  if (sessionServices) {
49
54
  await closeSessionServices(singletonServices.logger, sessionServices);
50
55
  }
@@ -5,13 +5,13 @@ import { pikkuState } from '../pikku-state.js';
5
5
  */
6
6
  export const logChannels = (logger) => {
7
7
  const channels = pikkuState('channel', 'channels');
8
- if (channels.length === 0) {
8
+ if (channels.size === 0) {
9
9
  logger.info('No channels added');
10
10
  return;
11
11
  }
12
12
  let scheduledChannels = 'Channels:';
13
- for (const { name, route } of channels) {
14
- scheduledChannels += `\n\t- ${name} at ${route}`;
15
- }
13
+ channels.forEach((channel) => {
14
+ scheduledChannels += `\n\t- ${channel.name} at ${channel.route}`;
15
+ });
16
16
  logger.info(scheduledChannels);
17
17
  };
@@ -1,23 +1,30 @@
1
1
  import { closeSessionServices } from '../../utils.js';
2
2
  import { processMessageHandlers } from '../channel-handler.js';
3
3
  import { openChannel } from '../channel-runner.js';
4
- import { createHTTPInteraction } from '../../http/http-route-runner.js';
4
+ import { createHTTPInteraction } from '../../http/http-runner.js';
5
5
  import { handleError } from '../../handle-error.js';
6
6
  import { PikkuUserSessionService } from '../../services/user-session-service.js';
7
7
  import { runMiddleware } from '../../middleware-runner.js';
8
8
  import { pikkuState } from '../../pikku-state.js';
9
9
  import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js';
10
+ import { runPikkuFuncDirectly } from '../../function/function-runner.js';
10
11
  const getVariablesForChannel = ({ channelId, channelName, channelHandlerFactory, openingData, }) => {
11
12
  const channels = pikkuState('channel', 'channels');
12
- const channelConfig = channels.find((channelConfig) => channelConfig.name === channelName);
13
+ const channelConfig = channels[channelName];
14
+ const channelsMeta = pikkuState('channel', 'meta');
15
+ const meta = channelsMeta[channelName];
13
16
  if (!channelConfig) {
14
17
  throw new Error(`Channel not found: ${channelName}`);
15
18
  }
19
+ if (!meta) {
20
+ throw new Error(`Channel meta not found: ${channelName}`);
21
+ }
16
22
  const channelHandler = channelHandlerFactory(channelId, channelConfig.name, openingData);
17
23
  return {
18
24
  channelConfig,
19
25
  channelHandler,
20
26
  channel: channelHandler.getChannel(),
27
+ meta
21
28
  };
22
29
  };
23
30
  export const runChannelConnect = async ({ singletonServices, channelId, channelObject, request, response, route, createSessionServices, channelStore, channelHandlerFactory, coerceDataFromSchema = true, logWarningsForStatusCodes = [], respondWith404 = true, bubbleErrors = false, }) => {
@@ -27,7 +34,7 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
27
34
  http = createHTTPInteraction(new PikkuFetchHTTPRequest(request), response);
28
35
  }
29
36
  const userSession = new PikkuUserSessionService(channelStore, channelId);
30
- const { channelConfig, openingData } = await openChannel({
37
+ const { channelConfig, openingData, meta } = await openChannel({
31
38
  channelId,
32
39
  createSessionServices,
33
40
  request,
@@ -52,7 +59,9 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
52
59
  if (createSessionServices) {
53
60
  sessionServices = await createSessionServices(singletonServices, { http }, await userSession.get());
54
61
  }
55
- await channelConfig.onConnect?.({ ...singletonServices, ...sessionServices }, channel);
62
+ if (channelConfig.onConnect && meta.connectPikkuFuncName) {
63
+ await runPikkuFuncDirectly(meta.connectPikkuFuncName, { ...singletonServices, ...sessionServices, channel }, openingData);
64
+ }
56
65
  http?.response?.status(101);
57
66
  }
58
67
  catch (e) {
@@ -72,7 +81,7 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
72
81
  export const runChannelDisconnect = async ({ singletonServices, ...params }) => {
73
82
  let sessionServices;
74
83
  const { openingData, channelName, session } = await params.channelStore.getChannelAndSession(params.channelId);
75
- const { channel, channelConfig } = getVariablesForChannel({
84
+ const { channel, channelConfig, meta } = getVariablesForChannel({
76
85
  ...params,
77
86
  openingData,
78
87
  channelName,
@@ -80,7 +89,9 @@ export const runChannelDisconnect = async ({ singletonServices, ...params }) =>
80
89
  if (!sessionServices && params.createSessionServices) {
81
90
  sessionServices = await params.createSessionServices(singletonServices, {}, session);
82
91
  }
83
- await channelConfig.onDisconnect?.({ ...singletonServices, ...sessionServices }, channel);
92
+ if (channelConfig.onDisconnect && meta.disconnectPikkuFuncName) {
93
+ await runPikkuFuncDirectly(meta.disconnectPikkuFuncName, { ...singletonServices, ...sessionServices, channel }, undefined);
94
+ }
84
95
  await params.channelStore.removeChannels([channel.channelId]);
85
96
  if (sessionServices) {
86
97
  await closeSessionServices(singletonServices.logger, sessionServices);
@@ -0,0 +1,12 @@
1
+ import { CoreServices, CoreSingletonServices, CoreUserSession } from '../types/core.types.js';
2
+ import { CoreAPIFunction, CoreAPIFunctionSessionless, CorePermissionGroup } from './functions.types.js';
3
+ export declare const addFunction: (funcName: string, func: CoreAPIFunction<any, any> | CoreAPIFunctionSessionless<any, any>) => void;
4
+ export declare const runPikkuFuncDirectly: <In, Out>(funcName: string, allServices: CoreServices, data: In, session?: CoreUserSession) => Promise<Out>;
5
+ export declare const runPikkuFunc: <In = any, Out = any>(funcName: string, { singletonServices, getAllServices, data, session, permissions, coerceDataFromSchema, }: {
6
+ singletonServices: CoreSingletonServices;
7
+ getAllServices: () => Promise<CoreServices> | CoreServices;
8
+ data: In;
9
+ session?: CoreUserSession;
10
+ permissions?: CorePermissionGroup;
11
+ coerceDataFromSchema?: boolean;
12
+ }) => Promise<Out>;
@@ -0,0 +1,38 @@
1
+ import { ForbiddenError } from '../errors/errors.js';
2
+ import { verifyPermissions } from '../permissions.js';
3
+ import { pikkuState } from '../pikku-state.js';
4
+ import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
5
+ export const addFunction = (funcName, func) => {
6
+ pikkuState('functions', 'nameToFunction').set(funcName, func);
7
+ };
8
+ export const runPikkuFuncDirectly = async (funcName, allServices, data, session) => {
9
+ const func = pikkuState('functions', 'nameToFunction').get(funcName);
10
+ if (!func) {
11
+ throw new Error(`Function not found: ${funcName}`);
12
+ }
13
+ return (await func(allServices, data, session));
14
+ };
15
+ export const runPikkuFunc = async (funcName, { singletonServices, getAllServices, data, session, permissions, coerceDataFromSchema, }) => {
16
+ const func = pikkuState('functions', 'nameToFunction').get(funcName);
17
+ if (!func) {
18
+ throw new Error(`Function not found: ${funcName}`);
19
+ }
20
+ const funcMeta = pikkuState('functions', 'meta')[funcName];
21
+ if (!funcMeta) {
22
+ throw new Error(`Function meta not found: ${funcName}`);
23
+ }
24
+ const schemaName = funcMeta.schemaName;
25
+ // Validate request data against the defined schema, if any
26
+ await validateSchema(singletonServices.logger, singletonServices.schema, schemaName, data);
27
+ // Coerce (top level) query string parameters or date objects if specified by the schema
28
+ if (coerceDataFromSchema && schemaName) {
29
+ coerceTopLevelDataFromSchema(schemaName, data);
30
+ }
31
+ const allServices = await getAllServices();
32
+ // Execute permission checks
33
+ const permissioned = await verifyPermissions(permissions, allServices, data, session);
34
+ if (permissioned === false) {
35
+ throw new ForbiddenError('Permission denied');
36
+ }
37
+ return (await func(allServices, data, session));
38
+ };
@@ -0,0 +1,37 @@
1
+ import { PikkuChannel } from '../channel/channel.types.js';
2
+ import type { CoreServices, CoreSingletonServices, CoreUserSession } from '../types/core.types.js';
3
+ /**
4
+ * Represents a core API function that performs an operation using core services and a user session.
5
+ *
6
+ * @template In - The input type.
7
+ * @template Out - The output type.
8
+ * @template Services - The services type, defaults to `CoreServices`.
9
+ * @template Session - The session type, defaults to `CoreUserSession`.
10
+ */
11
+ export type CoreAPIFunction<In, Out, ChannelData extends unknown | null = null, Services extends CoreSingletonServices = CoreServices & (ChannelData extends null ? {
12
+ channel?: PikkuChannel<unknown, Out> | undefined;
13
+ } : {
14
+ channel: PikkuChannel<ChannelData, Out>;
15
+ }), Session extends CoreUserSession = CoreUserSession> = (services: Services, data: In, session: Session) => ChannelData extends null ? Promise<Out> : Promise<Out> | Promise<void>;
16
+ /**
17
+ * Represents a core API function that can be used without a session.
18
+ *
19
+ * @template In - The input type.
20
+ * @template Out - The output type.
21
+ * @template Services - The services type, defaults to `CoreServices`.
22
+ * @template Session - The session type, defaults to `CoreUserSession`.
23
+ */
24
+ export type CoreAPIFunctionSessionless<In, Out, ChannelData extends unknown | null = null, Services extends CoreSingletonServices = CoreServices & (ChannelData extends null ? {
25
+ channel?: PikkuChannel<unknown, Out> | undefined;
26
+ } : {
27
+ channel: PikkuChannel<ChannelData, Out>;
28
+ }), Session extends CoreUserSession = CoreUserSession> = (services: Services, data: In, session?: Session) => ChannelData extends null ? Promise<Out> : Promise<Out> | Promise<void>;
29
+ /**
30
+ * Represents a function that checks permissions for a given API operation.
31
+ *
32
+ * @template In - The input type.
33
+ * @template Services - The services type, defaults to `CoreServices`.
34
+ * @template Session - The session type, defaults to `CoreUserSession`.
35
+ */
36
+ export type CoreAPIPermission<In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (services: Services, data: In, session?: Session) => Promise<boolean>;
37
+ export type CorePermissionGroup<APIPermission = CoreAPIPermission<any>> = Record<string, APIPermission | APIPermission[]> | undefined;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from './function-runner.js';
2
+ export * from './functions.types.js';
@@ -0,0 +1,2 @@
1
+ export * from './function-runner.js';
2
+ export * from './functions.types.js';
@@ -1,5 +1,5 @@
1
1
  import { Logger } from './services/logger.js';
2
- import { PikkuHTTP } from './http/http-routes.types.js';
2
+ import { PikkuHTTP } from './http/http.types.js';
3
3
  /**
4
4
  * Handle errors that occur during route processing
5
5
  *
@@ -24,11 +24,11 @@ export declare const addMiddleware: <APIMiddleware extends PikkuMiddleware>(rout
24
24
  * @template Route Route pattern as a string.
25
25
  * @template APIFunction Type for the route handler function.
26
26
  * @template APIFunctionSessionless Type for a sessionless handler.
27
- * @template APIPermission Type representing required permissions.
27
+ * @template APIPermissionGroup Type representing required permissions.
28
28
  * @template APIMiddleware Middleware type to be used with the route.
29
29
  * @param {CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>} route - The route configuration object.
30
30
  */
31
- export declare const addRoute: <In, Out, Route extends string, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>(route: CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>) => void;
31
+ export declare const addRoute: <In, Out, Route extends string, APIFunction, APIFunctionSessionless, APIPermissionGroup, APIMiddleware>(route: CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermissionGroup, APIMiddleware>) => void;
32
32
  /**
33
33
  * Combines the request and response objects into a single HTTP interaction object.
34
34
  *
@@ -87,4 +87,4 @@ export declare const pikkuFetch: <In, Out>(request: Request | PikkuHTTPRequest,
87
87
  * @param {RunRouteOptions & RunRouteParams} options - Options such as singleton services, session handling, and error configuration.
88
88
  * @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
89
89
  */
90
- export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { singletonServices, createSessionServices, skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, }: RunRouteOptions & RunRouteParams) => Promise<Out | void>;
90
+ export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { singletonServices, createSessionServices, skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, generateRequestId, }: RunRouteOptions & RunRouteParams) => Promise<Out | void>;