@pikku/core 0.9.4 → 0.9.6
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 +28 -0
- package/dist/function/function-runner.d.ts +2 -2
- package/dist/function/function-runner.js +22 -25
- package/dist/function/functions.types.d.ts +4 -0
- package/dist/function/functions.types.js +6 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/middleware-runner.d.ts +22 -13
- package/dist/middleware-runner.js +44 -16
- package/dist/permissions.d.ts +20 -4
- package/dist/permissions.js +84 -2
- package/dist/pikku-state.d.ts +4 -6
- package/dist/pikku-state.js +6 -5
- package/dist/schema.js +8 -6
- package/dist/types/core.types.d.ts +7 -4
- package/dist/types/core.types.js +6 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -0
- package/dist/wirings/channel/channel-handler.js +2 -1
- package/dist/wirings/channel/channel-runner.d.ts +2 -0
- package/dist/wirings/channel/channel-runner.js +23 -25
- package/dist/wirings/channel/local/local-channel-runner.js +9 -4
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +8 -3
- package/dist/wirings/http/http-runner.js +44 -54
- package/dist/wirings/http/http.types.d.ts +5 -5
- package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
- package/dist/wirings/http/routers/http-router.d.ts +12 -0
- package/dist/wirings/http/routers/http-router.js +2 -0
- package/dist/wirings/http/routers/path-to-regex.d.ts +10 -0
- package/dist/wirings/http/routers/path-to-regex.js +118 -0
- package/dist/wirings/mcp/mcp-runner.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.js +10 -4
- package/dist/wirings/queue/queue-runner.js +11 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +1 -2
- package/dist/wirings/rpc/rpc-runner.js +2 -1
- package/dist/wirings/scheduler/scheduler-runner.d.ts +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +10 -4
- package/package.json +1 -1
- package/src/factory-functions.test.ts +37 -0
- package/src/function/function-runner.test.ts +499 -0
- package/src/function/function-runner.ts +26 -43
- package/src/function/functions.types.ts +13 -0
- package/src/index.ts +3 -7
- package/src/middleware-runner.test.ts +357 -0
- package/src/middleware-runner.ts +68 -21
- package/src/permissions.test.ts +407 -42
- package/src/permissions.ts +159 -7
- package/src/pikku-state.ts +14 -8
- package/src/schema.ts +8 -6
- package/src/types/core.types.ts +15 -4
- package/src/utils.ts +19 -0
- package/src/wirings/channel/channel-handler.ts +17 -11
- package/src/wirings/channel/channel-runner.ts +31 -25
- package/src/wirings/channel/local/local-channel-runner.test.ts +4 -0
- package/src/wirings/channel/local/local-channel-runner.ts +9 -8
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +17 -15
- package/src/wirings/http/http-runner.test.ts +18 -6
- package/src/wirings/http/http-runner.ts +64 -75
- package/src/wirings/http/http.types.ts +5 -5
- package/src/wirings/http/pikku-fetch-http-request.ts +3 -0
- package/src/wirings/http/routers/http-router.ts +15 -0
- package/src/wirings/http/routers/path-to-regex.test.ts +309 -0
- package/src/wirings/http/routers/path-to-regex.ts +162 -0
- package/src/wirings/mcp/mcp-runner.ts +25 -14
- package/src/wirings/queue/queue-runner.ts +26 -8
- package/src/wirings/rpc/rpc-runner.ts +21 -16
- package/src/wirings/scheduler/scheduler-runner.ts +27 -14
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/pikku-function.d.ts +0 -1
- package/dist/pikku-function.js +0 -1
- package/src/pikku-function.ts +0 -1
package/src/pikku-state.ts
CHANGED
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
ScheduledTasksMeta,
|
|
11
11
|
} from './wirings/scheduler/scheduler.types.js'
|
|
12
12
|
import { ErrorDetails, PikkuError } from './errors/error-handler.js'
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CorePermissionGroup,
|
|
15
|
+
CorePikkuFunctionConfig,
|
|
16
|
+
CorePikkuPermission,
|
|
17
|
+
} from './function/functions.types.js'
|
|
14
18
|
import {
|
|
15
19
|
queueWorkersMeta,
|
|
16
20
|
CoreQueueWorker,
|
|
@@ -40,7 +44,8 @@ interface PikkuState {
|
|
|
40
44
|
>
|
|
41
45
|
}
|
|
42
46
|
http: {
|
|
43
|
-
middleware:
|
|
47
|
+
middleware: Map<string, CorePikkuMiddleware<any, any>[]>
|
|
48
|
+
permissions: Map<string, CorePermissionGroup | CorePikkuPermission[]>
|
|
44
49
|
routes: Map<HTTPMethod, Map<string, CoreHTTPFunctionWiring<any, any, any>>>
|
|
45
50
|
meta: HTTPWiringsMeta
|
|
46
51
|
}
|
|
@@ -68,7 +73,7 @@ interface PikkuState {
|
|
|
68
73
|
errors: Map<PikkuError, ErrorDetails>
|
|
69
74
|
schemas: Map<string, any>
|
|
70
75
|
middleware: Record<string, CorePikkuMiddleware[]>
|
|
71
|
-
permissions: Record<string,
|
|
76
|
+
permissions: Record<string, CorePermissionGroup | CorePikkuPermission[]>
|
|
72
77
|
}
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -83,9 +88,10 @@ export const resetPikkuState = () => {
|
|
|
83
88
|
files: new Map(),
|
|
84
89
|
},
|
|
85
90
|
http: {
|
|
86
|
-
|
|
91
|
+
permissions: new Map(),
|
|
92
|
+
middleware: new Map(),
|
|
87
93
|
routes: new Map(),
|
|
88
|
-
meta:
|
|
94
|
+
meta: {},
|
|
89
95
|
},
|
|
90
96
|
channel: {
|
|
91
97
|
channels: new Map(),
|
|
@@ -109,9 +115,9 @@ export const resetPikkuState = () => {
|
|
|
109
115
|
},
|
|
110
116
|
misc: {
|
|
111
117
|
errors: globalThis.pikkuState?.misc?.errors || new Map(),
|
|
112
|
-
schemas:
|
|
113
|
-
middleware:
|
|
114
|
-
permissions:
|
|
118
|
+
schemas: new Map(),
|
|
119
|
+
middleware: {},
|
|
120
|
+
permissions: {},
|
|
115
121
|
},
|
|
116
122
|
} as PikkuState
|
|
117
123
|
}
|
package/src/schema.ts
CHANGED
|
@@ -40,13 +40,15 @@ const validateAllSchemasLoaded = (
|
|
|
40
40
|
|
|
41
41
|
const missingSchemas: string[] = []
|
|
42
42
|
|
|
43
|
-
for (const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
for (const routePaths of Object.values(routesMeta)) {
|
|
44
|
+
for (const meta of Object.values(routePaths)) {
|
|
45
|
+
const inputs = pikkuState('function', 'meta')[meta.pikkuFuncName]?.inputs
|
|
46
|
+
const input = inputs?.[0]
|
|
47
|
+
if (!input || validators.has(input)) {
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
missingSchemas.push(input)
|
|
48
51
|
}
|
|
49
|
-
missingSchemas.push(input)
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
if (missingSchemas.length > 0) {
|
package/src/types/core.types.ts
CHANGED
|
@@ -114,14 +114,13 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
114
114
|
/**
|
|
115
115
|
* Represents different forms of interaction within Pikku and the outside world.
|
|
116
116
|
*/
|
|
117
|
-
export type PikkuInteraction = Partial<{
|
|
118
|
-
http: PikkuHTTP
|
|
117
|
+
export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
|
|
118
|
+
http: PikkuHTTP<In>
|
|
119
119
|
mcp: PikkuMCP
|
|
120
120
|
rpc: PikkuRPC
|
|
121
|
-
channel: PikkuChannel<unknown,
|
|
121
|
+
channel: PikkuChannel<unknown, Out>
|
|
122
122
|
scheduledTask: PikkuScheduledTask
|
|
123
123
|
queue: PikkuQueue
|
|
124
|
-
s
|
|
125
124
|
}>
|
|
126
125
|
|
|
127
126
|
/**
|
|
@@ -138,6 +137,18 @@ export type CorePikkuMiddleware<
|
|
|
138
137
|
next: () => Promise<void>
|
|
139
138
|
) => Promise<void>
|
|
140
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Factory function for creating middleware with tree-shaking support
|
|
142
|
+
*/
|
|
143
|
+
export const pikkuMiddleware = <
|
|
144
|
+
SingletonServices extends CoreSingletonServices = CoreSingletonServices,
|
|
145
|
+
UserSession extends CoreUserSession = CoreUserSession,
|
|
146
|
+
>(
|
|
147
|
+
middleware: CorePikkuMiddleware<SingletonServices, UserSession>
|
|
148
|
+
): CorePikkuMiddleware<SingletonServices, UserSession> => {
|
|
149
|
+
return middleware
|
|
150
|
+
}
|
|
151
|
+
|
|
141
152
|
/**
|
|
142
153
|
* Represents the core services used by Pikku, including singleton services and the request/response interaction.
|
|
143
154
|
*/
|
package/src/utils.ts
CHANGED
|
@@ -38,3 +38,22 @@ export const isSerializable = (data: any): boolean => {
|
|
|
38
38
|
data instanceof Float64Array
|
|
39
39
|
)
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
const EMPTY_ARRAY = Object.freeze([])
|
|
43
|
+
|
|
44
|
+
export const freezeDedupe = <T extends Function>(
|
|
45
|
+
arr?: readonly T[] | T[] | undefined
|
|
46
|
+
): readonly T[] => {
|
|
47
|
+
if (!arr || arr.length === 0) return EMPTY_ARRAY
|
|
48
|
+
if (arr.length === 1) return Object.freeze([arr[0]!])
|
|
49
|
+
const seen = new Set<T>()
|
|
50
|
+
const out: T[] = []
|
|
51
|
+
for (let i = 0; i < arr.length; i++) {
|
|
52
|
+
const fn = arr[i]!
|
|
53
|
+
if (!seen.has(fn)) {
|
|
54
|
+
seen.add(fn)
|
|
55
|
+
out.push(fn)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze(out)
|
|
59
|
+
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
CoreServices,
|
|
3
3
|
JSONValue,
|
|
4
4
|
CoreUserSession,
|
|
5
|
+
PikkuWiringTypes,
|
|
5
6
|
} from '../../types/core.types.js'
|
|
6
7
|
import {
|
|
7
8
|
ChannelMessageMeta,
|
|
@@ -98,17 +99,22 @@ export const processMessageHandlers = (
|
|
|
98
99
|
const middleware =
|
|
99
100
|
typeof onMessage === 'function' ? [] : onMessage.middleware
|
|
100
101
|
|
|
101
|
-
return await runPikkuFunc(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
return await runPikkuFunc(
|
|
103
|
+
PikkuWiringTypes.channel,
|
|
104
|
+
channelConfig.name,
|
|
105
|
+
pikkuFuncName,
|
|
106
|
+
{
|
|
107
|
+
getAllServices: () => ({
|
|
108
|
+
...services,
|
|
109
|
+
channel: channelHandler.getChannel(),
|
|
110
|
+
}),
|
|
111
|
+
data,
|
|
112
|
+
session,
|
|
113
|
+
permissions,
|
|
114
|
+
middleware,
|
|
115
|
+
tags: channelConfig.tags,
|
|
116
|
+
}
|
|
117
|
+
)
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
const onMessage = async (rawData): Promise<unknown> => {
|
|
@@ -3,13 +3,14 @@ import { addFunction } from '../../function/function-runner.js'
|
|
|
3
3
|
import { pikkuState } from '../../pikku-state.js'
|
|
4
4
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js'
|
|
5
5
|
import { UserSessionService } from '../../services/user-session-service.js'
|
|
6
|
+
import { CorePikkuMiddleware } from '../../types/core.types.js'
|
|
7
|
+
import { httpRouter } from '../http/routers/http-router.js'
|
|
6
8
|
import {
|
|
7
9
|
ChannelMeta,
|
|
8
10
|
CoreChannel,
|
|
9
11
|
RunChannelOptions,
|
|
10
12
|
RunChannelParams,
|
|
11
13
|
} from './channel.types.js'
|
|
12
|
-
import { match } from 'path-to-regexp'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Adds a channel and registers all functions referenced in it using the
|
|
@@ -91,30 +92,33 @@ export const wireChannel = <
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
const getMatchingChannelConfig = (request: string) => {
|
|
95
|
+
const matchedPath = httpRouter.match('get', request)
|
|
96
|
+
if (!matchedPath) {
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const meta = pikkuState('channel', 'meta')
|
|
101
|
+
const channelMeta = Object.values(meta).find(
|
|
102
|
+
(channelConfig) => channelConfig.route === matchedPath.route
|
|
103
|
+
)
|
|
104
|
+
if (!channelMeta) {
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
const channels = pikkuState('channel', 'channels')
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
matchedPath,
|
|
110
|
-
params: matchedPath.params,
|
|
111
|
-
channelConfig,
|
|
112
|
-
schemaName: channelMeta.input,
|
|
113
|
-
meta: channelMeta,
|
|
114
|
-
}
|
|
115
|
-
}
|
|
109
|
+
const channelConfig = channels.get(channelMeta.name)
|
|
110
|
+
if (!channelConfig) {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
matchedPath,
|
|
116
|
+
params: matchedPath.params,
|
|
117
|
+
channelConfig,
|
|
118
|
+
schemaName: channelMeta.input,
|
|
119
|
+
meta: channelMeta,
|
|
120
|
+
httpMiddleware: matchedPath.middleware,
|
|
116
121
|
}
|
|
117
|
-
return null
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
export const openChannel = async ({
|
|
@@ -129,6 +133,7 @@ export const openChannel = async ({
|
|
|
129
133
|
openingData: unknown
|
|
130
134
|
channelConfig: CoreChannel<unknown, any>
|
|
131
135
|
meta: ChannelMeta
|
|
136
|
+
httpMiddleware: CorePikkuMiddleware[] | undefined
|
|
132
137
|
}> => {
|
|
133
138
|
const matchingChannel = getMatchingChannelConfig(route)
|
|
134
139
|
if (!matchingChannel) {
|
|
@@ -136,7 +141,8 @@ export const openChannel = async ({
|
|
|
136
141
|
throw new NotFoundError(`Channel not found: ${route}`)
|
|
137
142
|
}
|
|
138
143
|
|
|
139
|
-
const { params, channelConfig, schemaName, meta } =
|
|
144
|
+
const { params, channelConfig, schemaName, meta, httpMiddleware } =
|
|
145
|
+
matchingChannel
|
|
140
146
|
|
|
141
147
|
const requiresSession = channelConfig.auth !== false
|
|
142
148
|
request?.setParams(params)
|
|
@@ -159,5 +165,5 @@ export const openChannel = async ({
|
|
|
159
165
|
)
|
|
160
166
|
}
|
|
161
167
|
|
|
162
|
-
return { openingData, channelConfig, meta }
|
|
168
|
+
return { openingData, channelConfig, meta, httpMiddleware }
|
|
163
169
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PikkuQuery,
|
|
11
11
|
} from '../../http/http.types.js'
|
|
12
12
|
import { SerializeOptions } from 'cookie'
|
|
13
|
+
import { httpRouter } from '../../http/routers/http-router.js'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Minimal stubs for dependencies that runChannel expects.
|
|
@@ -139,6 +140,9 @@ test('runChannel should return a channel handler if channel matches and no auth
|
|
|
139
140
|
auth: false,
|
|
140
141
|
})
|
|
141
142
|
|
|
143
|
+
// Initialize router after adding channel (for tests)
|
|
144
|
+
httpRouter.initialize()
|
|
145
|
+
|
|
142
146
|
const result = await runLocalChannel({
|
|
143
147
|
singletonServices: mockSingletonServices,
|
|
144
148
|
channelId: 'test-channel-id',
|
|
@@ -8,12 +8,9 @@ import {
|
|
|
8
8
|
RunChannelParams,
|
|
9
9
|
} from '../channel.types.js'
|
|
10
10
|
import { PikkuLocalChannelHandler } from './local-channel-handler.js'
|
|
11
|
-
import { SessionServices } from '../../../types/core.types.js'
|
|
11
|
+
import { PikkuWiringTypes, SessionServices } from '../../../types/core.types.js'
|
|
12
12
|
import { handleHTTPError } from '../../../handle-error.js'
|
|
13
|
-
import {
|
|
14
|
-
addMiddlewareForTags,
|
|
15
|
-
runMiddleware,
|
|
16
|
-
} from '../../../middleware-runner.js'
|
|
13
|
+
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
|
|
17
14
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js'
|
|
18
15
|
import { PikkuHTTP } from '../../http/http.types.js'
|
|
19
16
|
import { runPikkuFuncDirectly } from '../../../function/function-runner.js'
|
|
@@ -45,9 +42,9 @@ export const runLocalChannel = async ({
|
|
|
45
42
|
route = http?.request?.path()
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
let openingData, channelConfig, meta
|
|
45
|
+
let openingData, channelConfig, meta, httpMiddleware
|
|
49
46
|
try {
|
|
50
|
-
;({ openingData, channelConfig, meta } = await openChannel({
|
|
47
|
+
;({ openingData, channelConfig, meta, httpMiddleware } = await openChannel({
|
|
51
48
|
channelId,
|
|
52
49
|
createSessionServices,
|
|
53
50
|
respondWith404,
|
|
@@ -149,7 +146,11 @@ export const runLocalChannel = async ({
|
|
|
149
146
|
userSession,
|
|
150
147
|
},
|
|
151
148
|
{ http },
|
|
152
|
-
|
|
149
|
+
combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
150
|
+
wiringMiddleware: channelConfig.middleware,
|
|
151
|
+
wiringTags: channelConfig.tags,
|
|
152
|
+
httpMiddleware,
|
|
153
|
+
}),
|
|
153
154
|
main
|
|
154
155
|
)
|
|
155
156
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SessionServices } from '../../../types/core.types.js'
|
|
1
|
+
import { PikkuWiringTypes, SessionServices } from '../../../types/core.types.js'
|
|
2
2
|
import { closeSessionServices } from '../../../utils.js'
|
|
3
3
|
import { processMessageHandlers } from '../channel-handler.js'
|
|
4
4
|
import { openChannel } from '../channel-runner.js'
|
|
@@ -12,10 +12,7 @@ import { createHTTPInteraction } from '../../http/http-runner.js'
|
|
|
12
12
|
import { ChannelStore } from '../channel-store.js'
|
|
13
13
|
import { handleHTTPError } from '../../../handle-error.js'
|
|
14
14
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js'
|
|
15
|
-
import {
|
|
16
|
-
addMiddlewareForTags,
|
|
17
|
-
runMiddleware,
|
|
18
|
-
} from '../../../middleware-runner.js'
|
|
15
|
+
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
|
|
19
16
|
import { pikkuState } from '../../../pikku-state.js'
|
|
20
17
|
import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js'
|
|
21
18
|
import { PikkuHTTP } from '../../http/http.types.js'
|
|
@@ -88,15 +85,16 @@ export const runChannelConnect = async ({
|
|
|
88
85
|
|
|
89
86
|
const userSession = new PikkuUserSessionService(channelStore, channelId)
|
|
90
87
|
|
|
91
|
-
const { channelConfig, openingData, meta } =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
const { channelConfig, openingData, meta, httpMiddleware } =
|
|
89
|
+
await openChannel({
|
|
90
|
+
channelId,
|
|
91
|
+
createSessionServices,
|
|
92
|
+
request,
|
|
93
|
+
route,
|
|
94
|
+
singletonServices,
|
|
95
|
+
coerceDataFromSchema,
|
|
96
|
+
userSession,
|
|
97
|
+
})
|
|
100
98
|
|
|
101
99
|
const main = async () => {
|
|
102
100
|
try {
|
|
@@ -149,7 +147,11 @@ export const runChannelConnect = async ({
|
|
|
149
147
|
userSession,
|
|
150
148
|
},
|
|
151
149
|
{ http },
|
|
152
|
-
|
|
150
|
+
combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
151
|
+
wiringMiddleware: channelConfig.middleware,
|
|
152
|
+
wiringTags: channelConfig.tags,
|
|
153
|
+
httpMiddleware,
|
|
154
|
+
}),
|
|
153
155
|
main
|
|
154
156
|
)
|
|
155
157
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
PikkuMockResponse,
|
|
10
10
|
} from '../channel/local/local-channel-runner.test.js'
|
|
11
11
|
import { addFunction } from '../../function/function-runner.js'
|
|
12
|
+
import { httpRouter } from './routers/http-router.js'
|
|
12
13
|
|
|
13
14
|
const sessionMiddleware: CorePikkuMiddleware = async (services, _, next) => {
|
|
14
15
|
services.userSession.set({ userId: 'test' } as any)
|
|
@@ -22,13 +23,21 @@ const setHTTPFunctionMap = (func: any) => {
|
|
|
22
23
|
services: ['userSession'],
|
|
23
24
|
},
|
|
24
25
|
} as any)
|
|
25
|
-
pikkuState('http', 'meta',
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
pikkuState('http', 'meta', {
|
|
27
|
+
get: {
|
|
28
|
+
test: {
|
|
29
|
+
pikkuFuncName: 'pikku_func_name',
|
|
30
|
+
route: 'test',
|
|
31
|
+
method: 'get',
|
|
32
|
+
},
|
|
30
33
|
},
|
|
31
|
-
|
|
34
|
+
post: {},
|
|
35
|
+
delete: {},
|
|
36
|
+
patch: {},
|
|
37
|
+
head: {},
|
|
38
|
+
put: {},
|
|
39
|
+
options: {},
|
|
40
|
+
})
|
|
32
41
|
addFunction('pikku_func_name', { func })
|
|
33
42
|
}
|
|
34
43
|
|
|
@@ -85,6 +94,9 @@ describe('fetch', () => {
|
|
|
85
94
|
middleware: [sessionMiddleware],
|
|
86
95
|
})
|
|
87
96
|
|
|
97
|
+
// Initialize router after adding route (for tests)
|
|
98
|
+
httpRouter.initialize()
|
|
99
|
+
|
|
88
100
|
const result = await fetch(request, {
|
|
89
101
|
singletonServices,
|
|
90
102
|
createSessionServices,
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
CoreUserSession,
|
|
13
13
|
CorePikkuMiddleware,
|
|
14
14
|
SessionServices,
|
|
15
|
+
PikkuWiringTypes,
|
|
15
16
|
} from '../../types/core.types.js'
|
|
16
|
-
import { match } from 'path-to-regexp'
|
|
17
17
|
import { MissingSessionError, NotFoundError } from '../../errors/errors.js'
|
|
18
18
|
import {
|
|
19
19
|
closeSessionServices,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
PikkuUserSessionService,
|
|
25
25
|
UserSessionService,
|
|
26
26
|
} from '../../services/user-session-service.js'
|
|
27
|
-
import {
|
|
27
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
|
|
28
28
|
import { handleHTTPError } from '../../handle-error.js'
|
|
29
29
|
import { pikkuState } from '../../pikku-state.js'
|
|
30
30
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
|
|
@@ -32,6 +32,7 @@ import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js'
|
|
|
32
32
|
import { PikkuChannel } from '../channel/channel.types.js'
|
|
33
33
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
34
34
|
import { rpcService } from '../rpc/rpc-runner.js'
|
|
35
|
+
import { httpRouter } from './routers/http-router.js'
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* Registers middleware either globally or for a specific route.
|
|
@@ -49,32 +50,20 @@ export const addHTTPMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
|
|
|
49
50
|
middleware?: PikkuMiddleware[]
|
|
50
51
|
) => {
|
|
51
52
|
const middlewareStore = pikkuState('http', 'middleware')
|
|
53
|
+
let route = '*'
|
|
52
54
|
|
|
53
|
-
if (typeof routeOrMiddleware === 'string'
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Remove existing entry for this route if it exists
|
|
57
|
-
const existingIndex = middlewareStore.findIndex(
|
|
58
|
-
(item) => item.route === route
|
|
59
|
-
)
|
|
60
|
-
if (existingIndex !== -1) {
|
|
61
|
-
middlewareStore.splice(existingIndex, 1)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
middlewareStore.push({ route, middleware })
|
|
55
|
+
if (typeof routeOrMiddleware === 'string') {
|
|
56
|
+
route = routeOrMiddleware
|
|
57
|
+
middleware = middleware!
|
|
65
58
|
} else {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(item) => item.route === '*'
|
|
69
|
-
)
|
|
70
|
-
if (existingIndex !== -1) {
|
|
71
|
-
middlewareStore.splice(existingIndex, 1)
|
|
72
|
-
}
|
|
59
|
+
middleware = routeOrMiddleware
|
|
60
|
+
}
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
62
|
+
const currentMiddleware = middlewareStore.get(route)
|
|
63
|
+
if (currentMiddleware) {
|
|
64
|
+
middlewareStore.set(route, [...currentMiddleware, ...middleware])
|
|
65
|
+
} else {
|
|
66
|
+
middlewareStore.set(route, middleware)
|
|
78
67
|
}
|
|
79
68
|
}
|
|
80
69
|
|
|
@@ -113,10 +102,7 @@ export const wireHTTP = <
|
|
|
113
102
|
>
|
|
114
103
|
) => {
|
|
115
104
|
const httpMeta = pikkuState('http', 'meta')
|
|
116
|
-
const routeMeta = httpMeta.
|
|
117
|
-
(meta) =>
|
|
118
|
-
meta.route === httpWiring.route && meta.method === httpWiring.method
|
|
119
|
-
)
|
|
105
|
+
const routeMeta = httpMeta[httpWiring.method][httpWiring.route]
|
|
120
106
|
if (!routeMeta) {
|
|
121
107
|
throw new Error('Route metadata not found')
|
|
122
108
|
}
|
|
@@ -142,43 +128,29 @@ export const wireHTTP = <
|
|
|
142
128
|
* @returns {Object | undefined} An object with matched route details or undefined if no match.
|
|
143
129
|
*/
|
|
144
130
|
const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return undefined
|
|
150
|
-
}
|
|
151
|
-
for (const route of routes.values()) {
|
|
152
|
-
// Generate a matching function from the route pattern
|
|
153
|
-
const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
|
|
154
|
-
decode: decodeURIComponent,
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// Attempt to match the request path
|
|
158
|
-
const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'))
|
|
159
|
-
|
|
160
|
-
if (matchedPath) {
|
|
161
|
-
// Aggregate global and route-specific middleware
|
|
162
|
-
const globalMiddleware = middleware
|
|
163
|
-
.filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
|
|
164
|
-
.map((m) => m.middleware)
|
|
165
|
-
.flat()
|
|
166
|
-
|
|
167
|
-
const meta = pikkuState('http', 'meta').find(
|
|
168
|
-
(meta) => meta.route === route.route && meta.method === route.method
|
|
169
|
-
)
|
|
131
|
+
const matchedPath = httpRouter.match(
|
|
132
|
+
requestType.toLowerCase() as HTTPMethod,
|
|
133
|
+
requestPath
|
|
134
|
+
)
|
|
170
135
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
136
|
+
if (matchedPath) {
|
|
137
|
+
const route = pikkuState('http', 'routes')
|
|
138
|
+
.get(requestType.toLowerCase() as HTTPMethod)!
|
|
139
|
+
.get(matchedPath.route)!
|
|
140
|
+
const meta = pikkuState('http', 'meta')[
|
|
141
|
+
requestType.toLowerCase() as PikkuWiringTypes
|
|
142
|
+
][route.route]
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
matchedPath,
|
|
146
|
+
params: matchedPath.params,
|
|
147
|
+
route,
|
|
148
|
+
permissions: route.permissions,
|
|
149
|
+
httpMiddleware: matchedPath.middleware,
|
|
150
|
+
middleware: route.middleware,
|
|
151
|
+
meta: meta!,
|
|
179
152
|
}
|
|
180
153
|
}
|
|
181
|
-
return undefined
|
|
182
154
|
}
|
|
183
155
|
|
|
184
156
|
/**
|
|
@@ -251,7 +223,8 @@ const executeRouteWithMiddleware = async (
|
|
|
251
223
|
matchedPath: any
|
|
252
224
|
params: any
|
|
253
225
|
route: CoreHTTPFunctionWiring<any, any, any>
|
|
254
|
-
|
|
226
|
+
httpMiddleware: CorePikkuMiddleware[] | undefined
|
|
227
|
+
middleware: CorePikkuMiddleware[] | undefined
|
|
255
228
|
meta: HTTPWiringMeta
|
|
256
229
|
},
|
|
257
230
|
http: PikkuHTTP,
|
|
@@ -259,7 +232,8 @@ const executeRouteWithMiddleware = async (
|
|
|
259
232
|
coerceDataFromSchema: boolean
|
|
260
233
|
}
|
|
261
234
|
) => {
|
|
262
|
-
const { matchedPath, params, route, middleware, meta } =
|
|
235
|
+
const { matchedPath, params, route, httpMiddleware, middleware, meta } =
|
|
236
|
+
matchedRoute
|
|
263
237
|
const {
|
|
264
238
|
singletonServices,
|
|
265
239
|
userSession,
|
|
@@ -347,14 +321,19 @@ const executeRouteWithMiddleware = async (
|
|
|
347
321
|
})
|
|
348
322
|
}
|
|
349
323
|
|
|
350
|
-
result = await runPikkuFunc(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
324
|
+
result = await runPikkuFunc(
|
|
325
|
+
PikkuWiringTypes.http,
|
|
326
|
+
`${meta.method}:${meta.route}`,
|
|
327
|
+
meta.pikkuFuncName,
|
|
328
|
+
{
|
|
329
|
+
getAllServices,
|
|
330
|
+
session,
|
|
331
|
+
data,
|
|
332
|
+
permissions: route.permissions,
|
|
333
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
334
|
+
tags: route.tags,
|
|
335
|
+
}
|
|
336
|
+
)
|
|
358
337
|
|
|
359
338
|
// Respond with either a binary or JSON response based on configuration
|
|
360
339
|
if (route.returnsJSON === false) {
|
|
@@ -368,11 +347,20 @@ const executeRouteWithMiddleware = async (
|
|
|
368
347
|
// http?.response?.end()
|
|
369
348
|
}
|
|
370
349
|
|
|
350
|
+
// Get function config for middleware and tags
|
|
351
|
+
const funcConfig = pikkuState('function', 'functions').get(meta.pikkuFuncName)
|
|
352
|
+
|
|
371
353
|
// Execute middleware, then run the main logic
|
|
372
354
|
await runMiddleware(
|
|
373
355
|
{ ...singletonServices, userSession },
|
|
374
356
|
{ http },
|
|
375
|
-
|
|
357
|
+
combineMiddleware(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, {
|
|
358
|
+
httpMiddleware,
|
|
359
|
+
wiringMiddleware: middleware,
|
|
360
|
+
wiringTags: route.tags,
|
|
361
|
+
funcMiddleware: funcConfig?.middleware,
|
|
362
|
+
funcTags: funcConfig?.tags,
|
|
363
|
+
}),
|
|
376
364
|
runMain
|
|
377
365
|
)
|
|
378
366
|
|
|
@@ -498,7 +486,8 @@ export const fetchData = async <In, Out>(
|
|
|
498
486
|
ignoreMiddleware
|
|
499
487
|
? {
|
|
500
488
|
...matchedRoute,
|
|
501
|
-
middleware:
|
|
489
|
+
middleware: undefined,
|
|
490
|
+
httpMiddleware: undefined,
|
|
502
491
|
}
|
|
503
492
|
: matchedRoute,
|
|
504
493
|
http!,
|