@pikku/core 0.12.15 → 0.12.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## 0.12.4
2
2
 
3
+ ## 0.12.16
4
+
5
+ ### Patch Changes
6
+
7
+ - fbcf5b9: Add middleware priority system, telemetry middleware, and statusCode getter. Middleware now supports named priority levels (highest, high, medium, low, lowest) that control execution order regardless of registration order. Includes telemetryOuter and telemetryInner middleware for observability instrumentation via structured console.log output. PikkuHTTPResponse now exposes a readonly `statusCode` getter across all response implementations.
8
+
3
9
  ## 0.12.15
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @module @pikku/core
3
3
  */
4
- export type { CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuWiringTypes, RequireAtLeastOne, SerializedError, WireServices, } from './types/core.types.js';
4
+ export type { CommonWireMeta, CoreConfig, CorePikkuMiddleware, CorePikkuMiddlewareConfig, CorePikkuMiddlewareFactory, CorePikkuMiddlewareGroup, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, FunctionMeta, FunctionRuntimeMeta, FunctionServicesMeta, FunctionWiresMeta, FunctionsMeta, FunctionsRuntimeMeta, JSONPrimitive, JSONValue, MakeRequired, MiddlewareMetadata, MiddlewarePriority, PermissionMetadata, PickOptional, PickRequired, PikkuAIMiddlewareHooks, PikkuWire, PikkuWiringTypes, RequireAtLeastOne, SerializedError, WireServices, } from './types/core.types.js';
5
5
  export { pikkuAIMiddleware, pikkuChannelMiddleware, pikkuChannelMiddlewareFactory, pikkuMiddleware, pikkuMiddlewareFactory, } from './types/core.types.js';
6
6
  export type { CorePikkuAuth, CorePikkuAuthConfig, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuPermissionConfig, CorePikkuPermissionFactory, CorePikkuApprovalDescription, CorePermissionGroup, ZodLike, } from './function/functions.types.js';
7
7
  export { pikkuAuth, pikkuPermission, pikkuPermissionFactory, pikkuApprovalDescription, } from './function/functions.types.js';
@@ -3,5 +3,6 @@ export { authCookie } from './auth-cookie.js';
3
3
  export { authBearer } from './auth-bearer.js';
4
4
  export { pikkuRemoteAuthMiddleware } from './remote-auth.js';
5
5
  export { cors } from './cors.js';
6
+ export { telemetryOuter, telemetryInner } from './telemetry.js';
6
7
  export { addMiddleware, runMiddleware } from '../middleware-runner.js';
7
8
  export { addPermission } from '../permissions.js';
@@ -3,5 +3,6 @@ export { authCookie } from './auth-cookie.js';
3
3
  export { authBearer } from './auth-bearer.js';
4
4
  export { pikkuRemoteAuthMiddleware } from './remote-auth.js';
5
5
  export { cors } from './cors.js';
6
+ export { telemetryOuter, telemetryInner } from './telemetry.js';
6
7
  export { addMiddleware, runMiddleware } from '../middleware-runner.js';
