@pikku/core 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/CHANGELOG.md +339 -0
  2. package/README.md +5 -0
  3. package/dist/channel/channel-handler.d.ts +3 -0
  4. package/dist/channel/channel-handler.js +97 -0
  5. package/dist/channel/channel-runner.d.ts +26 -0
  6. package/dist/channel/channel-runner.js +86 -0
  7. package/dist/channel/channel-store.d.ts +14 -0
  8. package/dist/channel/channel-store.js +2 -0
  9. package/dist/channel/channel.types.d.ts +106 -0
  10. package/dist/channel/channel.types.js +1 -0
  11. package/dist/channel/eventhub-forwarder.d.ts +17 -0
  12. package/dist/channel/eventhub-forwarder.js +1 -0
  13. package/dist/channel/eventhub-service.d.ts +23 -0
  14. package/dist/channel/eventhub-service.js +1 -0
  15. package/dist/channel/eventhub-store.d.ts +5 -0
  16. package/dist/channel/eventhub-store.js +2 -0
  17. package/dist/channel/index.d.ts +9 -0
  18. package/dist/channel/index.js +9 -0
  19. package/dist/channel/local/index.d.ts +3 -0
  20. package/dist/channel/local/index.js +3 -0
  21. package/dist/channel/local/local-channel-handler.d.ts +17 -0
  22. package/dist/channel/local/local-channel-handler.js +41 -0
  23. package/dist/channel/local/local-channel-runner.d.ts +3 -0
  24. package/dist/channel/local/local-channel-runner.js +46 -0
  25. package/dist/channel/local/local-eventhub-service.d.ts +44 -0
  26. package/dist/channel/local/local-eventhub-service.js +81 -0
  27. package/dist/channel/log-channels.d.ts +6 -0
  28. package/dist/channel/log-channels.js +17 -0
  29. package/dist/channel/pikku-abstract-channel-handler.d.ts +15 -0
  30. package/dist/channel/pikku-abstract-channel-handler.js +33 -0
  31. package/dist/channel/serverless/index.d.ts +1 -0
  32. package/dist/channel/serverless/index.js +1 -0
  33. package/dist/channel/serverless/serverless-channel-runner.d.ts +10 -0
  34. package/dist/channel/serverless/serverless-channel-runner.js +98 -0
  35. package/dist/errors/error-handler.d.ts +39 -0
  36. package/dist/errors/error-handler.js +54 -0
  37. package/dist/errors/errors.d.ts +190 -0
  38. package/dist/errors/errors.js +315 -0
  39. package/dist/errors/index.d.ts +2 -0
  40. package/dist/errors/index.js +2 -0
  41. package/dist/http/http-route-runner.d.ts +30 -0
  42. package/dist/http/http-route-runner.js +208 -0
  43. package/dist/http/http-routes.types.d.ts +127 -0
  44. package/dist/http/http-routes.types.js +1 -0
  45. package/dist/http/http-session-service.d.ts +15 -0
  46. package/dist/http/http-session-service.js +1 -0
  47. package/dist/http/index.d.ts +6 -0
  48. package/dist/http/index.js +5 -0
  49. package/dist/http/log-http-routes.d.ts +6 -0
  50. package/dist/http/log-http-routes.js +17 -0
  51. package/dist/http/pikku-http-abstract-request.d.ts +57 -0
  52. package/dist/http/pikku-http-abstract-request.js +69 -0
  53. package/dist/http/pikku-http-abstract-response.d.ts +58 -0
  54. package/dist/http/pikku-http-abstract-response.js +54 -0
  55. package/dist/http/pikku-http-session-service.d.ts +45 -0
  56. package/dist/http/pikku-http-session-service.js +92 -0
  57. package/dist/index.d.ts +14 -0
  58. package/dist/index.js +14 -0
  59. package/dist/permissions.d.ts +10 -0
  60. package/dist/permissions.js +32 -0
  61. package/dist/pikku-request.d.ts +14 -0
  62. package/dist/pikku-request.js +21 -0
  63. package/dist/pikku-response.d.ts +6 -0
  64. package/dist/pikku-response.js +6 -0
  65. package/dist/scheduler/index.d.ts +3 -0
  66. package/dist/scheduler/index.js +3 -0
  67. package/dist/scheduler/log-schedulers.d.ts +6 -0
  68. package/dist/scheduler/log-schedulers.js +17 -0
  69. package/dist/scheduler/scheduler-runner.d.ts +21 -0
  70. package/dist/scheduler/scheduler-runner.js +73 -0
  71. package/dist/scheduler/scheduler.types.d.ts +21 -0
  72. package/dist/scheduler/scheduler.types.js +1 -0
  73. package/dist/schema.d.ts +26 -0
  74. package/dist/schema.js +100 -0
  75. package/dist/services/content-service.d.ts +55 -0
  76. package/dist/services/content-service.js +1 -0
  77. package/dist/services/index.d.ts +12 -0
  78. package/dist/services/index.js +14 -0
  79. package/dist/services/jwt-service.d.ts +29 -0
  80. package/dist/services/jwt-service.js +1 -0
  81. package/dist/services/local-content.d.ts +33 -0
  82. package/dist/services/local-content.js +6 -0
  83. package/dist/services/local-secrets.d.ts +26 -0
  84. package/dist/services/local-secrets.js +39 -0
  85. package/dist/services/local-variables.d.ts +7 -0
  86. package/dist/services/local-variables.js +12 -0
  87. package/dist/services/logger-console.d.ts +52 -0
  88. package/dist/services/logger-console.js +82 -0
  89. package/dist/services/logger.d.ts +48 -0
  90. package/dist/services/logger.js +9 -0
  91. package/dist/services/schema-service.d.ts +24 -0
  92. package/dist/services/schema-service.js +1 -0
  93. package/dist/services/secret-service.d.ts +17 -0
  94. package/dist/services/secret-service.js +1 -0
  95. package/dist/services/user-session-service.d.ts +6 -0
  96. package/dist/services/user-session-service.js +1 -0
  97. package/dist/services/variables-service.d.ts +4 -0
  98. package/dist/services/variables-service.js +1 -0
  99. package/dist/types/core.types.d.ts +109 -0
  100. package/dist/types/core.types.js +1 -0
  101. package/dist/types/functions.types.d.ts +27 -0
  102. package/dist/types/functions.types.js +1 -0
  103. package/dist/utils.d.ts +2 -0
  104. package/dist/utils.js +15 -0
  105. package/package.json +48 -0
  106. package/run-tests.sh +53 -0
  107. package/src/channel/channel-handler.ts +179 -0
  108. package/src/channel/channel-runner.ts +156 -0
  109. package/src/channel/channel-store.ts +32 -0
  110. package/src/channel/channel.types.ts +202 -0
  111. package/src/channel/eventhub-forwarder.ts +25 -0
  112. package/src/channel/eventhub-service.ts +30 -0
  113. package/src/channel/eventhub-store.ts +8 -0
  114. package/src/channel/index.ts +9 -0
  115. package/src/channel/local/index.ts +3 -0
  116. package/src/channel/local/local-channel-handler.ts +56 -0
  117. package/src/channel/local/local-channel-runner.test.ts +128 -0
  118. package/src/channel/local/local-channel-runner.ts +94 -0
  119. package/src/channel/local/local-eventhub-service.test.ts +95 -0
  120. package/src/channel/local/local-eventhub-service.ts +95 -0
  121. package/src/channel/log-channels.ts +20 -0
  122. package/src/channel/pikku-abstract-channel-handler.test.ts +77 -0
  123. package/src/channel/pikku-abstract-channel-handler.ts +44 -0
  124. package/src/channel/serverless/index.ts +1 -0
  125. package/src/channel/serverless/serverless-channel-runner.ts +190 -0
  126. package/src/errors/error-handler.ts +74 -0
  127. package/src/errors/error.test.ts +54 -0
  128. package/src/errors/errors.ts +340 -0
  129. package/src/errors/index.ts +2 -0
  130. package/src/http/http-route-runner.test.ts +193 -0
  131. package/src/http/http-route-runner.ts +365 -0
  132. package/src/http/http-routes.types.ts +189 -0
  133. package/src/http/http-session-service.ts +19 -0
  134. package/src/http/index.ts +16 -0
  135. package/src/http/log-http-routes.ts +20 -0
  136. package/src/http/pikku-http-abstract-request.ts +95 -0
  137. package/src/http/pikku-http-abstract-response.ts +79 -0
  138. package/src/http/pikku-http-session-service.test.ts +106 -0
  139. package/src/http/pikku-http-session-service.ts +135 -0
  140. package/src/index.ts +15 -0
  141. package/src/permissions.test.ts +58 -0
  142. package/src/permissions.ts +45 -0
  143. package/src/pikku-request.ts +19 -0
  144. package/src/pikku-response.ts +5 -0
  145. package/src/scheduler/index.ts +11 -0
  146. package/src/scheduler/log-schedulers.ts +20 -0
  147. package/src/scheduler/scheduler-runner.ts +132 -0
  148. package/src/scheduler/scheduler.types.ts +27 -0
  149. package/src/schema.test.ts +57 -0
  150. package/src/schema.ts +124 -0
  151. package/src/services/content-service.ts +69 -0
  152. package/src/services/index.ts +16 -0
  153. package/src/services/jwt-service.ts +36 -0
  154. package/src/services/local-content.ts +39 -0
  155. package/src/services/local-secrets.ts +43 -0
  156. package/src/services/local-variables.ts +17 -0
  157. package/src/services/logger-console.ts +97 -0
  158. package/src/services/logger.ts +57 -0
  159. package/src/services/schema-service.ts +26 -0
  160. package/src/services/secret-service.ts +17 -0
  161. package/src/services/user-session-service.ts +7 -0
  162. package/src/services/variables-service.ts +6 -0
  163. package/src/types/core.types.ts +162 -0
  164. package/src/types/functions.types.ts +44 -0
  165. package/src/utils.ts +21 -0
  166. package/tsconfig.json +13 -0
  167. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,189 @@
