@pikku/core 0.9.5 → 0.9.7

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 (73) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/function/function-runner.d.ts +2 -2
  3. package/dist/function/function-runner.js +3 -3
  4. package/dist/function/functions.types.d.ts +4 -0
  5. package/dist/function/functions.types.js +6 -1
  6. package/dist/index.d.ts +3 -3
  7. package/dist/index.js +3 -3
  8. package/dist/middleware-runner.d.ts +5 -4
  9. package/dist/middleware-runner.js +20 -6
  10. package/dist/permissions.d.ts +3 -3
  11. package/dist/permissions.js +30 -8
  12. package/dist/pikku-state.d.ts +2 -4
  13. package/dist/pikku-state.js +3 -2
  14. package/dist/schema.js +8 -6
  15. package/dist/services/local-content.js +1 -0
  16. package/dist/types/core.types.d.ts +4 -0
  17. package/dist/types/core.types.js +6 -0
  18. package/dist/utils.d.ts +1 -0
  19. package/dist/utils.js +17 -0
  20. package/dist/wirings/channel/channel-handler.js +2 -1
  21. package/dist/wirings/channel/channel-runner.d.ts +2 -0
  22. package/dist/wirings/channel/channel-runner.js +23 -25
  23. package/dist/wirings/channel/local/local-channel-runner.js +5 -3
  24. package/dist/wirings/channel/serverless/serverless-channel-runner.js +4 -2
  25. package/dist/wirings/http/http-runner.js +36 -53
  26. package/dist/wirings/http/http.types.d.ts +1 -1
  27. package/dist/wirings/http/pikku-fetch-http-request.js +4 -1
  28. package/dist/wirings/http/routers/http-router.d.ts +12 -0
  29. package/dist/wirings/http/routers/http-router.js +2 -0
  30. package/dist/wirings/http/routers/path-to-regex.d.ts +10 -0
  31. package/dist/wirings/http/routers/path-to-regex.js +118 -0
  32. package/dist/wirings/mcp/mcp-runner.d.ts +1 -1
  33. package/dist/wirings/mcp/mcp-runner.js +3 -2
  34. package/dist/wirings/queue/queue-runner.js +3 -2
  35. package/dist/wirings/rpc/rpc-runner.js +2 -1
  36. package/dist/wirings/scheduler/scheduler-runner.d.ts +1 -1
  37. package/dist/wirings/scheduler/scheduler-runner.js +3 -2
  38. package/package.json +1 -1
  39. package/run-tests.sh +1 -1
  40. package/src/factory-functions.test.ts +37 -0
  41. package/src/function/function-runner.test.ts +101 -52
  42. package/src/function/function-runner.ts +5 -2
  43. package/src/function/functions.types.ts +13 -0
  44. package/src/index.ts +3 -11
  45. package/src/middleware-runner.test.ts +71 -43
  46. package/src/middleware-runner.ts +43 -17
  47. package/src/permissions.test.ts +21 -17
  48. package/src/permissions.ts +68 -26
  49. package/src/pikku-state.ts +5 -3
  50. package/src/schema.ts +8 -6
  51. package/src/services/local-content.ts +1 -0
  52. package/src/types/core.types.ts +12 -0
  53. package/src/utils.ts +19 -0
  54. package/src/wirings/channel/channel-handler.ts +17 -11
  55. package/src/wirings/channel/channel-runner.ts +31 -25
  56. package/src/wirings/channel/local/local-channel-runner.test.ts +4 -0
  57. package/src/wirings/channel/local/local-channel-runner.ts +5 -4
  58. package/src/wirings/channel/serverless/serverless-channel-runner.ts +13 -11
  59. package/src/wirings/http/http-runner.test.ts +18 -6
  60. package/src/wirings/http/http-runner.ts +55 -74
  61. package/src/wirings/http/http.types.ts +1 -1
  62. package/src/wirings/http/pikku-fetch-http-request.ts +4 -1
  63. package/src/wirings/http/routers/http-router.ts +15 -0
  64. package/src/wirings/http/routers/path-to-regex.test.ts +309 -0
  65. package/src/wirings/http/routers/path-to-regex.ts +162 -0
  66. package/src/wirings/mcp/mcp-runner.ts +18 -12
  67. package/src/wirings/queue/queue-runner.ts +15 -7
  68. package/src/wirings/rpc/rpc-runner.ts +21 -16
  69. package/src/wirings/scheduler/scheduler-runner.ts +18 -12
  70. package/tsconfig.tsbuildinfo +1 -1
  71. package/dist/pikku-function.d.ts +0 -1
  72. package/dist/pikku-function.js +0 -1
  73. package/src/pikku-function.ts +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## 0.9.0