7
8
  export { addPermission } from '../permissions.js';
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Outer telemetry middleware that captures total request duration and outcome.
3
+ * Runs at `highest` priority (outermost in the middleware chain).
4
+ *
5
+ * Emits a structured JSON log entry with `__pikku_telemetry: 'end'` containing:
6
+ * - Total duration (including all middleware)
7
+ * - Outcome (ok/error)
8
+ * - Wire metadata (wireType, wireId, traceId)
9
+ * - HTTP details (method, path, status) if applicable
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { telemetryOuter } from '@pikku/core/middleware'
14
+ * addMiddleware('myTag', [telemetryOuter()])
15
+ * ```
16
+ */
17
+ export declare const telemetryOuter: import("../types/core.types.js").CorePikkuMiddlewareFactory<void | {
18
+ environmentId?: string;
19
+ orgId?: string;
20
+ }, import("../types/core.types.js").CoreSingletonServices<{
21
+ logLevel?: import("../services/logger.js").LogLevel;
22
+ secrets?: {};
23
+ workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
24
+ }>, import("../types/core.types.js").CoreUserSession>;
25
+ /**
26
+ * Inner telemetry middleware that captures function execution duration and user context.
27
+ * Runs at `lowest` priority (innermost, closest to the function).
28
+ *
29
+ * Emits a structured JSON log entry with `__pikku_telemetry: 'end'` containing:
30
+ * - Function-only duration (excluding outer middleware like auth)
31
+ * - User identity (pikkuUserId) if authenticated
32
+ * - Wire metadata (wireType, wireId, traceId)
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { telemetryInner } from '@pikku/core/middleware'
37
+ * addMiddleware('myTag', [telemetryInner()])
38
+ * ```
39
+ */
40
+ export declare const telemetryInner: import("../types/core.types.js").CorePikkuMiddlewareFactory<void | {
41
+ environmentId?: string;
42
+ orgId?: string;
43
+ }, import("../types/core.types.js").CoreSingletonServices<{
44
+ logLevel?: import("../services/logger.js").LogLevel;
45
+ secrets?: {};
46
+ workflow?: import("../wirings/workflow/workflow.types.js").WorkflowServiceConfig;
47
+ }>, import("../types/core.types.js").CoreUserSession>;
@@ -0,0 +1,106 @@
1
+ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js';
2
+ /**
3
+ * Outer telemetry middleware that captures total request duration and outcome.
4
+ * Runs at `highest` priority (outermost in the middleware chain).
5
+ *
6
+ * Emits a structured JSON log entry with `__pikku_telemetry: 'end'` containing:
7
+ * - Total duration (including all middleware)
8
+ * - Outcome (ok/error)
9
+ * - Wire metadata (wireType, wireId, traceId)
10
+ * - HTTP details (method, path, status) if applicable
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { telemetryOuter } from '@pikku/core/middleware'
15
+ * addMiddleware('myTag', [telemetryOuter()])
16
+ * ```
17
+ */
18
+ export const telemetryOuter = pikkuMiddlewareFactory(({ environmentId, orgId } = {}) => {
19
+ return pikkuMiddleware({
20
+ name: 'telemetry-outer',
21
+ priority: 'highest',
22
+ func: async (services, wire, next) => {
23
+ const start = performance.now();
24
+ let outcome = 'ok';
25
+ let errorMessage;
26
+ try {
27
+ await next();
28
+ }
29
+ catch (e) {
30
+ outcome = 'error';
31
+ errorMessage = e instanceof Error ? e.message : String(e);
32
+ throw e;
33
+ }
34
+ finally {
35
+ services.logger.info({
36
+ __pikku_telemetry: 'end',
37
+ __pikku_layer: 'outer',
38
+ traceId: wire.traceId,
39
+ wireType: wire.wireType,
40
+ wireId: wire.wireId,
41
+ totalDuration: Math.round(performance.now() - start),
42
+ outcome,
43
+ ...(errorMessage ? { errorMessage } : {}),
44
+ ...(wire.http
45
+ ? {
46
+ httpStatus: wire.http.response?.statusCode,
47
+ httpMethod: wire.http.request?.method(),
48
+ httpPath: wire.http.request?.path(),
49
+ }
50
+ : {}),
51
+ ...(environmentId ? { environmentId } : {}),
52
+ ...(orgId ? { orgId } : {}),
53
+ });
54
+ }
55
+ },
56
+ });
57
+ });
58
+ /**
59
+ * Inner telemetry middleware that captures function execution duration and user context.
60
+ * Runs at `lowest` priority (innermost, closest to the function).
61
+ *
62
+ * Emits a structured JSON log entry with `__pikku_telemetry: 'end'` containing:
63
+ * - Function-only duration (excluding outer middleware like auth)
64
+ * - User identity (pikkuUserId) if authenticated
65
+ * - Wire metadata (wireType, wireId, traceId)
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * import { telemetryInner } from '@pikku/core/middleware'
70
+ * addMiddleware('myTag', [telemetryInner()])
71
+ * ```
72
+ */
73
+ export const telemetryInner = pikkuMiddlewareFactory(({ environmentId, orgId } = {}) => {
74
+ return pikkuMiddleware({
75
+ name: 'telemetry-inner',
76
+ priority: 'lowest',
77
+ func: async (services, wire, next) => {
78
+ const start = performance.now();
79
+ let outcome = 'ok';
80
+ let errorMessage;
81
+ try {
82
+ await next();
83
+ }
84
+ catch (e) {
85
+ outcome = 'error';
86
+ errorMessage = e instanceof Error ? e.message : String(e);
87
+ throw e;
88
+ }
89
+ finally {
90
+ services.logger.info({
91
+ __pikku_telemetry: 'end',
92
+ __pikku_layer: 'inner',
93
+ traceId: wire.traceId,
94
+ wireType: wire.wireType,
95
+ wireId: wire.wireId,
96
+ functionDuration: Math.round(performance.now() - start),
97
+ outcome,
98
+ pikkuUserId: wire.pikkuUserId,
99
+ ...(errorMessage ? { errorMessage } : {}),
100
+ ...(environmentId ? { environmentId } : {}),
101
+ ...(orgId ? { orgId } : {}),
102
+ });
103
+ }
104
+ },
105
+ });
106
+ });
@@ -1,5 +1,19 @@
1
1
  import { pikkuState } from './pikku-state.js';