1
+ import { EError } from '../errors/error-handler.js'
2
+ import {
3
+ APIDocs,
4
+ CoreServices,
5
+ CoreSingletonServices,
6
+ CoreUserSession,
7
+ CreateSessionServices,
8
+ } from '../types/core.types.js'
9
+ import {
10
+ CoreAPIFunction,
11
+ CoreAPIFunctionSessionless,
12
+ CoreAPIPermission,
13
+ } from '../types/functions.types.js'
14
+ import { PikkuRequest } from '../pikku-request.js'
15
+ import { PikkuResponse } from '../pikku-response.js'
16
+ import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
17
+ import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
18
+
19
+ type ExtractRouteParams<S extends string> =
20
+ S extends `${string}:${infer Param}/${infer Rest}`
21
+ ? Param | ExtractRouteParams<`/${Rest}`>
22
+ : S extends `${string}:${infer Param}`
23
+ ? Param
24
+ : never
25
+
26
+ export type AssertRouteParams<In, Route extends string> =
27
+ ExtractRouteParams<Route> extends keyof In
28
+ ? unknown
29
+ : ['Error: Route parameters', ExtractRouteParams<Route>, 'not in', keyof In]
30
+
31
+ export type RunRouteOptions = Partial<{
32
+ skipUserSession: boolean
33
+ respondWith404: boolean
34
+ logWarningsForStatusCodes: number[]
35
+ coerceToArray: boolean
36
+ bubbleErrors: boolean
37
+ }>
38
+
39
+ export type RunRouteParams<In> = {
40
+ singletonServices: CoreSingletonServices
41
+ request: PikkuRequest<In> | PikkuHTTPAbstractRequest<In>
42
+ response?: PikkuResponse | PikkuHTTPAbstractResponse | undefined
43
+ createSessionServices: CreateSessionServices<
44
+ CoreSingletonServices,
45
+ CoreServices<CoreSingletonServices>,
46
+ CoreUserSession
47
+ >
48
+ }
49
+
50
+ /**
51
+ * Represents the HTTP methods supported for API routes.
52
+ */
53
+ export type HTTPMethod =
54
+ | 'post'
55
+ | 'get'
56
+ | 'delete'
57
+ | 'patch'
58
+ | 'head'
59
+ | 'put'
60
+ | 'options'
61
+
62
+ /**
63
+ * Represents an API route without a function, including metadata such as content type, route, and timeout settings.
64
+ */
65
+ export type CoreHTTPFunction = {
66
+ contentType?: 'xml' | 'json'
67
+ route: string
68
+ eventChannel?: false
69
+ returnsJSON?: false
70
+ timeout?: number
71
+ docs?: Partial<{
72
+ description: string
73
+ response: string
74
+ errors: Array<typeof EError>
75
+ tags: string[]
76
+ }>
77
+ }
78
+ /**
79
+ * Represents a http interaction within Pikku, including a request and response.
80
+ */
81
+ export interface PikkuHTTP {
82
+ request?: PikkuHTTPAbstractRequest
83
+ response?: PikkuHTTPAbstractResponse
84
+ }
85
+
86
+ /**
87
+ * Represents request headers as either a record or a function to get headers by name.
88
+ */
89
+ export type RequestHeaders =
90
+ | Record<string, string | string[] | undefined>
91
+ | ((headerName: string) => string | string[] | undefined)
92
+
93
+ /**
94
+ * Represents a query object for Pikku, where each key can be a string, a value, or an array of values.
95
+ */
96
+ export type PikkuQuery<T = Record<string, string | undefined>> = Record<
97
+ string,
98
+ string | T | null | Array<T | null>
99
+ >
100
+
101
+ /**
102
+ * Represents a core API route, which can have different configurations depending on whether it requires authentication and permissions.
103
+ *
104
+ * @template In - The input type.
105
+ * @template Out - The output type.
106
+ * @template R - The route string type.
107
+ * @template APIFunction - The API function type, defaults to `CoreAPIFunction`.
108
+ * @template APIFunctionSessionless - The sessionless API function type, defaults to `CoreAPIFunctionSessionless`.
109
+ * @template APIPermission - The permission function type, defaults to `CoreAPIPermission`.
110
+ */
111
+ export type CoreHTTPFunctionRoute<
112
+ In,
113
+ Out,
114
+ R extends string,
115
+ APIFunction = CoreAPIFunction<In, Out>,
116
+ APIFunctionSessionless = CoreAPIFunctionSessionless<In, Out>,
117
+ APIPermission = CoreAPIPermission<In>,
118
+ > =
119
+ | (CoreHTTPFunction & {
120
+ route: R
121
+ method: HTTPMethod
122
+ func: APIFunction
123
+ permissions?: Record<string, APIPermission[] | APIPermission>
124
+ auth?: true
125
+ })
126
+ | (CoreHTTPFunction & {
127
+ route: R
128
+ method: HTTPMethod
129
+ func: APIFunctionSessionless
130
+ permissions?: undefined
131
+ auth?: false
132
+ })
133
+ | (CoreHTTPFunction & {
134
+ route: R
135
+ method: 'post'
136
+ func: APIFunction
137
+ permissions?: Record<string, APIPermission[] | APIPermission>
138
+ auth?: true
139
+ query?: Array<keyof In>
140
+ })
141
+ | (CoreHTTPFunction & {
142
+ route: R
143
+ method: 'post'
144
+ func: APIFunctionSessionless
145
+ permissions?: undefined
146
+ auth?: false
147
+ query?: Array<keyof In>
148
+ })
149
+
150
+ /**
151
+ * Represents an array of core API routes.
152
+ */
153
+ export type CoreHTTPFunctionRoutes = Array<
154
+ CoreHTTPFunctionRoute<any, any, string>
155
+ >
156
+
157
+ /**
158
+ * Represents the input types for route metadata, including parameters, query, and body types.
159
+ */
160
+ export type HTTPFunctionMetaInputTypes = {
161
+ params?: string
162
+ query?: string
163
+ body?: string
164
+ }
165
+
166
+ /**
167
+ * Represents metadata for a set of routes, including route details, methods, input/output types, and documentation.
168
+ */
169
+ export type HTTPRoutesMeta = Array<{
170
+ route: string
171
+ method: HTTPMethod
172
+ params?: string[]
173
+ query?: string[]
174
+ input: string | null
175
+ output: string | null
176
+ inputTypes?: HTTPFunctionMetaInputTypes
177
+ docs?: APIDocs
178
+ }>
179
+
180
+ /**
181
+ * Verifies access to a route.
182
+ * @param route - The route to verify access for.
183
+ * @param session - The user session.
184
+ * @returns A promise that resolves if access is granted.
185
+ */
186
+ export type enforceHTTPAccess = (
187
+ route: CoreHTTPFunctionRoute<unknown, unknown, any>,
188
+ session?: CoreUserSession
189
+ ) => Promise<void> | void
@@ -0,0 +1,19 @@
1
+ import { CoreUserSession } from '../types/core.types.js'
2
+ import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
3
+
4
+ /**
5
+ * Interface for handling user sessions.
6
+ * @template UserSession - The type of the user session.
7
+ */
8
+ export interface HTTPSessionService<UserSession = CoreUserSession> {
9
+ /**
10
+ * Retrieves the user session.
11
+ * @param credentialsRequired - A flag indicating whether credentials are required.
12
+ * @param pikkuRequest - The request object.
13
+ * @returns A promise that resolves to the user session or undefined.
14
+ */
15
+ getUserSession: (
16
+ credentialsRequired: boolean,
17
+ pikkuRequest: PikkuHTTPAbstractRequest
18
+ ) => Promise<UserSession | undefined>
19
+ }
@@ -0,0 +1,16 @@
1
+ export * from './pikku-http-session-service.js'
2
+
3
+ export * from './pikku-http-abstract-request.js'
4
+
5
+ export * from './pikku-http-abstract-response.js'
6
+
7
+ export * from './log-http-routes.js'
8
+
9
+ export {
10
+ runHTTPRoute,
11
+ getRoutes,
12
+ setHTTPRoutesMeta,
13
+ addRoute,
14
+ } from './http-route-runner.js'
15
+
16
+ export type * from './http-routes.types.js'
@@ -0,0 +1,20 @@
1
+ import { Logger } from '../services/index.js'
2
+ import { getRoutes } from './http-route-runner.js'
3
+
4
+ /**
5
+ * Logs all the loaded routes.
6
+ * @param logger - A logger for logging information.
7
+ */
8
+ export const logRoutes = (logger: Logger) => {
9
+ const { routes } = getRoutes()
10
+ if (routes.length === 0) {
11
+ logger.info('No routes added')
12
+ return
13
+ }
14
+
15
+ let routesMessage = 'Routes loaded:'
16
+ for (const { method, route } of routes) {
17
+ routesMessage += `\n\t- ${method.toUpperCase()} -> ${route}`
18
+ }
19
+ logger.info(routesMessage)
20
+ }
@@ -0,0 +1,95 @@
1
+ import { parse as parseCookie } from 'cookie'
2
+ import { PikkuRequest } from '../pikku-request.js'
3
+ import { PikkuQuery } from './http-routes.types.js'
4
+
5
+ /**
6
+ * Abstract class representing a pikku request.
7
+ * @template In - The type of the request body.
8
+ * @group RequestResponse
9
+ */
10
+ export abstract class PikkuHTTPAbstractRequest<
11
+ In = unknown,
12
+ > extends PikkuRequest<In> {
13
+ private params: Partial<Record<string, string | string[]>> = {}
14
+
15
+ /**
16
+ * Retrieves the request body.
17
+ * @returns A promise that resolves to the request body.
18
+ */
19
+ public abstract getBody(): Promise<In>
20
+
21
+ /**
22
+ * Retrieves the raw request body as a Buffer.
23
+ * @returns A promise that resolves to the raw request body.
24
+ */
25
+ public getRawBody(): Promise<Buffer> {
26
+ throw new Error('Method not implemented.')
27
+ }
28
+
29
+ /**
30
+ * Retrieves the value of a specific header.
31
+ * @param headerName - The name of the header to retrieve.
32
+ * @returns The value of the header, or undefined if the header is not found.
33
+ */
34
+ public abstract getHeader(headerName: string): string | undefined
35
+
36
+ /**
37
+ * Retrieves the cookies from the request.
38
+ * @returns An object containing the cookies.
39
+ */
40
+ public getCookies(): Partial<Record<string, string>> {
41
+ const cookieHeader = this.getHeader('cookie')
42
+ if (cookieHeader) {
43
+ return parseCookie(cookieHeader)
44
+ }
45
+ return {}
46
+ }
47
+
48
+ /**
49
+ * Retrieves the request parameters.
50
+ * @returns An object containing the request parameters.
51
+ */
52
+ public getParams(): Partial<Record<string, string | string[]>> {
53
+ return this.params
54
+ }
55
+
56
+ /**
57
+ * Sets the request parameters.
58
+ * @param params - An object containing the request parameters to set.
59
+ */
60
+ public setParams(
61
+ params: Record<string, string | string[] | undefined>
62
+ ): void {
63
+ this.params = params
64
+ }
65
+
66
+ /**
67
+ * Retrieves the query parameters from the request.
68
+ * @returns An object containing the query parameters.
69
+ */
70
+ public getQuery(): PikkuQuery {
71
+ return {}
72
+ }
73
+
74
+ /**
75
+ * Retrieves the IP address of the client making the request.
76
+ * @returns The IP address of the client.
77
+ * @throws {Error} This method is not implemented and should be overridden by subclasses.
78
+ */
79
+ public getIP(): string {
80
+ throw new Error('Method not implemented.')
81
+ }
82
+
83
+ /**
84
+ * Retrieves the combined data from the request, including parameters, query, and body.
85
+ * @returns A promise that resolves to an object containing the combined data.
86
+ */
87
+ public async getData(): Promise<In> {
88
+ return {
89
+ ...this.getParams(),
90
+ ...this.getQuery(),
91
+ // TODO: If body isn't an object, we should insert it as the word...
92
+ ...(await this.getBody()),
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,79 @@
1
+ import { SerializeOptions, serialize } from 'cookie'
2
+ import { JSONValue } from '../types/core.types.js'
3
+ import { PikkuResponse } from '../pikku-response.js'
4
+
5
+ /**
6
+ * Abstract class representing a pikku response.
7
+ * @group RequestResponse
8
+ */
9
+ export abstract class PikkuHTTPAbstractResponse extends PikkuResponse {
10
+ /**
11
+ * Sets the HTTP status code for the response.
12
+ * @param status - The HTTP status code to set.
13
+ */
14
+ public abstract setStatus(status: number): void
15
+
16
+ /**
17
+ * Sets the JSON body for the response.
18
+ * @param body - The JSON body to set.
19
+ */
20
+ public abstract setJson(body: JSONValue): void
21
+
22
+ /**
23
+ * Sets the response content.
24
+ * @param response - The response content, which can be a string or a Buffer.
25
+ */
26
+ public abstract setResponse(response: string | Buffer): void
27
+
28
+ /**
29
+ * Sets a header for the response.
30
+ * @param name - The name of the header.
31
+ * @param value - The value of the header, which can be a string, boolean, or an array of strings.
32
+ */
33
+ public setHeader(name: string, value: string | boolean | string[]) {
34
+ throw new Error('Method not implemented.')
35
+ }
36
+
37
+ /**
38
+ * Sets multiple headers for the response.
39
+ * @param headers - An object containing header names and values.
40
+ */
41
+ public setHeaders(headers: Record<string, string>) {
42
+ for (const [name, value] of Object.entries(headers)) {
43
+ this.setHeader(name, value)
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Sets a cookie for the response.
49
+ * @param name - The name of the cookie.
50
+ * @param value - The value of the cookie.
51
+ * @param options - Options for cookie serialization.
52
+ */
53
+ public setCookie(name: string, value: string, options: SerializeOptions) {
54
+ this.setHeader('set-cookie', serialize(name, value, options))
55
+ }
56
+
57
+ /**
58
+ * Clears a cookie from the response.
59
+ * @param name - The name of the cookie to clear.
60
+ */
61
+ public clearCookie(name: string): void {
62
+ this.setHeader('set-cookie', serialize(name, '', { expires: new Date(0) }))
63
+ }
64
+
65
+ /**
66
+ * Sets a redirect for the response.
67
+ * @param path - The path to redirect to.
68
+ * @param status - The HTTP status code for the redirect.
69
+ */
70
+ public setRedirect(path: string, status: number) {
71
+ throw new Error('Method not implemented.')
72
+ }
73
+
74
+ /**
75
+ * Informs the response that it has ended, useful for when setting
76
+ * a status without a body or response.
77
+ */
78
+ public end() {}
79
+ }
@@ -0,0 +1,106 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { InvalidSessionError, MissingSessionError } from '../errors/errors.js'
4
+ import { PikkuHTTPSessionService } from './pikku-http-session-service.js'
5
+
6
+ test('PikkuHTTPSessionService: Handles JWT-based sessions', async () => {
7
+ const mockJWTService: any = {
8
+ decode: async (token) => {
9
+ if (token === 'validToken') return { id: 1, name: 'Test User' }
10
+ throw new InvalidSessionError()
11
+ },
12
+ }
13
+
14
+ const sessionService = new PikkuHTTPSessionService(mockJWTService, {})
15
+ const mockRequest: any = {
16
+ getHeader: (name) =>
17
+ name === 'authorization' ? 'Bearer validToken' : undefined,
18
+ getCookies: () => ({}),
19
+ }
20
+
21
+ const session = await sessionService.getUserSession(true, mockRequest)
22
+ assert.deepEqual(session, { id: 1, name: 'Test User' })
23
+ })
24
+
25
+ test('PikkuHTTPSessionService: Handles API key-based sessions', async () => {
26
+ const mockJWTService: any = { decode: async () => undefined }
27
+ const mockOptions = {
28
+ getSessionForAPIKey: async (apiKey) => {
29
+ return apiKey === 'validAPIKey' ? { id: 2, name: 'API User' } : undefined
30
+ },
31
+ }
32
+
33
+ const sessionService = new PikkuHTTPSessionService(
34
+ mockJWTService,
35
+ mockOptions
36
+ )
37
+ const mockRequest: any = {
38
+ getHeader: (name) => (name === 'x-api-key' ? 'validAPIKey' : undefined),
39
+ getCookies: () => ({}),
40
+ }
41
+
42
+ const session = await sessionService.getUserSession(true, mockRequest)
43
+ assert.deepEqual(session, { id: 2, name: 'API User' })
44
+ })
45
+
46
+ test('PikkuHTTPSessionService: Handles cookie-based sessions', async () => {
47
+ const mockJWTService: any = { decode: async () => undefined }
48
+ const mockOptions = {
49
+ cookieNames: ['session'],
50
+ getSessionForCookieValue: async (cookieValue, cookieName) => {
51
+ if (cookieValue === 'validCookie' && cookieName === 'session') {
52
+ return { id: 3, name: 'Cookie User' }
53
+ }
54
+ return undefined
55
+ },
56
+ }
57
+
58
+ const sessionService = new PikkuHTTPSessionService(
59
+ mockJWTService,
60
+ mockOptions
61
+ )
62
+ const mockRequest: any = {
63
+ getHeader: () => undefined,
64
+ getCookies: () => ({ session: 'validCookie' }),
65
+ }
66
+
67
+ const session = await sessionService.getUserSession(true, mockRequest)
68
+ assert.deepEqual(session, { id: 3, name: 'Cookie User' })
69
+ })
70
+
71
+ test('PikkuHTTPSessionService: Throws MissingSessionError when credentials are required but missing', async () => {
72
+ const mockJWTService: any = { decode: async () => undefined }
73
+ const sessionService = new PikkuHTTPSessionService(mockJWTService, {})
74
+
75
+ const mockRequest: any = {
76
+ getHeader: () => undefined,
77
+ getCookies: () => ({}),
78
+ }
79
+
80
+ await assert.rejects(
81
+ sessionService.getUserSession(true, mockRequest),
82
+ MissingSessionError
83
+ )
84
+ })
85
+
86
+ test('PikkuHTTPSessionService: Transforms session when transformSession is provided', async () => {
87
+ const mockJWTService: any = {
88
+ decode: async () => ({ id: 1, name: 'Original User' }),
89
+ }
90
+ const mockOptions = {
91
+ transformSession: async (session) => ({ ...session, transformed: true }),
92
+ }
93
+
94
+ const sessionService = new PikkuHTTPSessionService(
95
+ mockJWTService,
96
+ mockOptions
97
+ )
98
+ const mockRequest: any = {
99
+ getHeader: (name) =>
100
+ name === 'authorization' ? 'Bearer validToken' : undefined,
101
+ getCookies: () => ({}),
102
+ }
103
+
104
+ const session = await sessionService.getUserSession(true, mockRequest)
105
+ assert.deepEqual(session, { id: 1, name: 'Original User', transformed: true })
106
+ })
@@ -0,0 +1,135 @@
1
+ import { HTTPSessionService } from './http-session-service.js'
2
+ import { JWTService } from '../services/jwt-service.js'
3
+ import { InvalidSessionError, MissingSessionError } from '../errors/errors.js'
4
+ import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
5
+ import { PikkuQuery } from './http-routes.types.js'
6
+
7
+ /**
8
+ * The `PikkuHTTPSessionService` class provides session management capabilities, including handling JWT-based sessions,
9
+ * cookie-based sessions, and API key-based sessions. It allows for retrieving and transforming user sessions based on different
10
+ * authentication mechanisms.
11
+ *
12
+ * @template UserSession - The type representing a user session.
13
+ */
14
+ export class PikkuHTTPSessionService<UserSession>
15
+ implements HTTPSessionService<UserSession>
16
+ {
17
+ /**
18
+ * Constructs a new instance of the `PikkuHTTPSessionService` class.
19
+ *
20
+ * @param jwtService - The service for handling JWT operations.
21
+ * @param options - Options for configuring the session service.
22
+ */
23
+ constructor(
24
+ private jwtService: JWTService<UserSession>,
25
+ private options: {
26
+ cookieNames?: string[]
27
+ getSessionForCookieValue?: (
28
+ cookieValue: string,
29
+ cookieName: string
30
+ ) => Promise<UserSession>
31
+ getSessionForQueryValue?: (
32
+ queryValue: PikkuQuery
33
+ ) => Promise<UserSession> | undefined
34
+ getSessionForAPIKey?: (apiKey: string) => Promise<UserSession> | undefined
35
+ transformSession?: (session: any) => Promise<UserSession>
36
+ }
37
+ ) {}
38
+
39
+ /**
40
+ * Retrieves a session from cookies if available.
41
+ *
42
+ * @param request - The request object containing cookies.
43
+ * @returns A promise that resolves to the user session, or `undefined` if no session is found.
44
+ */
45
+ private async getCookieSession(
46
+ request: PikkuHTTPAbstractRequest
47
+ ): Promise<UserSession | undefined> {
48
+ const cookies = request.getCookies()
49
+ if (!cookies) {
50
+ return
51
+ }
52
+
53
+ let cookieName: string | undefined
54
+ if (this.options.cookieNames) {
55
+ for (const name of this.options.cookieNames) {
56
+ if (cookies[name]) {
57
+ cookieName = name
58
+ }
59
+ }
60
+ }
61
+
62
+ if (!cookieName) {
63
+ return
64
+ }
65
+
66
+ const cookieValue = cookies[cookieName]
67
+ if (!cookieValue) {
68
+ return
69
+ }
70
+
71
+ if (!this.options.getSessionForCookieValue) {
72
+ return
73
+ }
74
+
75
+ return await this.options.getSessionForCookieValue(cookieValue, cookieName)
76
+ }
77
+
78
+ /**
79
+ * Retrieves the user session based on available credentials (JWT, API key, or cookies).
80
+ *
81
+ * @param credentialsRequired - Whether credentials are required to proceed.
82
+ * @param request - The request object containing headers and cookies.
83
+ * @param debugJWTDecode - Whether to enable debugging for JWT decoding.
84
+ * @returns A promise that resolves to the user session, or `undefined` if no session is found and credentials are not required.
85
+ * @throws {MissingSessionError} - Throws an error if credentials are required but no session is found.
86
+ */
87
+ public async getUserSession(
88
+ credentialsRequired: boolean,
89
+ request: PikkuHTTPAbstractRequest,
90
+ debugJWTDecode?: boolean
91
+ ): Promise<UserSession | undefined> {
92
+ let userSession: UserSession | undefined
93
+
94
+ const authorization =
95
+ request.getHeader('authorization') || request.getHeader('Authorization')
96
+ if (authorization) {
97
+ const [part1, part2] = authorization.split(' ')
98
+ if (part1 !== 'Bearer' || !part2) {
99
+ throw new InvalidSessionError()
100
+ }
101
+ userSession = await this.jwtService.decode(
102
+ part2,
103
+ undefined,
104
+ debugJWTDecode
105
+ )
106
+ }
107
+
108
+ if (!userSession && this.options.getSessionForAPIKey) {
109
+ const apiKey = request.getHeader('x-api-key')
110
+ if (apiKey) {
111
+ userSession = await this.options.getSessionForAPIKey(apiKey)
112
+ }
113
+ }
114
+
115
+ if (!userSession && this.options.getSessionForCookieValue) {
116
+ userSession = await this.getCookieSession(request)
117
+ }
118
+
119
+ if (!userSession && this.options.getSessionForQueryValue) {
120
+ userSession = await this.options.getSessionForQueryValue(
121
+ request.getQuery()
122
+ )
123
+ }
124
+
125
+ if (!userSession && credentialsRequired) {
126
+ throw new MissingSessionError()
127
+ }
128
+
129
+ if (userSession && this.options.transformSession) {
130
+ return await this.options.transformSession(userSession)
131
+ }
132
+
133
+ return userSession
134
+ }
135
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @module @pikku/core
3
+ */
4
+ export * from './types/core.types.js'
5
+ export * from './types/functions.types.js'
6
+ export * from './pikku-request.js'
7
+ export * from './pikku-response.js'
8
+ export * from './services/index.js'
9
+ export * from './http/index.js'
10
+ export * from './channel/index.js'
11
+ export * from './scheduler/index.js'
12
+
13
+ export { addRoute } from './http/http-route-runner.js'
14
+ export { addChannel } from './channel/channel-runner.js'
15
+ export { addScheduledTask } from './scheduler/scheduler-runner.js'