@pikku/core 0.12.15 → 0.12.17
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 +34 -0
- package/dist/function/index.d.ts +1 -0
- package/dist/function/list.types.d.ts +113 -0
- package/dist/function/list.types.js +28 -0
- package/dist/index.d.ts +2 -1
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/telemetry.d.ts +47 -0
- package/dist/middleware/telemetry.js +106 -0
- package/dist/middleware-runner.js +31 -4
- package/dist/types/core.types.d.ts +13 -0
- package/dist/types/core.types.js +7 -1
- package/dist/wirings/cli/cli-runner.js +2 -1
- package/dist/wirings/http/http.types.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-response.js +3 -0
- package/dist/wirings/mcp/mcp.types.d.ts +1 -1
- package/package.json +1 -1
- package/src/function/index.ts +7 -0
- package/src/function/list.types.test.ts +122 -0
- package/src/function/list.types.ts +121 -0
- package/src/index.ts +8 -0
- package/src/middleware/index.ts +1 -0
- package/src/middleware/telemetry.ts +110 -0
- package/src/middleware-runner.test.ts +149 -1
- package/src/middleware-runner.ts +48 -4
- package/src/types/core.types.ts +28 -1
- package/src/wirings/channel/local/local-channel-runner.test.ts +5 -1
- package/src/wirings/cli/cli-runner.ts +2 -1
- package/src/wirings/http/http.types.ts +1 -0
- package/src/wirings/http/pikku-fetch-http-response.ts +4 -0
- package/src/wirings/mcp/mcp.types.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
## 0.12.4
|
|
2
2
|
|
|
3
|
+
## 0.12.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 854737b: Add `ListInput<F, S>` / `ListOutput<Row>` / `Filter<F>` types for list-function primitives.
|
|
8
|
+
|
|
9
|
+
A "list function" is any Pikku function that returns a paginated collection. Adopting this shape unlocks a shared vocabulary across MCP tools, AI agents, typed RPC clients, and widget libraries — they all reason about cursor, filter, sort, and search uniformly.
|
|
10
|
+
|
|
11
|
+
These are purely structural constraints; no runtime behaviour change. A list function is still a normal `pikkuFunc` whose input extends `ListInput<F, S>` and output extends `ListOutput<Row>`.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { pikkuFunc } from '#pikku'
|
|
15
|
+
import type { ListInput, ListOutput } from '@pikku/core'
|
|
16
|
+
|
|
17
|
+
export const listSessions = pikkuFunc<
|
|
18
|
+
ListInput<{ status?: SessionStatus[] }, 'user' | 'status' | 'uploaded_at'>,
|
|
19
|
+
ListOutput<Session>
|
|
20
|
+
>({
|
|
21
|
+
func: async ({ kysely }, input) => {
|
|
22
|
+
/* ... */
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`Filter<F>` is a recursive AND/OR tree: arrays are AND of children, objects with label keys are OR of children, single-key objects with a field name from `F` are leaf predicates. Leaf operators mirror Prisma's vocabulary (`equals`, `in`, `notIn`, `gt`, `gte`, `lt`, `lte`, `contains`, `startsWith`, `endsWith`, `not`, `mode`).
|
|
28
|
+
|
|
29
|
+
Follow-ups (separate PRs): `applyFilter<DB>(qb, filter)` Kysely helper, `usePikkuListQuery` in the CLI's react-query generator, first-class MCP list-tool shape.
|
|
30
|
+
|
|
31
|
+
## 0.12.16
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- 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.
|
|
36
|
+
|
|
3
37
|
## 0.12.15
|
|
4
38
|
|
|
5
39
|
### Patch Changes
|
package/dist/function/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { addFunction, getAllFunctionNames } from './function-runner.js';
|
|
2
2
|
export { pikkuAuth, pikkuPermission, pikkuPermissionFactory, pikkuApprovalDescription, } from './functions.types.js';
|
|
3
3
|
export type { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuFunctionConfig, CorePikkuAuth, CorePikkuAuthConfig, CorePikkuPermission, } from './functions.types.js';
|
|
4
|
+
export type { ListInput, ListOutput, Filter, LeafFilter, LeafValue, } from './list.types.js';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List-function primitives.
|
|
3
|
+
*
|
|
4
|
+
* A "list function" is any Pikku function that returns a paginated
|
|
5
|
+
* collection. Adopting this shape unlocks a shared vocabulary across
|
|
6
|
+
* MCP tools, AI agents, typed RPC clients, and widget libraries — they
|
|
7
|
+
* all reason about cursor, filter, sort, and search uniformly.
|
|
8
|
+
*
|
|
9
|
+
* Under the hood, a list function is still a normal `pikkuFunc`. These
|
|
10
|
+
* types are purely structural constraints; no runtime behaviour change.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { pikkuFunc } from '#pikku'
|
|
15
|
+
* import type { ListInput, ListOutput } from '@pikku/core'
|
|
16
|
+
*
|
|
17
|
+
* export const listSessions = pikkuFunc<
|
|
18
|
+
* ListInput<{ status?: SessionStatus[] }, 'user' | 'status' | 'uploaded_at'>,
|
|
19
|
+
* ListOutput<Session>
|
|
20
|
+
* >({
|
|
21
|
+
* func: async ({ kysely }, input) => {
|
|
22
|
+
* // input.sort / input.filter / input.cursor / input.search are typed
|
|
23
|
+
* return { rows, nextCursor, totalCount }
|
|
24
|
+
* },
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Shape every list function accepts as input.
|
|
30
|
+
*
|
|
31
|
+
* @template F - User-defined filter shape. Each key is a filterable field.
|
|
32
|
+
* @template S - String union of sortable column names.
|
|
33
|
+
*/
|
|
34
|
+
export interface ListInput<F extends Record<string, unknown> = Record<string, never>, S extends string = never> {
|
|
35
|
+
/** Opaque cursor from the previous page's `nextCursor`. */
|
|
36
|
+
cursor?: string;
|
|
37
|
+
/** Page size. Server may cap. */
|
|
38
|
+
limit?: number;
|
|
39
|
+
/** Ordered sort criteria — first entry is primary. */
|
|
40
|
+
sort?: Array<{
|
|
41
|
+
column: S;
|
|
42
|
+
direction: 'asc' | 'desc';
|
|
43
|
+
}>;
|
|
44
|
+
/** Structured filter tree. See {@link Filter}. */
|
|
45
|
+
filter?: Filter<F>;
|
|
46
|
+
/** Unstructured text search across server-configured fields. */
|
|
47
|
+
search?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Shape every list function returns.
|
|
51
|
+
*/
|
|
52
|
+
export interface ListOutput<Row> {
|
|
53
|
+
rows: Row[];
|
|
54
|
+
/** Null when no more pages. */
|
|
55
|
+
nextCursor: string | null;
|
|
56
|
+
/** Optional — backend may skip when expensive. */
|
|
57
|
+
totalCount?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Leaf predicate value on a single field.
|
|
61
|
+
*
|
|
62
|
+
* Operator keywords (`equals`, `not`, `in`, `notIn`, `gt`, `gte`, `lt`,
|
|
63
|
+
* `lte`, `contains`, `startsWith`, `endsWith`, `mode`) mirror Prisma's
|
|
64
|
+
* vocabulary for dev familiarity. These keys are reserved and cannot be
|
|
65
|
+
* used as user field names in a Filter's `F` type.
|
|
66
|
+
*/
|
|
67
|
+
export type LeafValue<T> = T | null | T[] | {
|
|
68
|
+
equals?: T | null;
|
|
69
|
+
not?: T | null | LeafValue<T>;
|
|
70
|
+
in?: T[];
|
|
71
|
+
notIn?: T[];
|
|
72
|
+
gt?: T;
|
|
73
|
+
gte?: T;
|
|
74
|
+
lt?: T;
|
|
75
|
+
lte?: T;
|
|
76
|
+
contains?: string;
|
|
77
|
+
startsWith?: string;
|
|
78
|
+
endsWith?: string;
|
|
79
|
+
mode?: 'sensitive' | 'insensitive';
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A single-key object keyed by a field name from F, value is a leaf
|
|
83
|
+
* predicate. Single-key so the runtime discriminator is unambiguous:
|
|
84
|
+
* multi-key objects are OR groups.
|
|
85
|
+
*/
|
|
86
|
+
export type LeafFilter<F extends Record<string, unknown>> = {
|
|
87
|
+
[K in keyof F]: {
|
|
88
|
+
[Key in K]: LeafValue<F[K]>;
|
|
89
|
+
};
|
|
90
|
+
}[keyof F];
|
|
91
|
+
/**
|
|
92
|
+
* Recursive filter tree.
|
|
93
|
+
*
|
|
94
|
+
* - `Array<Filter<F>>` — AND of children.
|
|
95
|
+
* - `{ [label: string]: Filter<F> }` — OR of children. Keys are arbitrary
|
|
96
|
+
* labels (unique strings), ignored at evaluation time.
|
|
97
|
+
* - `LeafFilter<F>` — single-key predicate on a declared field.
|
|
98
|
+
*
|
|
99
|
+
* @example "status = pending AND (therapist = kim OR therapist = park) AND uploaded_at > 2026-04-10"
|
|
100
|
+
* ```ts
|
|
101
|
+
* const filter: Filter<{ status: string; therapistId: string; uploadedAt: string }> = [
|
|
102
|
+
* { status: 'pending' },
|
|
103
|
+
* {
|
|
104
|
+
* kim: { therapistId: 'kim' },
|
|
105
|
+
* park: { therapistId: 'park' },
|
|
106
|
+
* },
|
|
107
|
+
* { uploadedAt: { gt: '2026-04-10' } },
|
|
108
|
+
* ]
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export type Filter<F extends Record<string, unknown>> = LeafFilter<F> | Filter<F>[] | {
|
|
112
|
+
[label: string]: Filter<F>;
|
|
113
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List-function primitives.
|
|
3
|
+
*
|
|
4
|
+
* A "list function" is any Pikku function that returns a paginated
|
|
5
|
+
* collection. Adopting this shape unlocks a shared vocabulary across
|
|
6
|
+
* MCP tools, AI agents, typed RPC clients, and widget libraries — they
|
|
7
|
+
* all reason about cursor, filter, sort, and search uniformly.
|
|
8
|
+
*
|
|
9
|
+
* Under the hood, a list function is still a normal `pikkuFunc`. These
|
|
10
|
+
* types are purely structural constraints; no runtime behaviour change.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { pikkuFunc } from '#pikku'
|
|
15
|
+
* import type { ListInput, ListOutput } from '@pikku/core'
|
|
16
|
+
*
|
|
17
|
+
* export const listSessions = pikkuFunc<
|
|
18
|
+
* ListInput<{ status?: SessionStatus[] }, 'user' | 'status' | 'uploaded_at'>,
|
|
19
|
+
* ListOutput<Session>
|
|
20
|
+
* >({
|
|
21
|
+
* func: async ({ kysely }, input) => {
|
|
22
|
+
* // input.sort / input.filter / input.cursor / input.search are typed
|
|
23
|
+
* return { rows, nextCursor, totalCount }
|
|
24
|
+
* },
|
|
25
|
+
* })
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
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';
|
|
8
8
|
export { addFunction, getAllFunctionNames } from './function/index.js';
|
|
9
|
+
export type { ListInput, ListOutput, Filter, LeafFilter, LeafValue, } from './function/list.types.js';
|
|
9
10
|
export { PikkuRequest } from './pikku-request.js';
|
|
10
11
|
export { getRelativeTimeOffsetFromNow, parseDurationString, } from './time-utils.js';
|
|
11
12
|
export type { RelativeTimeInput } from './time-utils.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';
|
package/dist/middleware/index.js
CHANGED
|
@@ -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
|
-
//
|
|
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 (
|
|
25
|
-
return await
|
|
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
|
-
//
|
|
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
|
package/dist/types/core.types.js
CHANGED
|
@@ -24,7 +24,13 @@
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
export const pikkuMiddleware = (middleware) => {
|
|
27
|
-
|
|
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;
|
|
@@ -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
|
|
83
|
+
description?: string;
|
|
84
84
|
summary?: string;
|
|
85
85
|
errors?: string[];
|
|
86
86
|
func: PikkuFunctionConfig;
|
package/package.json
CHANGED