2
2
  import { freezeDedupe, getTagGroups } from './utils.js';
3
+ const PRIORITY_ORDER = {
4
+ highest: 0,
5
+ high: 1,
6
+ medium: 2,
7
+ low: 3,
8
+ lowest: 4,
9
+ };
10
+ const getMiddlewarePriority = (fn) => {
11
+ const priority = fn.__priority;
12
+ return PRIORITY_ORDER[priority ?? 'medium'];
13
+ };
14
+ const sortByPriority = (middlewares) => {
15
+ return middlewares.sort((a, b) => getMiddlewarePriority(a) - getMiddlewarePriority(b));
16
+ };
3
17
  /**
4
18
  * Runs a chain of middleware functions in sequence before executing the main function.
5
19
  *
@@ -18,11 +32,14 @@ import { freezeDedupe, getTagGroups } from './utils.js';
18
32
  * );
19
33
  */
20
34
  export const runMiddleware = async (services, wire, middlewares, main) => {
21
- // Deduplicate middleware using Set to avoid running the same middleware multiple times
35
+ // Sort by priority if not already sorted (cached middleware is pre-sorted)
36
+ const sorted = isSortedByPriority(middlewares)
37
+ ? middlewares
38
+ : [...middlewares].sort((a, b) => getMiddlewarePriority(a) - getMiddlewarePriority(b));
22
39
  let result;
23
40
  const dispatch = async (index) => {
24
- if (middlewares && index < middlewares.length) {
25
- return await middlewares[index](services, wire, () => dispatch(index + 1));
41
+ if (sorted && index < sorted.length) {
42
+ return await sorted[index](services, wire, () => dispatch(index + 1));
26
43
  }
27
44
  else if (main) {
28
45
  result = await main();
@@ -31,6 +48,15 @@ export const runMiddleware = async (services, wire, middlewares, main) => {
31
48
  await dispatch(0);
32
49
  return result;
33
50
  };
51
+ const isSortedByPriority = (middlewares) => {
52
+ for (let i = 1; i < middlewares.length; i++) {
53
+ if (getMiddlewarePriority(middlewares[i]) <
54
+ getMiddlewarePriority(middlewares[i - 1])) {
55
+ return false;
56
+ }
57
+ }
58
+ return true;
59
+ };
34
60
  /**
35
61
  * Registers global middleware for a specific tag.
36
62
  *
@@ -176,7 +202,8 @@ export const combineMiddleware = (wireType, uid, { wireInheritedMiddleware, wire
176
202
  if (funcMiddleware) {
177
203
  resolved.push(...funcMiddleware);
178
204
  }
179
- // Deduplicate and freeze
205
+ // Sort by priority, deduplicate, and freeze
206
+ sortByPriority(resolved);
180
207
  middlewareCache[wireType][uid] = freezeDedupe(resolved);
181
208
  return middlewareCache[wireType][uid];
182
209
  };
@@ -224,6 +224,17 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
224
224
  * A function that can wrap an wire and be called before or after
225
225
  */
226
226
  export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices, wires: PikkuWire<unknown, unknown, false, UserSession>, next: () => Promise<void>) => Promise<void>;
227
+ /**
228
+ * Priority levels for middleware execution order.
229
+ * Lower priority runs first (outermost in the onion model).
230
+ *
231
+ * - `highest` — Runs first (outermost). Use for telemetry, request tracing.
232
+ * - `high` — Runs early. Use for CORS, rate limiting.
233
+ * - `medium` — Default. Use for auth, most user middleware.
234
+ * - `low` — Runs late. Use for post-auth processing.
235
+ * - `lowest` — Runs last (innermost, closest to function). Use for inner telemetry.
236
+ */
237
+ export type MiddlewarePriority = 'highest' | 'high' | 'medium' | 'low' | 'lowest';
227
238
  /**
228
239
  * Configuration object for creating middleware with metadata
229
240
  *
@@ -237,6 +248,8 @@ export type CorePikkuMiddlewareConfig<SingletonServices extends CoreSingletonSer
237
248
  name?: string;
238
249
  /** Optional description of what the middleware does */
239
250
  description?: string;
251
+ /** Execution priority. Lower runs first (outermost). Defaults to 'medium'. */
252
+ priority?: MiddlewarePriority;
240
253
  };
241
254
  /**
242
255
  * A factory function that takes input and returns middleware
@@ -24,7 +24,13 @@
24
24
  * ```
25
25
  */
26
26
  export const pikkuMiddleware = (middleware) => {
27
- return typeof middleware === 'function' ? middleware : middleware.func;
27
+ if (typeof middleware === 'function')
28
+ return middleware;
29
+ const func = middleware.func;
30
+ if (middleware.priority) {
31
+ func.__priority = middleware.priority;
32
+ }
33
+ return func;
28
34
  };
29
35
  /**
30
36
  * Factory function for creating middleware factories
@@ -323,8 +323,9 @@ export async function executeCLI({ programName, args, createConfig, createSingle
323
323
  const config = createConfig
324
324
  ? await createConfig(new LocalVariablesService(), data)
325
325
  : {};
326
- // Create services with config
326
+ // Create services with config and register in global state
327
327
  const singletonServices = await createSingletonServices(config);
328
+ pikkuState(null, 'package', 'singletonServices', singletonServices);
328
329
  // Execute the command
329
330
  await runCLICommand({
330
331
  program: programName,
@@ -167,6 +167,7 @@ export interface PikkuHTTPRequest<In = unknown> {
167
167
  query(): PikkuQuery;
168
168
  }
169
169
  export interface PikkuHTTPResponse<Out = unknown> {
170
+ readonly statusCode: number;
170
171
  status(code: number): this;
171
172
  cookie(name: string, value: string | null, options: SerializeOptions): this;
172
173
  header(name: string, value: string | string[]): this;
@@ -3,6 +3,7 @@ import type { SerializeOptions as CookieSerializeOptions } from 'cookie';
3
3
  export declare class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
4
4
  #private;
5
5
  setMode(mode: 'stream'): void;
6
+ get statusCode(): number;
6
7
  status(code: number): this;
7
8
  cookie(name: string, value: string, flags: CookieSerializeOptions): this;
8
9
  header(name: string, value: string | string[]): this;
@@ -13,6 +13,9 @@ export class PikkuFetchHTTPResponse {
13
13
  this.#body = this.createStream();
14
14
  }
15
15
  }
16
+ get statusCode() {
17
+ return this.#statusCode;
18
+ }
16
19
  status(code) {
17
20
  this.#statusCode = code;
18
21
  return this;
@@ -80,7 +80,7 @@ export type CoreMCPResource<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePi
80
80
  export type CoreMCPTool<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>, PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
81
81
  name: string;
82
82
  title?: string;
83
- description: string;
83
+ description?: string;
84
84
  summary?: string;
85
85
  errors?: string[];
86
86
  func: PikkuFunctionConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.12.15",
3
+ "version": "0.12.16",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export type {
22
22
  JSONValue,
23
23
  MakeRequired,
24
24
  MiddlewareMetadata,
25
+ MiddlewarePriority,
25
26
  PermissionMetadata,
26
27
  PickOptional,
27
28
  PickRequired,
@@ -3,5 +3,6 @@ export { authCookie } from './auth-cookie.js'
3
3
  export { authBearer } from './auth-bearer.js'
4
4
  export { pikkuRemoteAuthMiddleware } from './remote-auth.js'
5
5
  export { cors } from './cors.js'
6
+ export { telemetryOuter, telemetryInner } from './telemetry.js'
6
7
  export { addMiddleware, runMiddleware } from '../middleware-runner.js'
7
8
  export { addPermission } from '../permissions.js'
@@ -0,0 +1,110 @@
1
+ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
2
+
3
+ /**
4
+ * Outer telemetry middleware that captures total request duration and outcome.
5
+ * Runs at `highest` priority (outermost in the middleware chain).
6
+ *
7
+ * Emits a structured JSON log entry with `__pikku_telemetry: 'end'` containing:
8
+ * - Total duration (including all middleware)
9
+ * - Outcome (ok/error)
10
+ * - Wire metadata (wireType, wireId, traceId)
11
+ * - HTTP details (method, path, status) if applicable
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { telemetryOuter } from '@pikku/core/middleware'
16
+ * addMiddleware('myTag', [telemetryOuter()])
17
+ * ```
18
+ */
19
+ export const telemetryOuter = pikkuMiddlewareFactory<{
20
+ environmentId?: string
21
+ orgId?: string
22
+ } | void>(({ environmentId, orgId } = {}) => {
23
+ return pikkuMiddleware({
24
+ name: 'telemetry-outer',
25
+ priority: 'highest',
26
+ func: async (services, wire, next) => {
27
+ const start = performance.now()
28
+ let outcome = 'ok'
29
+ let errorMessage: string | undefined
30
+ try {
31
+ await next()
32
+ } catch (e) {
33
+ outcome = 'error'
34
+ errorMessage = e instanceof Error ? e.message : String(e)
35
+ throw e
36
+ } finally {
37
+ services.logger.info({
38
+ __pikku_telemetry: 'end',
39
+ __pikku_layer: 'outer',
40
+ traceId: wire.traceId,
41
+ wireType: wire.wireType,
42
+ wireId: wire.wireId,
43
+ totalDuration: Math.round(performance.now() - start),
44
+ outcome,
45
+ ...(errorMessage ? { errorMessage } : {}),
46
+ ...(wire.http
47
+ ? {
48
+ httpStatus: wire.http.response?.statusCode,
49
+ httpMethod: wire.http.request?.method(),
50
+ httpPath: wire.http.request?.path(),
51
+ }
52
+ : {}),
53
+ ...(environmentId ? { environmentId } : {}),
54
+ ...(orgId ? { orgId } : {}),
55
+ })
56
+ }
57
+ },
58
+ })
59
+ })
60
+
61
+ /**
62
+ * Inner telemetry middleware that captures function execution duration and user context.
63
+ * Runs at `lowest` priority (innermost, closest to the function).
64
+ *
65
+ * Emits a structured JSON log entry with `__pikku_telemetry: 'end'` containing:
66
+ * - Function-only duration (excluding outer middleware like auth)
67
+ * - User identity (pikkuUserId) if authenticated
68
+ * - Wire metadata (wireType, wireId, traceId)
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * import { telemetryInner } from '@pikku/core/middleware'
73
+ * addMiddleware('myTag', [telemetryInner()])
74
+ * ```
75
+ */
76
+ export const telemetryInner = pikkuMiddlewareFactory<{
77
+ environmentId?: string
78
+ orgId?: string
79
+ } | void>(({ environmentId, orgId } = {}) => {
80
+ return pikkuMiddleware({
81
+ name: 'telemetry-inner',
82
+ priority: 'lowest',
83
+ func: async (services, wire, next) => {
84
+ const start = performance.now()
85
+ let outcome = 'ok'
86
+ let errorMessage: string | undefined
87
+ try {
88
+ await next()
89
+ } catch (e) {
90
+ outcome = 'error'
91
+ errorMessage = e instanceof Error ? e.message : String(e)
92
+ throw e
93
+ } finally {
94
+ services.logger.info({
95
+ __pikku_telemetry: 'end',
96
+ __pikku_layer: 'inner',
97
+ traceId: wire.traceId,
98
+ wireType: wire.wireType,
99
+ wireId: wire.wireId,
100
+ functionDuration: Math.round(performance.now() - start),
101
+ outcome,
102
+ pikkuUserId: wire.pikkuUserId,
103
+ ...(errorMessage ? { errorMessage } : {}),
104
+ ...(environmentId ? { environmentId } : {}),
105
+ ...(orgId ? { orgId } : {}),
106
+ })
107
+ }
108
+ },
109
+ })
110
+ })
@@ -6,7 +6,27 @@ import {
6
6
  runMiddleware,
7
7
  } from './middleware-runner.js'
8
8
  import { resetPikkuState } from './pikku-state.js'
9
- import type { CorePikkuMiddleware } from './types/core.types.js'
9
+ import type {
10
+ CorePikkuMiddleware,
11
+ MiddlewarePriority,
12
+ } from './types/core.types.js'
13
+ import { pikkuMiddleware } from './types/core.types.js'
14
+
15
+ const withPriority = (
16
+ name: string,
17
+ priority: MiddlewarePriority,
18
+ log: string[]
19
+ ): CorePikkuMiddleware => {
20
+ return pikkuMiddleware({
21
+ name,
22
+ priority,
23
+ func: async (_services, _wire, next) => {
24
+ log.push(`start:${name}`)
25
+ await next()
26
+ log.push(`end:${name}`)
27
+ },
28
+ })
29
+ }
10
30
 
11
31
  beforeEach(() => {
12
32
  resetPikkuState()
@@ -329,6 +349,56 @@ describe('combineMiddleware', () => {
329
349
  assert.equal(result.length, 1)
330
350
  assert.equal(result[0], middleware)
331
351
  })
352
+
353
+ test('should sort middleware by priority', () => {
354
+ const log: string[] = []
355
+ const low = withPriority('low', 'low', log)
356
+ const highest = withPriority('highest', 'highest', log)
357
+ const medium = withPriority('medium', 'medium', log)
358
+
359
+ const result = combineMiddleware('http', Math.random().toString(), {
360
+ wireMiddleware: [low, highest, medium],
361
+ })
362
+
363
+ assert.equal(result.length, 3)
364
+ assert.equal(result[0], highest)
365
+ assert.equal(result[1], medium)
366
+ assert.equal(result[2], low)
367
+ })
368
+
369
+ test('should default unprioritized middleware to medium', () => {
370
+ const log: string[] = []
371
+ const highest = withPriority('highest', 'highest', log)
372
+ const lowest = withPriority('lowest', 'lowest', log)
373
+ const noPriority: CorePikkuMiddleware = async (_services, _wire, next) => {
374
+ await next()
375
+ }
376
+
377
+ const result = combineMiddleware('http', Math.random().toString(), {
378
+ wireMiddleware: [lowest, noPriority, highest],
379
+ })
380
+
381
+ assert.equal(result.length, 3)
382
+ assert.equal(result[0], highest)
383
+ assert.equal(result[1], noPriority) // defaults to medium
384
+ assert.equal(result[2], lowest)
385
+ })
386
+
387
+ test('should preserve registration order within same priority', () => {
388
+ const log: string[] = []
389
+ const first = withPriority('first', 'medium', log)
390
+ const second = withPriority('second', 'medium', log)
391
+ const third = withPriority('third', 'medium', log)
392
+
393
+ const result = combineMiddleware('http', Math.random().toString(), {
394
+ wireMiddleware: [first, second, third],
395
+ })
396
+
397
+ assert.equal(result.length, 3)
398
+ assert.equal(result[0], first)
399
+ assert.equal(result[1], second)
400
+ assert.equal(result[2], third)
401
+ })
332
402
  })
333
403
 
334
404
  describe('runMiddleware', () => {
@@ -415,4 +485,82 @@ describe('runMiddleware', () => {
415
485
 
416
486
  assert.deepEqual(executionOrder, ['middleware'])
417
487
  })
488
+
489
+ test('should execute middleware in priority order (highest first, lowest last)', async () => {
490
+ const log: string[] = []
491
+ const low = withPriority('low', 'low', log)
492
+ const highest = withPriority('highest', 'highest', log)
493
+ const medium = withPriority('medium', 'medium', log)
494
+
495
+ await runMiddleware(
496
+ {} as any,
497
+ {} as any,
498
+ [low, highest, medium],
499
+ async () => {
500
+ log.push('main')
501
+ }
502
+ )
503
+
504
+ assert.deepEqual(log, [
505
+ 'start:highest',
506
+ 'start:medium',
507
+ 'start:low',
508
+ 'main',
509
+ 'end:low',
510
+ 'end:medium',
511
+ 'end:highest',
512
+ ])
513
+ })
514
+
515
+ test('should sort unsorted middleware passed directly to runMiddleware', async () => {
516
+ const log: string[] = []
517
+ const lowest = withPriority('lowest', 'lowest', log)
518
+ const highest = withPriority('highest', 'highest', log)
519
+
520
+ await runMiddleware({} as any, {} as any, [lowest, highest], async () => {
521
+ log.push('main')
522
+ })
523
+
524
+ // highest should run first (outermost), lowest last (innermost)
525
+ assert.deepEqual(log, [
526
+ 'start:highest',
527
+ 'start:lowest',
528
+ 'main',
529
+ 'end:lowest',
530
+ 'end:highest',
531
+ ])
532
+ })
533
+
534
+ test('should handle all five priority levels in correct order', async () => {
535
+ const log: string[] = []
536
+ const lowest = withPriority('lowest', 'lowest', log)
537
+ const low = withPriority('low', 'low', log)
538
+ const medium = withPriority('medium', 'medium', log)
539
+ const high = withPriority('high', 'high', log)
540
+ const highest = withPriority('highest', 'highest', log)
541
+
542
+ // Pass in reverse order to verify sorting
543
+ await runMiddleware(
544
+ {} as any,
545
+ {} as any,
546
+ [lowest, low, medium, high, highest],
547
+ async () => {
548
+ log.push('main')
549
+ }
550
+ )
551
+
552
+ assert.deepEqual(log, [
553
+ 'start:highest',
554
+ 'start:high',
555
+ 'start:medium',
556
+ 'start:low',
557
+ 'start:lowest',
558
+ 'main',
559
+ 'end:lowest',
560
+ 'end:low',
561
+ 'end:medium',
562
+ 'end:high',
563
+ 'end:highest',
564
+ ])
565
+ })
418
566
  })