@pikku/core 0.6.21 → 0.6.22

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 (62) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/channel/channel-handler.d.ts +2 -2
  3. package/dist/channel/channel-handler.js +49 -26
  4. package/dist/channel/channel-runner.d.ts +3 -3
  5. package/dist/channel/channel-runner.js +4 -4
  6. package/dist/channel/channel.types.d.ts +12 -19
  7. package/dist/channel/local/local-channel-runner.d.ts +1 -1
  8. package/dist/channel/local/local-channel-runner.js +14 -8
  9. package/dist/channel/serverless/serverless-channel-runner.js +12 -7
  10. package/dist/handle-error.js +8 -6
  11. package/dist/http/http-route-runner.d.ts +75 -18
  12. package/dist/http/http-route-runner.js +154 -64
  13. package/dist/http/http-routes.types.d.ts +24 -9
  14. package/dist/http/incomingmessage-to-request-convertor.d.ts +1 -0
  15. package/dist/http/incomingmessage-to-request-convertor.js +1 -0
  16. package/dist/http/index.d.ts +4 -3
  17. package/dist/http/index.js +4 -3
  18. package/dist/http/{pikku-http-abstract-request.d.ts → pikku-fetch-http-request.d.ts} +14 -20
  19. package/dist/http/pikku-fetch-http-request.js +92 -0
  20. package/dist/http/pikku-fetch-http-response.d.ts +14 -0
  21. package/dist/http/pikku-fetch-http-response.js +59 -0
  22. package/dist/middleware/auth-apikey.js +4 -4
  23. package/dist/middleware/auth-bearer.js +4 -4
  24. package/dist/middleware/auth-cookie.js +16 -25
  25. package/dist/middleware-runner.d.ts +1 -1
  26. package/dist/pikku-request.d.ts +3 -3
  27. package/dist/pikku-request.js +5 -5
  28. package/dist/scheduler/scheduler-runner.d.ts +1 -1
  29. package/dist/services/user-session-service.d.ts +9 -14
  30. package/dist/services/user-session-service.js +22 -22
  31. package/dist/types/core.types.d.ts +1 -1
  32. package/dist/types/functions.types.d.ts +7 -2
  33. package/lcov.info +1543 -1420
  34. package/package.json +3 -4
  35. package/src/channel/channel-handler.ts +82 -29
  36. package/src/channel/channel-runner.ts +6 -6
  37. package/src/channel/channel.types.ts +19 -23
  38. package/src/channel/local/local-channel-runner.test.ts +68 -23
  39. package/src/channel/local/local-channel-runner.ts +23 -9
  40. package/src/channel/serverless/serverless-channel-runner.ts +15 -6
  41. package/src/handle-error.ts +8 -6
  42. package/src/http/http-route-runner.test.ts +13 -57
  43. package/src/http/http-route-runner.ts +184 -92
  44. package/src/http/http-routes.types.ts +26 -9
  45. package/src/http/incomingmessage-to-request-convertor.ts +0 -0
  46. package/src/http/index.ts +4 -5
  47. package/src/http/{pikku-http-abstract-request.ts → pikku-fetch-http-request.ts} +48 -39
  48. package/src/http/pikku-fetch-http-response.ts +70 -0
  49. package/src/middleware/auth-apikey.ts +4 -4
  50. package/src/middleware/auth-bearer.ts +4 -4
  51. package/src/middleware/auth-cookie.ts +20 -28
  52. package/src/middleware-runner.ts +1 -1
  53. package/src/pikku-request.ts +8 -4
  54. package/src/scheduler/scheduler-runner.ts +0 -2
  55. package/src/services/user-session-service.ts +26 -28
  56. package/src/types/core.types.ts +1 -1
  57. package/src/types/functions.types.ts +19 -4
  58. package/tsconfig.tsbuildinfo +1 -1
  59. package/dist/http/pikku-http-abstract-request.js +0 -76
  60. package/dist/http/pikku-http-abstract-response.d.ts +0 -58
  61. package/dist/http/pikku-http-abstract-response.js +0 -54
  62. package/src/http/pikku-http-abstract-response.ts +0 -79