2
2
 
3
+ ## 0.9.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 85a1c76: fix: fixing delete method return type in local-content and ignoring body (for now) in delete calls
8
+
9
+ ## 0.9.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 6059c87: refactor: move PikkuPermission to pikkuPermission and same for middleware for api consistency to to improve future features
14
+ - 6db63bb: perf: changing http meta to a lookup map to reduce loops
15
+ - 74f8634: perf: moving router externally to be able to swap them out, similar to hono
16
+ - 766fef1: feat: adding caching for middleware and permissions
17
+
3
18
  ## 0.9.5
4
19
 
5
20
  ### Patch Changes
@@ -1,8 +1,8 @@
1
- import { CoreServices, CoreUserSession, CorePikkuMiddleware } from '../types/core.types.js';
1
+ import { CoreServices, CoreUserSession, CorePikkuMiddleware, PikkuWiringTypes } from '../types/core.types.js';
2
2
  import { CorePermissionGroup, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from './functions.types.js';
3
3
  export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any> | CorePikkuFunctionSessionless<any, any> | CorePikkuFunction<any, any>) => void;
4
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, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags, }: {
5
+ export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags, }: {
6
6
  getAllServices: () => Promise<CoreServices> | CoreServices;
7
7
  data: In;
8
8
  session?: CoreUserSession;
@@ -16,7 +16,7 @@ export const runPikkuFuncDirectly = async (funcName, allServices, data, session)
16
16
  }
17
17
  return (await funcConfig.func(allServices, data, session));
18
18
  };
