@pikku/core 0.6.27 → 0.7.1
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 +16 -1
- package/dist/channel/channel-handler.js +16 -38
- package/dist/channel/channel-runner.d.ts +7 -8
- package/dist/channel/channel-runner.js +58 -7
- package/dist/channel/channel.types.d.ts +14 -27
- package/dist/channel/local/local-channel-runner.js +9 -4
- package/dist/channel/log-channels.js +4 -4
- package/dist/channel/serverless/serverless-channel-runner.js +17 -6
- package/dist/function/function-runner.d.ts +12 -0
- package/dist/function/function-runner.js +38 -0
- package/dist/{types → function}/functions.types.d.ts +12 -7
- package/dist/function/index.d.ts +2 -0
- package/dist/function/index.js +2 -0
- package/dist/handle-error.d.ts +1 -1
- package/dist/http/{http-route-runner.d.ts → http-runner.d.ts} +4 -4
- package/dist/http/{http-route-runner.js → http-runner.js} +81 -43
- package/dist/http/{http-routes.types.d.ts → http.types.d.ts} +41 -12
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +1 -1
- package/dist/http/log-http-routes.js +7 -5
- package/dist/http/pikku-fetch-http-request.d.ts +1 -1
- package/dist/http/pikku-fetch-http-response.d.ts +4 -2
- package/dist/http/pikku-fetch-http-response.js +64 -12
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/middleware/auth-apikey.js +5 -4
- package/dist/middleware/auth-bearer.js +5 -4
- package/dist/middleware/auth-cookie.js +10 -9
- package/dist/permissions.d.ts +2 -2
- package/dist/pikku-function.d.ts +1 -0
- package/dist/pikku-function.js +1 -0
- package/dist/pikku-state.d.ts +10 -5
- package/dist/pikku-state.js +7 -3
- package/dist/scheduler/scheduler-runner.d.ts +1 -1
- package/dist/scheduler/scheduler-runner.js +24 -6
- package/dist/scheduler/scheduler.types.d.ts +3 -2
- package/dist/services/content-service.d.ts +33 -37
- package/dist/types/core.types.d.ts +15 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +15 -0
- package/lcov.info +1240 -965
- package/package.json +2 -1
- package/src/channel/channel-handler.ts +16 -78
- package/src/channel/channel-runner.ts +72 -17
- package/src/channel/channel.types.ts +25 -63
- package/src/channel/local/local-channel-runner.test.ts +10 -11
- package/src/channel/local/local-channel-runner.ts +20 -7
- package/src/channel/log-channels.ts +4 -4
- package/src/channel/serverless/serverless-channel-runner.ts +26 -15
- package/src/function/function-runner.ts +91 -0
- package/src/{types → function}/functions.types.ts +25 -11
- package/src/function/index.ts +2 -0
- package/src/handle-error.ts +1 -1
- package/src/http/{http-route-runner.test.ts → http-runner.test.ts} +29 -6
- package/src/http/{http-route-runner.ts → http-runner.ts} +106 -73
- package/src/http/{http-routes.types.ts → http.types.ts} +55 -16
- package/src/http/index.ts +2 -2
- package/src/http/log-http-routes.ts +7 -5
- package/src/http/pikku-fetch-http-request.ts +1 -5
- package/src/http/pikku-fetch-http-response.ts +67 -13
- package/src/index.ts +3 -2
- package/src/middleware/auth-apikey.ts +5 -4
- package/src/middleware/auth-bearer.ts +5 -4
- package/src/middleware/auth-cookie.ts +14 -9
- package/src/permissions.ts +2 -4
- package/src/pikku-function.ts +1 -0
- package/src/pikku-state.ts +25 -9
- package/src/scheduler/scheduler-runner.ts +31 -13
- package/src/scheduler/scheduler.types.ts +12 -8
- package/src/services/content-service.ts +36 -40
- package/src/types/core.types.ts +20 -1
- package/src/utils.ts +19 -0
- package/tsconfig.tsbuildinfo +1 -1
- /package/dist/{types → function}/functions.types.js +0 -0
- /package/dist/http/{http-routes.types.js → http.types.js} +0 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ForbiddenError } from '../errors/errors.js'
|
|
2
|
+
import { verifyPermissions } from '../permissions.js'
|
|
3
|
+
import { pikkuState } from '../pikku-state.js'
|
|
4
|
+
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
|
|
5
|
+
import {
|
|
6
|
+
CoreServices,
|
|
7
|
+
CoreSingletonServices,
|
|
8
|
+
CoreUserSession,
|
|
9
|
+
} from '../types/core.types.js'
|
|
10
|
+
import {
|
|
11
|
+
CoreAPIFunction,
|
|
12
|
+
CoreAPIFunctionSessionless,
|
|
13
|
+
CorePermissionGroup,
|
|
14
|
+
} from './functions.types.js'
|
|
15
|
+
|
|
16
|
+
export const addFunction = (
|
|
17
|
+
funcName: string,
|
|
18
|
+
func: CoreAPIFunction<any, any> | CoreAPIFunctionSessionless<any, any>
|
|
19
|
+
) => {
|
|
20
|
+
pikkuState('functions', 'nameToFunction').set(funcName, func)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const runPikkuFuncDirectly = async <In, Out>(
|
|
24
|
+
funcName: string,
|
|
25
|
+
allServices: CoreServices,
|
|
26
|
+
data: In,
|
|
27
|
+
session?: CoreUserSession
|
|
28
|
+
) => {
|
|
29
|
+
const func = pikkuState('functions', 'nameToFunction').get(funcName)
|
|
30
|
+
if (!func) {
|
|
31
|
+
throw new Error(`Function not found: ${funcName}`)
|
|
32
|
+
}
|
|
33
|
+
return (await func(allServices, data, session!)) as Out
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const runPikkuFunc = async <In = any, Out = any>(
|
|
37
|
+
funcName: string,
|
|
38
|
+
{
|
|
39
|
+
singletonServices,
|
|
40
|
+
getAllServices,
|
|
41
|
+
data,
|
|
42
|
+
session,
|
|
43
|
+
permissions,
|
|
44
|
+
coerceDataFromSchema,
|
|
45
|
+
}: {
|
|
46
|
+
singletonServices: CoreSingletonServices
|
|
47
|
+
getAllServices: () => Promise<CoreServices> | CoreServices
|
|
48
|
+
data: In
|
|
49
|
+
session?: CoreUserSession
|
|
50
|
+
permissions?: CorePermissionGroup
|
|
51
|
+
coerceDataFromSchema?: boolean
|
|
52
|
+
}
|
|
53
|
+
): Promise<Out> => {
|
|
54
|
+
const func = pikkuState('functions', 'nameToFunction').get(funcName)
|
|
55
|
+
if (!func) {
|
|
56
|
+
throw new Error(`Function not found: ${funcName}`)
|
|
57
|
+
}
|
|
58
|
+
const funcMeta = pikkuState('functions', 'meta')[funcName]
|
|
59
|
+
if (!funcMeta) {
|
|
60
|
+
throw new Error(`Function meta not found: ${funcName}`)
|
|
61
|
+
}
|
|
62
|
+
const schemaName = funcMeta.schemaName
|
|
63
|
+
// Validate request data against the defined schema, if any
|
|
64
|
+
await validateSchema(
|
|
65
|
+
singletonServices.logger,
|
|
66
|
+
singletonServices.schema,
|
|
67
|
+
schemaName,
|
|
68
|
+
data
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// Coerce (top level) query string parameters or date objects if specified by the schema
|
|
72
|
+
if (coerceDataFromSchema && schemaName) {
|
|
73
|
+
coerceTopLevelDataFromSchema(schemaName, data)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const allServices = await getAllServices()
|
|
77
|
+
|
|
78
|
+
// Execute permission checks
|
|
79
|
+
const permissioned = await verifyPermissions(
|
|
80
|
+
permissions,
|
|
81
|
+
allServices,
|
|
82
|
+
data,
|
|
83
|
+
session
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if (permissioned === false) {
|
|
87
|
+
throw new ForbiddenError('Permission denied')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (await func(allServices, data, session!)) as Out
|
|
91
|
+
}
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
CoreServices,
|
|
4
4
|
CoreSingletonServices,
|
|
5
5
|
CoreUserSession,
|
|
6
|
-
} from '
|
|
6
|
+
} from '../types/core.types.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Represents a core API function that performs an operation using core services and a user session.
|
|
@@ -16,16 +16,21 @@ import type {
|
|
|
16
16
|
export type CoreAPIFunction<
|
|
17
17
|
In,
|
|
18
18
|
Out,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
ChannelData extends unknown | null = null,
|
|
20
|
+
Services extends CoreSingletonServices = CoreServices &
|
|
21
|
+
(ChannelData extends null
|
|
22
|
+
? {
|
|
23
|
+
channel?: PikkuChannel<unknown, Out> | undefined
|
|
24
|
+
}
|
|
25
|
+
: {
|
|
26
|
+
channel: PikkuChannel<ChannelData, Out>
|
|
27
|
+
}),
|
|
22
28
|
Session extends CoreUserSession = CoreUserSession,
|
|
23
|
-
Channel extends boolean = false,
|
|
24
29
|
> = (
|
|
25
30
|
services: Services,
|
|
26
31
|
data: In,
|
|
27
32
|
session: Session
|
|
28
|
-
) =>
|
|
33
|
+
) => ChannelData extends null ? Promise<Out> : Promise<Out> | Promise<void>
|
|
29
34
|
|
|
30
35
|
/**
|
|
31
36
|
* Represents a core API function that can be used without a session.
|
|
@@ -38,16 +43,21 @@ export type CoreAPIFunction<
|
|
|
38
43
|
export type CoreAPIFunctionSessionless<
|
|
39
44
|
In,
|
|
40
45
|
Out,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
ChannelData extends unknown | null = null,
|
|
47
|
+
Services extends CoreSingletonServices = CoreServices &
|
|
48
|
+
(ChannelData extends null
|
|
49
|
+
? {
|
|
50
|
+
channel?: PikkuChannel<unknown, Out> | undefined
|
|
51
|
+
}
|
|
52
|
+
: {
|
|
53
|
+
channel: PikkuChannel<ChannelData, Out>
|
|
54
|
+
}),
|
|
44
55
|
Session extends CoreUserSession = CoreUserSession,
|
|
45
|
-
Channel extends boolean = false,
|
|
46
56
|
> = (
|
|
47
57
|
services: Services,
|
|
48
58
|
data: In,
|
|
49
59
|
session?: Session
|
|
50
|
-
) =>
|
|
60
|
+
) => ChannelData extends null ? Promise<Out> : Promise<Out> | Promise<void>
|
|
51
61
|
|
|
52
62
|
/**
|
|
53
63
|
* Represents a function that checks permissions for a given API operation.
|
|
@@ -61,3 +71,7 @@ export type CoreAPIPermission<
|
|
|
61
71
|
Services extends CoreSingletonServices = CoreServices,
|
|
62
72
|
Session extends CoreUserSession = CoreUserSession,
|
|
63
73
|
> = (services: Services, data: In, session?: Session) => Promise<boolean>
|
|
74
|
+
|
|
75
|
+
export type CorePermissionGroup<APIPermission = CoreAPIPermission<any>> =
|
|
76
|
+
| Record<string, APIPermission | APIPermission[]>
|
|
77
|
+
| undefined
|
package/src/handle-error.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getErrorResponse } from './errors/error-handler.js'
|
|
2
2
|
import { NotFoundError } from './errors/errors.js'
|
|
3
3
|
import { Logger } from './services/logger.js'
|
|
4
|
-
import { PikkuHTTP } from './http/http
|
|
4
|
+
import { PikkuHTTP } from './http/http.types.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Handle errors that occur during route processing
|
|
@@ -2,18 +2,38 @@ import { test, describe, beforeEach, afterEach } from 'node:test'
|
|
|
2
2
|
import * as assert from 'assert'
|
|
3
3
|
import { NotFoundError } from '../errors/errors.js'
|
|
4
4
|
import { JSONValue, PikkuMiddleware } from '../types/core.types.js'
|
|
5
|
-
import { fetch,
|
|
6
|
-
import { resetPikkuState } from '../pikku-state.js'
|
|
5
|
+
import { fetch, addHTTPRoute } from './http-runner.js'
|
|
6
|
+
import { pikkuState, resetPikkuState } from '../pikku-state.js'
|
|
7
7
|
import {
|
|
8
8
|
PikkuMockRequest,
|
|
9
9
|
PikkuMockResponse,
|
|
10
10
|
} from '../channel/local/local-channel-runner.test.js'
|
|
11
|
+
import { addFunction } from '../function/function-runner.js'
|
|
11
12
|
|
|
12
13
|
const sessionMiddleware: PikkuMiddleware = async (services, _, next) => {
|
|
13
14
|
services.userSession.set({ userId: 'test' } as any)
|
|
14
15
|
await next()
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
const setHTTPFunctionMap = (fun: any) => {
|
|
19
|
+
pikkuState('functions', 'meta', {
|
|
20
|
+
pikku_func_name: {
|
|
21
|
+
pikkuFuncName: 'pikku_func_name',
|
|
22
|
+
services: ['userSession'],
|
|
23
|
+
},
|
|
24
|
+
} as any)
|
|
25
|
+
pikkuState('http', 'meta', [
|
|
26
|
+
{
|
|
27
|
+
pikkuFuncName: 'pikku_func_name',
|
|
28
|
+
route: 'test',
|
|
29
|
+
method: 'get',
|
|
30
|
+
input: null,
|
|
31
|
+
output: null,
|
|
32
|
+
},
|
|
33
|
+
])
|
|
34
|
+
addFunction('pikku_func_name', fun)
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
describe('fetch', () => {
|
|
18
38
|
let singletonServices: any
|
|
19
39
|
let createSessionServices: any
|
|
@@ -57,7 +77,9 @@ describe('fetch', () => {
|
|
|
57
77
|
|
|
58
78
|
test('should call the route function and return its result when a matching route is found', async () => {
|
|
59
79
|
const routeFunc = async () => ({ success: true })
|
|
60
|
-
|
|
80
|
+
setHTTPFunctionMap(routeFunc)
|
|
81
|
+
|
|
82
|
+
addHTTPRoute({
|
|
61
83
|
route: 'test',
|
|
62
84
|
method: 'get',
|
|
63
85
|
func: routeFunc,
|
|
@@ -75,8 +97,9 @@ describe('fetch', () => {
|
|
|
75
97
|
test('should verify permissions if provided', async () => {
|
|
76
98
|
const permissions = { test: async () => true }
|
|
77
99
|
const routeFunc = async () => ({ success: true })
|
|
100
|
+
setHTTPFunctionMap(routeFunc)
|
|
78
101
|
|
|
79
|
-
|
|
102
|
+
addHTTPRoute({
|
|
80
103
|
route: 'test',
|
|
81
104
|
method: 'get',
|
|
82
105
|
func: routeFunc,
|
|
@@ -97,13 +120,13 @@ describe('fetch', () => {
|
|
|
97
120
|
const routeFunc = async () => {
|
|
98
121
|
throw error
|
|
99
122
|
}
|
|
100
|
-
|
|
123
|
+
setHTTPFunctionMap(routeFunc)
|
|
124
|
+
addHTTPRoute({
|
|
101
125
|
route: 'test',
|
|
102
126
|
method: 'get',
|
|
103
127
|
func: routeFunc,
|
|
104
128
|
middleware: [sessionMiddleware],
|
|
105
129
|
})
|
|
106
|
-
|
|
107
130
|
await assert.rejects(
|
|
108
131
|
async () =>
|
|
109
132
|
fetch(request, {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { verifyPermissions } from '../permissions.js'
|
|
2
1
|
import {
|
|
3
2
|
CoreHTTPFunctionRoute,
|
|
4
3
|
RunRouteOptions,
|
|
@@ -6,20 +5,21 @@ import {
|
|
|
6
5
|
PikkuHTTP,
|
|
7
6
|
PikkuHTTPRequest,
|
|
8
7
|
PikkuHTTPResponse,
|
|
9
|
-
|
|
8
|
+
HTTPRouteMeta,
|
|
9
|
+
HTTPMethod,
|
|
10
|
+
} from './http.types.js'
|
|
10
11
|
import {
|
|
11
12
|
CoreUserSession,
|
|
12
13
|
PikkuMiddleware,
|
|
13
14
|
SessionServices,
|
|
14
15
|
} from '../types/core.types.js'
|
|
15
16
|
import { match } from 'path-to-regexp'
|
|
17
|
+
import { MissingSessionError, NotFoundError } from '../errors/errors.js'
|
|
16
18
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from '../
|
|
21
|
-
import { closeSessionServices } from '../utils.js'
|
|
22
|
-
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
|
|
19
|
+
closeSessionServices,
|
|
20
|
+
createWeakUID,
|
|
21
|
+
isSerializable,
|
|
22
|
+
} from '../utils.js'
|
|
23
23
|
import {
|
|
24
24
|
PikkuUserSessionService,
|
|
25
25
|
UserSessionService,
|
|
@@ -29,6 +29,8 @@ import { handleError } from '../handle-error.js'
|
|
|
29
29
|
import { pikkuState } from '../pikku-state.js'
|
|
30
30
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
|
|
31
31
|
import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js'
|
|
32
|
+
import { PikkuChannel } from '../channel/channel.types.js'
|
|
33
|
+
import { addFunction, runPikkuFunc } from '../function/function-runner.js'
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Registers middleware either globally or for a specific route.
|
|
@@ -69,30 +71,44 @@ export const addMiddleware = <APIMiddleware extends PikkuMiddleware>(
|
|
|
69
71
|
* @template Route Route pattern as a string.
|
|
70
72
|
* @template APIFunction Type for the route handler function.
|
|
71
73
|
* @template APIFunctionSessionless Type for a sessionless handler.
|
|
72
|
-
* @template
|
|
74
|
+
* @template APIPermissionGroup Type representing required permissions.
|
|
73
75
|
* @template APIMiddleware Middleware type to be used with the route.
|
|
74
76
|
* @param {CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>} route - The route configuration object.
|
|
75
77
|
*/
|
|
76
|
-
export const
|
|
78
|
+
export const addHTTPRoute = <
|
|
77
79
|
In,
|
|
78
80
|
Out,
|
|
79
81
|
Route extends string,
|
|
80
82
|
APIFunction,
|
|
81
83
|
APIFunctionSessionless,
|
|
82
|
-
|
|
84
|
+
APIPermissionGroup,
|
|
83
85
|
APIMiddleware,
|
|
84
86
|
>(
|
|
85
|
-
|
|
87
|
+
httpRoute: CoreHTTPFunctionRoute<
|
|
86
88
|
In,
|
|
87
89
|
Out,
|
|
88
90
|
Route,
|
|
89
91
|
APIFunction,
|
|
90
92
|
APIFunctionSessionless,
|
|
91
|
-
|
|
93
|
+
APIPermissionGroup,
|
|
92
94
|
APIMiddleware
|
|
93
95
|
>
|
|
94
96
|
) => {
|
|
95
|
-
pikkuState('http', '
|
|
97
|
+
const httpMeta = pikkuState('http', 'meta')
|
|
98
|
+
const routeMeta = httpMeta.find(
|
|
99
|
+
(meta) => meta.route === httpRoute.route && meta.method === httpRoute.method
|
|
100
|
+
)
|
|
101
|
+
if (!routeMeta) {
|
|
102
|
+
throw new Error('Route metadata not found')
|
|
103
|
+
}
|
|
104
|
+
addFunction(routeMeta.pikkuFuncName, httpRoute.func as any)
|
|
105
|
+
const routes = pikkuState('http', 'routes')
|
|
106
|
+
if (!routes.has(httpRoute.method)) {
|
|
107
|
+
routes.set(httpRoute.method, new Map())
|
|
108
|
+
}
|
|
109
|
+
pikkuState('http', 'routes')
|
|
110
|
+
.get(httpRoute.method)
|
|
111
|
+
?.set(httpRoute.route, httpRoute as any)
|
|
96
112
|
}
|
|
97
113
|
|
|
98
114
|
/**
|
|
@@ -107,16 +123,13 @@ export const addRoute = <
|
|
|
107
123
|
* @returns {Object | undefined} An object with matched route details or undefined if no match.
|
|
108
124
|
*/
|
|
109
125
|
const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
110
|
-
const
|
|
126
|
+
const allRoutes = pikkuState('http', 'routes')
|
|
111
127
|
const middleware = pikkuState('http', 'middleware')
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
continue
|
|
118
|
-
}
|
|
119
|
-
|
|
128
|
+
const routes = allRoutes.get(requestType.toLowerCase() as HTTPMethod)
|
|
129
|
+
if (!routes) {
|
|
130
|
+
return undefined
|
|
131
|
+
}
|
|
132
|
+
for (const route of routes.values()) {
|
|
120
133
|
// Generate a matching function from the route pattern
|
|
121
134
|
const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
|
|
122
135
|
decode: decodeURIComponent,
|
|
@@ -132,18 +145,17 @@ const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
|
132
145
|
.map((m) => m.middleware)
|
|
133
146
|
.flat()
|
|
134
147
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
routeMeta.method === route.method && routeMeta.route === route.route
|
|
139
|
-
)?.input
|
|
148
|
+
const meta = pikkuState('http', 'meta').find(
|
|
149
|
+
(meta) => meta.route === route.route && meta.method === route.method
|
|
150
|
+
)
|
|
140
151
|
|
|
141
152
|
return {
|
|
142
153
|
matchedPath,
|
|
143
154
|
params: matchedPath.params,
|
|
144
155
|
route,
|
|
156
|
+
permissions: route.permissions,
|
|
145
157
|
middleware: [...globalMiddleware, ...(route.middleware || [])],
|
|
146
|
-
|
|
158
|
+
meta: meta!,
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
}
|
|
@@ -205,25 +217,27 @@ const executeRouteWithMiddleware = async (
|
|
|
205
217
|
userSession: UserSessionService<CoreUserSession>
|
|
206
218
|
createSessionServices: Function
|
|
207
219
|
skipUserSession: boolean
|
|
220
|
+
requestId: string
|
|
208
221
|
},
|
|
209
222
|
matchedRoute: {
|
|
210
223
|
matchedPath: any
|
|
211
224
|
params: any
|
|
212
225
|
route: CoreHTTPFunctionRoute<any, any, any>
|
|
213
226
|
middleware: any[]
|
|
214
|
-
|
|
227
|
+
meta: HTTPRouteMeta
|
|
215
228
|
},
|
|
216
|
-
http: PikkuHTTP
|
|
229
|
+
http: PikkuHTTP,
|
|
217
230
|
options: {
|
|
218
231
|
coerceDataFromSchema: boolean
|
|
219
232
|
}
|
|
220
233
|
) => {
|
|
221
|
-
const { matchedPath, params, route, middleware,
|
|
234
|
+
const { matchedPath, params, route, middleware, meta } = matchedRoute
|
|
222
235
|
const {
|
|
223
236
|
singletonServices,
|
|
224
237
|
userSession,
|
|
225
238
|
createSessionServices,
|
|
226
239
|
skipUserSession,
|
|
240
|
+
requestId,
|
|
227
241
|
} = services
|
|
228
242
|
|
|
229
243
|
const requiresSession = route.auth !== false
|
|
@@ -256,48 +270,62 @@ const executeRouteWithMiddleware = async (
|
|
|
256
270
|
throw new MissingSessionError()
|
|
257
271
|
}
|
|
258
272
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
const data = http.request!.data()
|
|
274
|
+
|
|
275
|
+
const getAllServices = async () => {
|
|
276
|
+
let channel: PikkuChannel<unknown, unknown> | undefined
|
|
277
|
+
if (matchedRoute.route.sse) {
|
|
278
|
+
const response = http?.response
|
|
279
|
+
if (!response) {
|
|
280
|
+
throw new Error('SSE requires a valid HTTP response object')
|
|
281
|
+
}
|
|
282
|
+
if (!response.setMode) {
|
|
283
|
+
throw new Error('Response object does not support SSE mode')
|
|
284
|
+
}
|
|
285
|
+
response.setMode('stream')
|
|
286
|
+
response.header('Content-Type', 'text/event-stream')
|
|
287
|
+
response.header('Cache-Control', 'no-cache')
|
|
288
|
+
response.header('Connection', 'keep-alive')
|
|
289
|
+
response.header('Transfer-Encoding', 'chunked')
|
|
290
|
+
channel = {
|
|
291
|
+
channelId: requestId,
|
|
292
|
+
openingData: data,
|
|
293
|
+
send: (data: any) => {
|
|
294
|
+
response.arrayBuffer(
|
|
295
|
+
isSerializable(data) ? JSON.stringify(data) : data
|
|
296
|
+
)
|
|
297
|
+
},
|
|
298
|
+
close: () => {
|
|
299
|
+
channel!.state = 'closed'
|
|
300
|
+
response.close?.()
|
|
301
|
+
},
|
|
302
|
+
state: 'open',
|
|
303
|
+
}
|
|
304
|
+
}
|
|
281
305
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
306
|
+
// Create session-specific services for handling the request
|
|
307
|
+
sessionServices = await createSessionServices(
|
|
308
|
+
{ ...singletonServices, userSession, channel },
|
|
309
|
+
{ http },
|
|
310
|
+
session
|
|
311
|
+
)
|
|
312
|
+
return {
|
|
313
|
+
...singletonServices,
|
|
314
|
+
...sessionServices,
|
|
315
|
+
http,
|
|
316
|
+
userSession,
|
|
317
|
+
channel,
|
|
318
|
+
}
|
|
285
319
|
}
|
|
286
320
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
321
|
+
const result = await runPikkuFunc(meta.pikkuFuncName, {
|
|
322
|
+
singletonServices,
|
|
323
|
+
getAllServices,
|
|
324
|
+
session,
|
|
291
325
|
data,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (permissioned === false) {
|
|
296
|
-
throw new ForbiddenError('Permission denied')
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Invoke the actual route handler function
|
|
300
|
-
result = await route.func(allServices, data, session!)
|
|
326
|
+
permissions: route.permissions,
|
|
327
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
328
|
+
})
|
|
301
329
|
|
|
302
330
|
// Respond with either a binary or JSON response based on configuration
|
|
303
331
|
if (route.returnsJSON === false) {
|
|
@@ -398,8 +426,13 @@ export const fetchData = async <In, Out>(
|
|
|
398
426
|
logWarningsForStatusCodes = [],
|
|
399
427
|
coerceDataFromSchema = true,
|
|
400
428
|
bubbleErrors = false,
|
|
429
|
+
generateRequestId,
|
|
401
430
|
}: RunRouteOptions & RunRouteParams
|
|
402
431
|
): Promise<Out | void> => {
|
|
432
|
+
const requestId =
|
|
433
|
+
(request as any).getHeader?.('x-request-id') ||
|
|
434
|
+
generateRequestId?.() ||
|
|
435
|
+
createWeakUID()
|
|
403
436
|
const userSession = new PikkuUserSessionService()
|
|
404
437
|
let sessionServices: SessionServices<typeof singletonServices> | undefined
|
|
405
438
|
let result: Out
|
|
@@ -414,7 +447,6 @@ export const fetchData = async <In, Out>(
|
|
|
414
447
|
|
|
415
448
|
// Locate the matching route based on the HTTP method and path
|
|
416
449
|
const matchedRoute = getMatchingRoute(apiType, apiRoute)
|
|
417
|
-
|
|
418
450
|
try {
|
|
419
451
|
// If no route matches, log the occurrence and throw a NotFoundError
|
|
420
452
|
if (!matchedRoute) {
|
|
@@ -433,9 +465,10 @@ export const fetchData = async <In, Out>(
|
|
|
433
465
|
userSession,
|
|
434
466
|
createSessionServices,
|
|
435
467
|
skipUserSession,
|
|
468
|
+
requestId,
|
|
436
469
|
},
|
|
437
470
|
matchedRoute,
|
|
438
|
-
http
|
|
471
|
+
http!,
|
|
439
472
|
{ coerceDataFromSchema }
|
|
440
473
|
))
|
|
441
474
|
|
|
@@ -445,7 +478,7 @@ export const fetchData = async <In, Out>(
|
|
|
445
478
|
handleError(
|
|
446
479
|
e,
|
|
447
480
|
http,
|
|
448
|
-
|
|
481
|
+
requestId,
|
|
449
482
|
singletonServices.logger,
|
|
450
483
|
logWarningsForStatusCodes,
|
|
451
484
|
respondWith404,
|