package/src/http/index.ts CHANGED
@@ -1,9 +1,8 @@
1
- export * from './pikku-http-abstract-request.js'
2
-
3
- export * from './pikku-http-abstract-response.js'
4
-
1
+ export * from './pikku-fetch-http-request.js'
2
+ export * from './pikku-fetch-http-response.js'
3
+ export * from './incomingmessage-to-request-convertor.js'
5
4
  export * from './log-http-routes.js'
6
5
 
7
- export { runHTTPRoute, addRoute } from './http-route-runner.js'
6
+ export { fetch, fetchData, addRoute } from './http-route-runner.js'
8
7
 
9
8
  export type * from './http-routes.types.js'
@@ -1,36 +1,49 @@
1
+ import { parse as parseQuery } from 'picoquery'
1
2
  import { parse as parseCookie } from 'cookie'
2
- import { PikkuRequest } from '../pikku-request.js'
3
- import { HTTPMethod, PikkuQuery } from './http-routes.types.js'
3
+ import {
4
+ HTTPMethod,
5
+ PikkuHTTPRequest,
6
+ PikkuQuery,
7
+ } from './http-routes.types.js'
4
8
 
5
9
  /**
6
10
  * Abstract class representing a pikku request.
7
11
  * @template In - The type of the request body.
8
12
  * @group RequestResponse
9
13
  */