19
- export const runPikkuFunc = async (funcName, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags = [], }) => {
19
+ export const runPikkuFunc = async (wireType, wireId, funcName, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags = [], }) => {
20
20
  const funcConfig = pikkuState('function', 'functions').get(funcName);
21
21
  if (!funcConfig) {
22
22
  throw new Error(`Function not found: ${funcName}`);
@@ -39,7 +39,7 @@ export const runPikkuFunc = async (funcName, { getAllServices, data, session, pe
39
39
  }
40
40
  }
41
41
  // Run permission checks in the specified order
42
- await runPermissions({
42
+ await runPermissions(wireType, wireId, {
43
43
  wiringTags: tags,
44
44
  wiringPermissions,
45
45
  funcTags: funcConfig.tags,
@@ -49,7 +49,7 @@ export const runPikkuFunc = async (funcName, { getAllServices, data, session, pe
49
49
  session,
50
50
  });
51
51
  // Combine all middleware: wiring tags → wiring middleware → func middleware → func tags
52
- const allMiddleware = combineMiddleware({
52
+ const allMiddleware = combineMiddleware(wireType, wireId, {
53
53
  wiringTags: tags,
54
54
  wiringMiddleware,
55
55
  funcMiddleware: funcConfig.middleware,
@@ -34,6 +34,10 @@ export type CorePikkuFunctionSessionless<In, Out, ChannelData extends unknown |
34
34
  * @template Session - The session type, defaults to `CoreUserSession`.
35
35
  */
36
36
  export type CorePikkuPermission<In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (services: Services, data: In, session?: Session) => Promise<boolean>;
37
+ /**
38
+ * Factory function for creating permissions with tree-shaking support
39
+ */
40
+ export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(permission: CorePikkuPermission<In, Services, Session>) => CorePikkuPermission<In, Services, Session>;
37
41
  export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> = Record<string, PikkuPermission | PikkuPermission[]> | undefined;
38
42
  export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>> = {
39
43
  name?: string;
@@ -1 +1,6 @@
1
- export {};
1
+ /**
2
+ * Factory function for creating permissions with tree-shaking support
3
+ */
4
+ export const pikkuPermission = (permission) => {
5
+ return permission;
6
+ };
package/dist/index.d.ts CHANGED
@@ -18,11 +18,11 @@ export * from './wirings/queue/index.js';
18
18
  export * from './wirings/mcp/index.js';
19
19
  export * from './errors/index.js';
20
20
  export * from './middleware/index.js';
21
- export * from './time-utils.js';
22
21
  export * from './utils.js';
22
+ export * from './time-utils.js';
23
23
  export { pikkuState } from './pikku-state.js';
24
- export { runMiddleware, addMiddleware, combineMiddleware, } from './middleware-runner.js';
25
- export { addPermission, getPermissionsForTags, runPermissions, } from './permissions.js';
24
+ export { addMiddleware, runMiddleware } from './middleware-runner.js';
25
+ export { addPermission } from './permissions.js';
26
26
  export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js';
27
27
  export { wireChannel } from './wirings/channel/channel-runner.js';
28
28
  export { wireScheduler } from './wirings/scheduler/scheduler-runner.js';
package/dist/index.js CHANGED
@@ -18,11 +18,11 @@ export * from './wirings/queue/index.js';
18
18
  export * from './wirings/mcp/index.js';
19
19
  export * from './errors/index.js';
20
20
  export * from './middleware/index.js';
21
- export * from './time-utils.js';
22
21
  export * from './utils.js';
22
+ export * from './time-utils.js';
23
23
  export { pikkuState } from './pikku-state.js';
24
- export { runMiddleware, addMiddleware, combineMiddleware, } from './middleware-runner.js';
25
- export { addPermission, getPermissionsForTags, runPermissions, } from './permissions.js';
24
+ export { addMiddleware, runMiddleware } from './middleware-runner.js';
25
+ export { addPermission } from './permissions.js';
26
26
  export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js';
27
27
  export { wireChannel } from './wirings/channel/channel-runner.js';
28
28
  export { wireScheduler } from './wirings/scheduler/scheduler-runner.js';
@@ -1,5 +1,5 @@
1
1
  import { UserSessionService } from './services/user-session-service.js';
2
- import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware } from './types/core.types.js';
2
+ import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware, PikkuWiringTypes } from './types/core.types.js';
3
3
  /**
4
4
  * Runs a chain of middleware functions in sequence before executing the main function.
5
5
  *
@@ -19,7 +19,7 @@ import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware } from './
19
19
  */
20
20
  export declare const runMiddleware: <Middleware extends CorePikkuMiddleware>(services: CoreSingletonServices & {
21
21
  userSession?: UserSessionService<any>;
22
- }, interaction: PikkuInteraction, middlewares: Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
22
+ }, interaction: PikkuInteraction, middlewares: readonly Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
23
23
  /**
24
24
  * Adds global middleware for a specific tag.
25
25
  *
@@ -84,9 +84,10 @@ export declare const getMiddlewareForTags: (tags?: string[]) => CorePikkuMiddlew
84
84
  * })
85
85
  * ```
86
86
  */
87
- export declare const combineMiddleware: ({ wiringMiddleware, wiringTags, funcMiddleware, funcTags, }?: {
87
+ export declare const combineMiddleware: (wireType: PikkuWiringTypes, uid: string, { httpMiddleware, wiringMiddleware, wiringTags, funcMiddleware, funcTags, }?: {
88
+ httpMiddleware?: CorePikkuMiddleware[];
88
89
  wiringMiddleware?: CorePikkuMiddleware[];
89
90
  wiringTags?: string[];
90
91
  funcMiddleware?: CorePikkuMiddleware[];
91
92
  funcTags?: string[];
92
- }) => CorePikkuMiddleware[];
93
+ }) => readonly CorePikkuMiddleware[];
@@ -1,4 +1,6 @@
1
+ import { PikkuWiringTypes, } from './types/core.types.js';
1
2
  import { pikkuState } from './pikku-state.js';
3
+ import { freezeDedupe } from './utils.js';
2
4
  /**
3
5
  * Runs a chain of middleware functions in sequence before executing the main function.
4
6
  *
@@ -18,11 +20,10 @@ import { pikkuState } from './pikku-state.js';
18
20
  */
19
21
  export const runMiddleware = async (services, interaction, middlewares, main) => {
20
22
  // Deduplicate middleware using Set to avoid running the same middleware multiple times
21
- const uniqueMiddleware = Array.from(new Set(middlewares));
22
23
  let result;
23
24
  const dispatch = async (index) => {
24
- if (uniqueMiddleware && index < uniqueMiddleware.length) {
25
- return await uniqueMiddleware[index](services, interaction, () => dispatch(index + 1));
25
+ if (middlewares && index < middlewares.length) {
26
+ return await middlewares[index](services, interaction, () => dispatch(index + 1));
26
27
  }
27
28
  else if (main) {
28
29
  result = await main();
@@ -93,6 +94,14 @@ export const getMiddlewareForTags = (tags) => {
93
94
  }
94
95
  return applicableMiddleware;
95
96
  };
97
+ const middlewareCache = {
98
+ [PikkuWiringTypes.http]: {},
99
+ [PikkuWiringTypes.rpc]: {},
100
+ [PikkuWiringTypes.channel]: {},
101
+ [PikkuWiringTypes.queue]: {},
102
+ [PikkuWiringTypes.scheduler]: {},
103
+ [PikkuWiringTypes.mcp]: {},
104
+ };
96
105
  /**
97
106
  * Combines tag-based middleware with wiring-specific middleware and function-level middleware.
98
107
  *
@@ -116,7 +125,10 @@ export const getMiddlewareForTags = (tags) => {
116
125
  * })
117
126
  * ```
118
127
  */
119
- export const combineMiddleware = ({ wiringMiddleware, wiringTags, funcMiddleware, funcTags, } = {}) => {
128
+ export const combineMiddleware = (wireType, uid, { httpMiddleware, wiringMiddleware, wiringTags, funcMiddleware, funcTags, } = {}) => {
129
+ if (middlewareCache[wireType][uid]) {
130
+ return middlewareCache[wireType][uid];
131
+ }
120
132
  // Run middleware in specific order:
121
133
  // 1) wiringTags middleware
122
134
  // 2) wiringMiddleware
@@ -124,10 +136,12 @@ export const combineMiddleware = ({ wiringMiddleware, wiringTags, funcMiddleware
124
136
  // 4) funcTags middleware
125
137
  const wiringTaggedMiddleware = getMiddlewareForTags(wiringTags);
126
138
  const funcTaggedMiddleware = getMiddlewareForTags(funcTags);
127
- return [
139
+ middlewareCache[wireType][uid] = freezeDedupe([
140
+ ...(httpMiddleware || []),
128
141
  ...wiringTaggedMiddleware,
129
142
  ...(wiringMiddleware || []),
130
143
  ...(funcMiddleware || []),
131
144
  ...funcTaggedMiddleware,
132
- ];
145
+ ]);
146
+ return middlewareCache[wireType][uid];
133
147
  };
@@ -1,4 +1,4 @@
1
- import { CoreServices, CoreUserSession } from './types/core.types.js';
1
+ import { CoreServices, CoreUserSession, PikkuWiringTypes } from './types/core.types.js';
2
2
  import { CorePermissionGroup, CorePikkuPermission } from './function/functions.types.js';
3
3
  /**
4
4
  * This function validates permissions by iterating over permission groups and executing the corresponding permission functions. If all functions in at least one group return true, the permission is considered valid.
@@ -47,7 +47,7 @@ export declare const addPermission: (tag: string, permissions: CorePermissionGro
47
47
  * const permissions = getPermissionsForTags(['api', 'auth'])
48
48
  * ```
49
49
  */
50
- export declare const getPermissionsForTags: (tags?: string[]) => Array<CorePermissionGroup | CorePikkuPermission>;
50
+ export declare const getPermissionsForTags: (tags?: string[]) => readonly (CorePermissionGroup | CorePikkuPermission)[];
51
51
  /**
52
52
  * Runs permission checks in the specified order:
53
53
  * 1) wiring tag permissions - at least one must pass if any exist
@@ -55,7 +55,7 @@ export declare const getPermissionsForTags: (tags?: string[]) => Array<CorePermi
55
55
  * 3) function tag permissions - at least one must pass if any exist
56
56
  * 4) function permissions - must pass if defined
57
57
  */
58
- export declare const runPermissions: ({ wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }: {
58
+ export declare const runPermissions: (wireType: PikkuWiringTypes, uid: string, { wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }: {
59
59
  wiringTags?: string[];
60
60
  wiringPermissions?: CorePermissionGroup;
61
61
  funcTags?: string[];
@@ -1,3 +1,4 @@
1
+ import { PikkuWiringTypes, } from './types/core.types.js';
1
2
  import { pikkuState } from './pikku-state.js';
2
3
  import { ForbiddenError } from './errors/errors.js';
3
4
  /**
@@ -63,6 +64,7 @@ export const addPermission = (tag, permissions) => {
63
64
  }
64
65
  permissionsStore[tag] = permissions;
65
66
  };
67
+ const EMPTY = [];
66
68
  /**
67
69
  * Retrieves permissions for a given set of tags.
68
70
  *
@@ -80,7 +82,7 @@ export const addPermission = (tag, permissions) => {
80
82
  */
81
83
  export const getPermissionsForTags = (tags) => {
82
84
  if (!tags || tags.length === 0) {
83
- return [];
85
+ return EMPTY;
84
86
  }
85
87
  const permissionsStore = pikkuState('misc', 'permissions');
86
88
  const applicablePermissions = [];
@@ -98,6 +100,14 @@ export const getPermissionsForTags = (tags) => {
98
100
  }
99
101
  return applicablePermissions;
100
102
  };
103
+ const permissionCache = {
104
+ [PikkuWiringTypes.http]: {},
105
+ [PikkuWiringTypes.rpc]: {},
106
+ [PikkuWiringTypes.channel]: {},
107
+ [PikkuWiringTypes.queue]: {},
108
+ [PikkuWiringTypes.scheduler]: {},
109
+ [PikkuWiringTypes.mcp]: {},
110
+ };
101
111
  /**
102
112
  * Runs permission checks in the specified order:
103
113
  * 1) wiring tag permissions - at least one must pass if any exist
@@ -105,10 +115,18 @@ export const getPermissionsForTags = (tags) => {
105
115
  * 3) function tag permissions - at least one must pass if any exist
106
116
  * 4) function permissions - must pass if defined
107
117
  */
108
- export const runPermissions = async ({ wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }) => {
118
+ export const runPermissions = async (wireType, uid, { wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }) => {
119
+ let cachedPermission = permissionCache[wireType][uid];
120
+ if (!cachedPermission) {
121
+ cachedPermission = {
122
+ wiringTags: getPermissionsForTags(wiringTags),
123
+ funcTags: getPermissionsForTags(funcTags),
124
+ };
125
+ permissionCache[wireType][uid] = cachedPermission;
126
+ }
109
127
  let permissioned = true;
110
128
  // 1. Wiring tag permissions - at least one must pass if any exist
111
- const wiringTaggedPermissions = getPermissionsForTags(wiringTags || []);
129
+ const wiringTaggedPermissions = cachedPermission.wiringTags;
112
130
  if (wiringTaggedPermissions.length > 0) {
113
131
  permissioned = false; // Start false, need at least one to pass
114
132
  for (const permissions of wiringTaggedPermissions) {
@@ -119,18 +137,20 @@ export const runPermissions = async ({ wiringTags, wiringPermissions, funcTags,
119
137
  }
120
138
  }
121
139
  if (!permissioned) {
122
- throw new ForbiddenError('Permission denied - wiring tag permissions');
140
+ allServices.logger.debug('Permission denied - wiring tag permissions');
141
+ throw new ForbiddenError('Permission denied');
123
142
  }
124
143
  }
125
144
  // 2. Wiring permissions - must pass if defined
126
145
  if (wiringPermissions) {
127
146
  permissioned = await verifyPermissions(wiringPermissions, allServices, data, session);
128
147
  if (!permissioned) {
129
- throw new ForbiddenError('Permission denied - wiring permissions');
148
+ allServices.logger.debug('Permission denied - wiring permissions');
149
+ throw new ForbiddenError('Permission denied');
130
150
  }
131
151
  }
132
152
  // 3. Function tag permissions - at least one must pass if any exist
133
- const funcTaggedPermissions = getPermissionsForTags(funcTags || []);
153
+ const funcTaggedPermissions = cachedPermission.funcTags;
134
154
  if (funcTaggedPermissions.length > 0) {
135
155
  permissioned = false; // Start false, need at least one to pass
136
156
  for (const permissions of funcTaggedPermissions) {
@@ -141,14 +161,16 @@ export const runPermissions = async ({ wiringTags, wiringPermissions, funcTags,
141
161
  }
142
162
  }
143
163
  if (!permissioned) {
144
- throw new ForbiddenError('Permission denied - function tag permissions');
164
+ allServices.logger.debug('Permission denied - function tag permissions');
165
+ throw new ForbiddenError('Permission denied');
145
166
  }
146
167
  }
147
168
  // 4. Function permissions - must pass if defined
148
169
  if (funcPermissions) {
149
170
  permissioned = await verifyPermissions(funcPermissions, allServices, data, session);
150
171
  if (!permissioned) {
151
- throw new ForbiddenError('Permission denied - function permissions');
172
+ allServices.logger.debug('Permission denied - function permissions');
173
+ throw new ForbiddenError('Permission denied');
152
174
  }
153
175
  }
154
176
  };
@@ -19,10 +19,8 @@ interface PikkuState {
19
19
  }>;
20
20
  };
21
21
  http: {
22
- middleware: Array<{
23
- route: string;
24
- middleware: CorePikkuMiddleware[];
25
- }>;
22
+ middleware: Map<string, CorePikkuMiddleware<any, any>[]>;
23
+ permissions: Map<string, CorePermissionGroup | CorePikkuPermission[]>;
26
24
  routes: Map<HTTPMethod, Map<string, CoreHTTPFunctionWiring<any, any, any>>>;
27
25
  meta: HTTPWiringsMeta;
28
26
  };
@@ -9,9 +9,10 @@ export const resetPikkuState = () => {
9
9
  files: new Map(),
10
10
  },
11
11
  http: {
12
- middleware: [],
12
+ permissions: new Map(),
13
+ middleware: new Map(),
13
14
  routes: new Map(),
14
- meta: [],
15
+ meta: {},
15
16
  },
16
17
  channel: {
17
18
  channels: new Map(),
package/dist/schema.js CHANGED
@@ -27,13 +27,15 @@ const validateAllSchemasLoaded = (logger, schemaService) => {
27
27
  const routesMeta = pikkuState('http', 'meta');
28
28
  const validators = schemaService.getSchemaNames();
29
29
  const missingSchemas = [];
30
- for (const route of routesMeta) {
31
- const inputs = pikkuState('function', 'meta')[route.pikkuFuncName]?.inputs;
32
- const input = inputs?.[0];
33
- if (!input || validators.has(input)) {
34
- continue;
30
+ for (const routePaths of Object.values(routesMeta)) {
31
+ for (const meta of Object.values(routePaths)) {
32
+ const inputs = pikkuState('function', 'meta')[meta.pikkuFuncName]?.inputs;
33
+ const input = inputs?.[0];
34
+ if (!input || validators.has(input)) {
35
+ continue;
36
+ }
37
+ missingSchemas.push(input);
35
38
  }
36
- missingSchemas.push(input);
37
39
  }
38
40
  if (missingSchemas.length > 0) {
39
41
  logger.error(`Error: Failed to load schemas:\n.${missingSchemas.join('\n')}`);
@@ -71,6 +71,7 @@ export class LocalContent {
71
71
  this.logger.debug(`deleting key: ${assetKey}`);
72
72
  try {
73
73
  await promises.unlink(`${this.config.localFileUploadPath}/${assetKey}`);
74
+ return true;
74
75
  }
75
76
  catch (e) {
76
77
  this.logger.error(`Error deleting content ${assetKey}`, e);
@@ -106,6 +106,10 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
106
106
  export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices & {
107
107
  userSession: UserSessionService<UserSession>;
108
108
  }, interactions: PikkuInteraction, next: () => Promise<void>) => Promise<void>;
109
+ /**
110
+ * Factory function for creating middleware with tree-shaking support
111
+ */
112
+ export declare const pikkuMiddleware: <SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession>(middleware: CorePikkuMiddleware<SingletonServices, UserSession>) => CorePikkuMiddleware<SingletonServices, UserSession>;
109
113
  /**
110
114
  * Represents the core services used by Pikku, including singleton services and the request/response interaction.
111
115
  */
@@ -7,3 +7,9 @@ export var PikkuWiringTypes;
7
7
  PikkuWiringTypes["queue"] = "queue";
8
8
  PikkuWiringTypes["mcp"] = "mcp";
9
9
  })(PikkuWiringTypes || (PikkuWiringTypes = {}));
10
+ /**
11
+ * Factory function for creating middleware with tree-shaking support
12
+ */
13
+ export const pikkuMiddleware = (middleware) => {
14
+ return middleware;
15
+ };
package/dist/utils.d.ts CHANGED
@@ -2,3 +2,4 @@ import { Logger } from './services/logger.js';
2
2
  export declare const closeSessionServices: (logger: Logger, sessionServices: Record<string, any>) => Promise<void>;
3
3
  export declare const createWeakUID: () => string;
4
4
  export declare const isSerializable: (data: any) => boolean;
5
+ export declare const freezeDedupe: <T extends Function>(arr?: readonly T[] | T[] | undefined) => readonly T[];
package/dist/utils.js CHANGED
@@ -28,3 +28,20 @@ export const isSerializable = (data) => {
28
28
  data instanceof Float32Array ||
29
29
  data instanceof Float64Array);
30
30
  };
31
+ const EMPTY_ARRAY = Object.freeze([]);
32
+ export const freezeDedupe = (arr) => {
33
+ if (!arr || arr.length === 0)
34
+ return EMPTY_ARRAY;
35
+ if (arr.length === 1)
36
+ return Object.freeze([arr[0]]);
37
+ const seen = new Set();
38
+ const out = [];
39
+ for (let i = 0; i < arr.length; i++) {
40
+ const fn = arr[i];
41
+ if (!seen.has(fn)) {
42
+ seen.add(fn);
43
+ out.push(fn);
44
+ }
45
+ }
46
+ return Object.freeze(out);
47
+ };
@@ -1,3 +1,4 @@
1
+ import { PikkuWiringTypes, } from '../../types/core.types.js';
1
2
  import { pikkuState } from '../../pikku-state.js';
2
3
  import { runPikkuFunc } from '../../function/function-runner.js';
3
4
  const getRouteMeta = (channelName, routingProperty, routerValue) => {
@@ -47,7 +48,7 @@ export const processMessageHandlers = (services, session, channelConfig, channel
47
48
  const { pikkuFuncName } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
48
49
  const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
49
50
  const middleware = typeof onMessage === 'function' ? [] : onMessage.middleware;
50
- return await runPikkuFunc(pikkuFuncName, {
51
+ return await runPikkuFunc(PikkuWiringTypes.channel, channelConfig.name, pikkuFuncName, {
51
52
  getAllServices: () => ({
52
53
  ...services,
53
54
  channel: channelHandler.getChannel(),
@@ -1,4 +1,5 @@
1
1
  import { UserSessionService } from '../../services/user-session-service.js';
2
+ import { CorePikkuMiddleware } from '../../types/core.types.js';
2
3
  import { ChannelMeta, CoreChannel, RunChannelOptions, RunChannelParams } from './channel.types.js';
3
4
  /**
4
5
  * Adds a channel and registers all functions referenced in it using the
@@ -11,4 +12,5 @@ export declare const openChannel: ({ route, singletonServices, coerceDataFromSch
11
12
  openingData: unknown;
12
13
  channelConfig: CoreChannel<unknown, any>;
13
14
  meta: ChannelMeta;
15
+ httpMiddleware: CorePikkuMiddleware[] | undefined;
14
16
  }>;
@@ -2,7 +2,7 @@ import { NotFoundError } from '../../errors/errors.js';
2
2
  import { addFunction } from '../../function/function-runner.js';
3
3
  import { pikkuState } from '../../pikku-state.js';
4
4
  import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js';
5
- import { match } from 'path-to-regexp';
5
+ import { httpRouter } from '../http/routers/http-router.js';
6
6
  /**
7
7
  * Adds a channel and registers all functions referenced in it using the
8
8
  * function names already stored in the channel metadata
@@ -65,30 +65,28 @@ export const wireChannel = (channel) => {
65
65
  pikkuState('channel', 'channels').set(channel.name, channel);
66
66
  };
67
67
  const getMatchingChannelConfig = (request) => {
68
+ const matchedPath = httpRouter.match('get', request);
69
+ if (!matchedPath) {
70
+ return null;
71
+ }
72
+ const meta = pikkuState('channel', 'meta');
73
+ const channelMeta = Object.values(meta).find((channelConfig) => channelConfig.route === matchedPath.route);
74
+ if (!channelMeta) {
75
+ return null;
76
+ }
68
77
  const channels = pikkuState('channel', 'channels');
69
- const channelsMeta = pikkuState('channel', 'meta');
70
- for (const channelConfig of channels.values()) {
71
- const cleanedRoute = channelConfig.route.replace(/^\/\//, '/');
72
- const cleanedRequest = request.replace(/^\/\//, '/');
73
- const matchFunc = match(cleanedRoute, {
74
- decode: decodeURIComponent,
75
- });
76
- const matchedPath = matchFunc(cleanedRequest);
77
- if (matchedPath) {
78
- const channelMeta = channelsMeta[channelConfig.name];
79
- if (!channelMeta) {
80
- throw new Error(`Channel ${channelConfig.name} not found in metadata`);
81
- }
82
- return {
83
- matchedPath,
84
- params: matchedPath.params,
85
- channelConfig,
86
- schemaName: channelMeta.input,
87
- meta: channelMeta,
88
- };
89
- }
78
+ const channelConfig = channels.get(channelMeta.name);
79
+ if (!channelConfig) {
80
+ return null;
90
81
  }
91
- return null;
82
+ return {
83
+ matchedPath,
84
+ params: matchedPath.params,
85
+ channelConfig,
86
+ schemaName: channelMeta.input,
87
+ meta: channelMeta,
88
+ httpMiddleware: matchedPath.middleware,
89
+ };
92
90
  };
93
91
  export const openChannel = async ({ route, singletonServices, coerceDataFromSchema = true, request, }) => {
94
92
  const matchingChannel = getMatchingChannelConfig(route);
@@ -96,7 +94,7 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
96
94
  singletonServices.logger.info(`Channel not found: ${route}`);
97
95
  throw new NotFoundError(`Channel not found: ${route}`);
98
96
  }
99
- const { params, channelConfig, schemaName, meta } = matchingChannel;
97
+ const { params, channelConfig, schemaName, meta, httpMiddleware } = matchingChannel;
100
98
  const requiresSession = channelConfig.auth !== false;
101
99
  request?.setParams(params);
102
100
  singletonServices.logger.info(`Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`);
@@ -108,5 +106,5 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
108
106
  }
109
107
  await validateSchema(singletonServices.logger, singletonServices.schema, schemaName, openingData);
110
108
  }
111
- return { openingData, channelConfig, meta };
109
+ return { openingData, channelConfig, meta, httpMiddleware };
112
110
  };
@@ -3,6 +3,7 @@ import { createHTTPInteraction } from '../../http/http-runner.js';
3
3
  import { closeSessionServices } from '../../../utils.js';
4
4
  import { processMessageHandlers } from '../channel-handler.js';
5
5
  import { PikkuLocalChannelHandler } from './local-channel-handler.js';
6
+ import { PikkuWiringTypes } from '../../../types/core.types.js';
6
7
  import { handleHTTPError } from '../../../handle-error.js';
7
8
  import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
8
9
  import { PikkuUserSessionService } from '../../../services/user-session-service.js';
@@ -17,10 +18,10 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
17
18
  http = createHTTPInteraction(request, response);
18
19
  route = http?.request?.path();
19
20
  }
20
- let openingData, channelConfig, meta;
21
+ let openingData, channelConfig, meta, httpMiddleware;
21
22
  try {
22
23
  ;
23
- ({ openingData, channelConfig, meta } = await openChannel({
24
+ ({ openingData, channelConfig, meta, httpMiddleware } = await openChannel({
24
25
  channelId,
25
26
  createSessionServices,
26
27
  respondWith404,
@@ -77,9 +78,10 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
77
78
  await runMiddleware({
78
79
  ...singletonServices,
79
80
  userSession,
80
- }, { http }, combineMiddleware({
81
+ }, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
81
82
  wiringMiddleware: channelConfig.middleware,
82
83
  wiringTags: channelConfig.tags,
84
+ httpMiddleware,
83
85
  }), main);
84
86
  return channelHandler;
85
87
  };
@@ -1,3 +1,4 @@
1
+ import { PikkuWiringTypes } from '../../../types/core.types.js';
1
2
  import { closeSessionServices } from '../../../utils.js';
2
3
  import { processMessageHandlers } from '../channel-handler.js';
3
4
  import { openChannel } from '../channel-runner.js';
@@ -34,7 +35,7 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
34
35
  http = createHTTPInteraction(new PikkuFetchHTTPRequest(request), response);
35
36
  }
36
37
  const userSession = new PikkuUserSessionService(channelStore, channelId);
37
- const { channelConfig, openingData, meta } = await openChannel({
38
+ const { channelConfig, openingData, meta, httpMiddleware } = await openChannel({
38
39
  channelId,
39
40
  createSessionServices,
40
41
  request,
@@ -76,9 +77,10 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
76
77
  await runMiddleware({
77
78
  ...singletonServices,
78
79
  userSession,
79
- }, { http }, combineMiddleware({
80
+ }, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
80
81
  wiringMiddleware: channelConfig.middleware,
81
82
  wiringTags: channelConfig.tags,
83
+ httpMiddleware,
82
84
  }), main);
83
85
  };
84
86
  export const runChannelDisconnect = async ({ singletonServices, ...params }) => {