10
- export abstract class PikkuHTTPAbstractRequest<
11
- In = unknown,
12
- > extends PikkuRequest<In> {
13
- private params: Partial<Record<string, string | string[]>> = {}
14
+ export class PikkuFetchHTTPRequest<In = unknown>
15
+ implements PikkuHTTPRequest<In>
16
+ {
17
+ #cookies: Partial<Record<string, string>> | undefined
18
+ #params: Partial<Record<string, string | string[]>> = {}
19
+ #url: URL
14
20
 
15
- constructor(
16
- public path: string,
17
- public method: HTTPMethod
18
- ) {
19
- super()
21
+ constructor(private request: Request) {
22
+ this.#url = new URL(request.url)
23
+ }
24
+
25
+ public method(): HTTPMethod {
26
+ return this.request.method.toLowerCase() as HTTPMethod
27
+ }
28
+
29
+ public path(): string {
30
+ return this.#url.pathname
20
31
  }
21
32
 
22
33
  /**
23
34
  * Retrieves the request body.
24
35
  * @returns A promise that resolves to the request body.
25
36
  */
26
- public abstract getBody(): Promise<In>
37
+ public json(): Promise<In> {
38
+ return this.request.json()
39
+ }
27
40
 
28
41
  /**
29
42
  * Retrieves the raw request body as a Buffer.
30
43
  * @returns A promise that resolves to the raw request body.
31
44
  */
32
- public getRawBody(): Promise<Buffer> {
33
- throw new Error('Method not implemented.')
45
+ public arrayBuffer(): Promise<ArrayBuffer> {
46
+ return this.request.arrayBuffer()
34
47
  }
35
48
 
36
49
  /**
@@ -38,26 +51,26 @@ export abstract class PikkuHTTPAbstractRequest<
38
51
  * @param headerName - The name of the header to retrieve.
39
52
  * @returns The value of the header, or undefined if the header is not found.
40
53
  */
41
- public abstract getHeader(headerName: string): string | undefined
54
+ public header(headerName: string): string | null {
55
+ return this.request.headers.get(headerName.toLowerCase())
56
+ }
42
57
 
43
58
  /**
44
59
  * Retrieves the cookies from the request.
45
60
  * @returns An object containing the cookies.
46
61
  */
47
- public getCookies(): Partial<Record<string, string>> {
48
- const cookieHeader = this.getHeader('cookie')
49
- if (cookieHeader) {
50
- return parseCookie(cookieHeader)
51
- }
52
- return {}
62
+ public cookie(cookieName: string): string | null {
63
+ const cookieHeader = this.header('cookie')
64
+ this.#cookies = cookieHeader ? parseCookie(cookieHeader) : {}
65
+ return this.#cookies[cookieName] || null
53
66
  }
54
67
 
55
68
  /**
56
69
  * Retrieves the request parameters.
57
70
  * @returns An object containing the request parameters.
58
71
  */
59
- public getParams(): Partial<Record<string, string | string[]>> {
60
- return this.params
72
+ public params(): Partial<Record<string, string | string[]>> {
73
+ return this.#params
61
74
  }
62
75
 
63
76
  /**
@@ -67,36 +80,32 @@ export abstract class PikkuHTTPAbstractRequest<
67
80
  public setParams(
68
81
  params: Record<string, string | string[] | undefined>
69
82
  ): void {
70
- this.params = params
83
+ this.#params = params
71
84
  }
72
85
 
73
86
  /**
74
87
  * Retrieves the query parameters from the request.
75
88
  * @returns An object containing the query parameters.
76
89
  */
77
- public getQuery(): PikkuQuery {
78
- return {}
79
- }
80
-
81
- /**
82
- * Retrieves the IP address of the client making the request.
83
- * @returns The IP address of the client.
84
- * @throws {Error} This method is not implemented and should be overridden by subclasses.
85
- */
86
- public getIP(): string {
87
- throw new Error('Method not implemented.')
90
+ public query(): PikkuQuery {
91
+ return parseQuery(this.#url.searchParams.toString()) as PikkuQuery
88
92
  }
89
93
 
90
94
  /**
91
95
  * Retrieves the combined data from the request, including parameters, query, and body.
92
96
  * @returns A promise that resolves to an object containing the combined data.
93
97
  */
94
- public async getData(): Promise<In> {
98
+ public async data(): Promise<In> {
99
+ let body: any = {}
100
+ try {
101
+ body = await this.json()
102
+ } catch (e) {}
103
+
95
104
  return {
96
- ...this.getParams(),
97
- ...this.getQuery(),
105
+ ...this.params(),
106
+ ...this.query(),
98
107
  // TODO: If body isn't an object, we should insert it as the word...
99
- ...(await this.getBody()),
108
+ ...body,
100
109
  }
101
110
  }
102
111
  }
@@ -0,0 +1,70 @@
1
+ import { PikkuHTTPResponse } from './http-routes.types.js'
2
+
3
+ export class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
4
+ #statusCode: number = 200
5
+ #headers = new Headers()
6
+ #body: BodyInit | null = null
7
+
8
+ public status(code: number): this {
9
+ this.#statusCode = code
10
+ return this
11
+ }
12
+
13
+ public cookie(name: string, value: string, flags: any): this {
14
+ // TODO
15
+ return this
16
+ }
17
+
18
+ public header(name: string, value: string | string[]): this {
19
+ this.#headers.delete(name)
20
+ if (Array.isArray(value)) {
21
+ value.forEach((v) => this.#headers.append(name, v))
22
+ } else {
23
+ this.#headers.set(name, value)
24
+ }
25
+ return this
26
+ }
27
+
28
+ public arrayBuffer(data: XMLHttpRequestBodyInit): this {
29
+ this.#body = data
30
+ this.header('Content-Type', 'application/octet-stream')
31
+ return this
32
+ }
33
+
34
+ public json(data: unknown): this {
35
+ this.#body = JSON.stringify(data)
36
+ this.header('Content-Type', 'application/json')
37
+ return this
38
+ }
39
+
40
+ public text(content: string): this {
41
+ this.#body = content
42
+ this.header('Content-Type', 'text/plain')
43
+ return this
44
+ }
45
+
46
+ public html(content: string): this {
47
+ this.#body = content
48
+ this.header('Content-Type', 'text/html')
49
+ return this
50
+ }
51
+
52
+ public body(body: BodyInit): this {
53
+ this.#body = body
54
+ return this
55
+ }
56
+
57
+ public redirect(location: string, status: number = 302): this {
58
+ this.#statusCode = status
59
+ this.header('Location', location)
60
+ return this
61
+ }
62
+
63
+ public toResponse(args?: Record<string, any>): Response {
64
+ return new Response(this.#body, {
65
+ ...args,
66
+ status: this.#statusCode,
67
+ headers: this.#headers,
68
+ })
69
+ }
70
+ }
@@ -33,16 +33,16 @@ export const authAPIKey = <
33
33
  }
34
34
  )) => {
35
35
  const middleware: PikkuMiddleware = async (services, { http }, next) => {
36
- if (!http?.request || services.userSession.get()) {
36
+ if (!http?.request || services.userSessionService.get()) {
37
37
  return next()
38
38
  }
39
39
 
40
40
  let apiKey: string | null = null
41
41
  if (source === 'header' || source === 'all') {
42
- apiKey = http.request.getHeader('x-api-key') as string | null
42
+ apiKey = http.request.header('x-api-key') as string | null
43
43
  }
44
44
  if (!apiKey && (source === 'query' || source === 'all')) {
45
- apiKey = http.request.getQuery().apiKey as string | null
45
+ apiKey = http.request.query().apiKey as string | null
46
46
  }
47
47
  if (apiKey) {
48
48
  let userSession: UserSession | null = null
@@ -55,7 +55,7 @@ export const authAPIKey = <
55
55
  userSession = await getSessionForAPIKey!(services as any, apiKey)
56
56
  }
57
57
  if (userSession) {
58
- await services.userSession.set(userSession)
58
+ services.userSessionService.setInitial(userSession)
59
59
  }
60
60
  }
61
61
  return next()
@@ -29,13 +29,13 @@ export const authBearer = <
29
29
  } = {}): PikkuMiddleware => {
30
30
  const middleware: PikkuMiddleware = async (services, { http }, next) => {
31
31
  // Skip if session already exists.
32
- if (!http?.request || services.userSession.get()) {
32
+ if (!http?.request || services.userSessionService.get()) {
33
33
  return next()
34
34
  }
35
35
 
36
36
  const authHeader =
37
- http.request.getHeader('authorization') ||
38
- http.request.getHeader('Authorization')
37
+ http.request.header('authorization') ||
38
+ http.request.header('Authorization')
39
39
  if (authHeader) {
40
40
  const [scheme, bearerToken] = authHeader.split(' ')
41
41
  if (scheme !== 'Bearer' || !token || !bearerToken) {
@@ -56,7 +56,7 @@ export const authBearer = <
56
56
  }
57
57
 
58
58
  if (userSession) {
59
- await services.userSession.set(userSession)
59
+ services.userSessionService.setInitial(userSession)
60
60
  }
61
61
  }
62
62
  return next()
@@ -35,41 +35,33 @@ export const authCookie = <
35
35
  }
36
36
  )): PikkuMiddleware => {
37
37
  const middleware: PikkuMiddleware = async (services, { http }, next) => {
38
- if (!http?.request || services.userSession.get()) {
38
+ if (!http?.request || services.userSessionService.get()) {
39
39
  return next()
40
40
  }
41
41
 
42
- const cookies = http.request.getCookies()
43
- if (cookies) {
44
- let cookieName: string | undefined
45
- for (const name of cookieNames) {
46
- if (cookies[name]) {
47
- cookieName = name
48
- break
49
- }
50
- }
51
- if (cookieName) {
52
- let userSession: UserSession | null = null
53
- const cookieValue = cookies[cookieName]
54
- if (cookieValue) {
55
- if (jwt) {
56
- if (!services.jwt) {
57
- throw new Error('JWT service is required for JWT decoding.')
58
- }
59
- userSession = await services.jwt.decode(cookieValue)
60
- } else if (getSessionForCookieValue) {
61
- userSession = await getSessionForCookieValue(
62
- services as any,
63
- cookieValue,
64
- cookieName
65
- )
66
- }
67
- if (userSession) {
68
- await services.userSession.set(userSession)
42
+ let userSession: UserSession | null = null
43
+ for (const cookieName of cookieNames) {
44
+ const cookieValue = http.request.cookie(cookieName)
45
+ if (cookieValue) {
46
+ if (jwt) {
47
+ if (!services.jwt) {
48
+ throw new Error('JWT service is required for JWT decoding.')
69
49
  }
50
+ userSession = await services.jwt.decode(cookieValue)
51
+ } else if (getSessionForCookieValue) {
52
+ userSession = await getSessionForCookieValue(
53
+ services as any,
54
+ cookieValue,
55
+ cookieName
56
+ )
70
57
  }
58
+ break
71
59
  }
72
60
  }
61
+
62
+ if (userSession) {
63
+ services.userSessionService.setInitial(userSession)
64
+ }
73
65
  return next()
74
66
  }
75
67
  return middleware
@@ -24,7 +24,7 @@ import {
24
24
  */
25
25
  export const runMiddleware = async (
26
26
  services: CoreSingletonServices & {
27
- userSession: UserSessionService<any>
27
+ userSessionService: UserSessionService<any>
28
28
  },
29
29
  interaction: PikkuInteraction,
30
30
  middlewares: PikkuMiddleware[],
@@ -4,16 +4,20 @@
4
4
  * @group RequestResponse
5
5
  */
6
6
  export abstract class PikkuRequest<In = any> {
7
- constructor(private data?: In) {}
7
+ #data: In | undefined
8
+
9
+ constructor(data: In) {
10
+ this.#data = data
11
+ }
8
12
 
9
13
  /**
10
14
  * Retrieves the data
11
15
  * @returns A promise that resolves to an object containing the combined data.
12
16
  */
13
- public async getData(): Promise<In> {
14
- if (!this.data) {
17
+ public async data(): Promise<In> {
18
+ if (!this.#data) {
15
19
  throw new Error('Data not found')
16
20
  }
17
- return this.data
21
+ return this.#data
18
22
  }
19
23
  }
@@ -42,8 +42,6 @@ class ScheduledTaskNotFoundError extends Error {
42
42
  export async function runScheduledTask<
43
43
  SingletonServices extends CoreSingletonServices = CoreSingletonServices,
44
44
  UserSession extends CoreUserSession = CoreUserSession,
45
- Services extends
46
- CoreServices<SingletonServices> = CoreServices<SingletonServices>,
47
45
  >({
48
46
  name,
49
47
  session,
@@ -2,50 +2,48 @@ import { ChannelStore } from '../channel/channel-store.js'
2
2
  import { CoreUserSession } from '../types/core.types.js'
3
3
 
4
4
  export interface UserSessionService<UserSession extends CoreUserSession> {
5
+ setInitial(session: UserSession): void
5
6
  set(session: UserSession): Promise<void> | void
6
7
  clear(): Promise<void> | void
7
8
  get(): Promise<UserSession | undefined> | UserSession | undefined
8
9
  }
9
10
 
10
- export class LocalUserSessionService<UserSession extends CoreUserSession>
11
+ export class PikkuUserSessionService<UserSession extends CoreUserSession>
11
12
  implements UserSessionService<UserSession>
12
13
  {
13
- constructor(private session: UserSession | undefined = undefined) {}
14
-
15
- public set(session: UserSession) {
16
- this.session = session
17
- }
18
-
19
- public clear() {
20
- this.session = undefined
14
+ private session: UserSession | undefined
15
+ constructor(
16
+ private channelStore?: ChannelStore<unknown, unknown, UserSession>,
17
+ private channelId?: string
18
+ ) {
19
+ if (channelStore && !channelId) {
20
+ throw new Error('Channel ID is required when using channel store')
21
+ }
21
22
  }
22
23
 
23
- public get(): UserSession | undefined {
24
- return this.session
24
+ public setInitial(session: UserSession) {
25
+ this.session = session
25
26
  }
26
- }
27
-
28
- export class RemoteUserSessionService<UserSession extends CoreUserSession>
29
- implements UserSessionService<UserSession>
30
- {
31
- constructor(
32
- private channelStore: ChannelStore<unknown, unknown, UserSession>,
33
- private channelId: string,
34
- public session: UserSession | undefined = undefined
35
- ) {}
36
27
 
37
28
  public set(session: UserSession) {
38
- return this.channelStore.setUserSession(this.channelId, session)
29
+ this.session = session
30
+ return this.channelStore?.setUserSession(this.channelId!, session)
39
31
  }
40
32
 
41
33
  public clear() {
42
- return this.channelStore.setUserSession(this.channelId, null)
34
+ this.session = undefined
35
+ return this.channelStore?.setUserSession(this.channelId!, null)
43
36
  }
44
37
 
45
- public async get(): Promise<UserSession | undefined> {
46
- const { session } = await this.channelStore.getChannelAndSession(
47
- this.channelId
48
- )
49
- return session
38
+ public get(): Promise<UserSession> | UserSession | undefined {
39
+ if (this.channelStore) {
40
+ const channel = this.channelStore.getChannelAndSession(this.channelId!)
41
+ if (channel instanceof Promise) {
42
+ return channel.then(({ session }) => session)
43
+ } else {
44
+ return channel.session
45
+ }
46
+ }
47
+ return this.session
50
48
  }
51
49
  }
@@ -93,7 +93,7 @@ export type PikkuMiddleware<
93
93
  UserSession extends CoreUserSession = CoreUserSession,
94
94
  > = (
95
95
  services: SingletonServices & {
96
- userSession: UserSessionService<UserSession>
96
+ userSessionService: UserSessionService<UserSession>
97
97
  },
98
98
  interactions: PikkuInteraction,
99
99
  next: () => Promise<void>
@@ -1,3 +1,4 @@
1
+ import { PikkuChannel } from '../channel/channel.types.js'
1
2
  import type {
2
3
  CoreServices,
3
4
  CoreSingletonServices,
@@ -15,9 +16,16 @@ import type {
15
16
  export type CoreAPIFunction<
16
17
  In,
17
18
  Out,
18
- Services extends CoreSingletonServices = CoreServices,
19
+ Services extends CoreSingletonServices = CoreServices & {
20
+ channel?: PikkuChannel<unknown, Out>
21
+ },
19
22
  Session extends CoreUserSession = CoreUserSession,
20
- > = (services: Services, data: In, session: Session) => Promise<Out>
23
+ Channel extends boolean = false,
24
+ > = (
25
+ services: Services,
26
+ data: In,
27
+ session: Session
28
+ ) => Channel extends true ? Promise<Out> | Promise<void> : Promise<Out>
21
29
 
22
30
  /**
23
31
  * Represents a core API function that can be used without a session.
@@ -30,9 +38,16 @@ export type CoreAPIFunction<
30
38
  export type CoreAPIFunctionSessionless<
31
39
  In,
32
40
  Out,
33
- Services extends CoreSingletonServices = CoreServices,
41
+ Services extends CoreSingletonServices = CoreServices & {
42
+ channel?: PikkuChannel<unknown, Out>
43
+ },
34
44
  Session extends CoreUserSession = CoreUserSession,
35
- > = (services: Services, data: In, session?: Session) => Promise<Out>
45
+ Channel extends boolean = false,
46
+ > = (
47
+ services: Services,
48
+ data: In,
49
+ session: Session
50
+ ) => Channel extends true ? Promise<Out> | Promise<void> : Promise<Out>
36
51
 
37
52
  /**
38
53
  * Represents a function that checks permissions for a given API operation.