@declaro/core 2.0.0-beta.8 → 2.0.0-y.0
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/dist/app/app-context.d.ts +8 -0
- package/dist/app/app-lifecycle.d.ts +4 -0
- package/dist/app/app.d.ts +22 -0
- package/dist/app/index.d.ts +3 -20
- package/dist/auth/permission-validator.d.ts +34 -0
- package/dist/auth/permission-validator.test.d.ts +1 -0
- package/dist/context/context.d.ts +88 -13
- package/dist/context/legacy-context.test.d.ts +1 -0
- package/dist/errors/errors.d.ts +36 -0
- package/dist/events/event-manager.d.ts +11 -6
- package/dist/http/headers.d.ts +4 -0
- package/dist/http/headers.spec.d.ts +1 -0
- package/dist/http/request-context.d.ts +12 -0
- package/dist/http/request-context.spec.d.ts +1 -0
- package/dist/http/request.d.ts +8 -0
- package/dist/http/request.spec.d.ts +1 -0
- package/dist/http/url.d.ts +8 -0
- package/dist/http/url.spec.d.ts +1 -0
- package/dist/index.d.ts +9 -3
- package/dist/pkg.cjs +30 -2
- package/dist/pkg.mjs +56461 -207
- package/dist/schema/application.d.ts +83 -0
- package/dist/schema/application.test.d.ts +1 -0
- package/dist/schema/define-model.d.ts +7 -4
- package/dist/schema/index.d.ts +7 -0
- package/dist/schema/labels.d.ts +13 -0
- package/dist/schema/labels.test.d.ts +1 -0
- package/dist/schema/module.d.ts +7 -0
- package/dist/schema/module.test.d.ts +1 -0
- package/dist/schema/properties.d.ts +19 -0
- package/dist/schema/response.d.ts +31 -0
- package/dist/schema/response.test.d.ts +1 -0
- package/dist/schema/transform-model.d.ts +1 -1
- package/dist/schema/types.d.ts +81 -15
- package/dist/schema/types.test.d.ts +1 -0
- package/dist/typescript/constant-manipulation/snake-case.d.ts +22 -0
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/objects.d.ts +6 -0
- package/package.json +8 -3
- package/src/app/app-context.ts +14 -0
- package/src/app/app-lifecycle.ts +14 -0
- package/src/app/app.ts +45 -0
- package/src/app/index.ts +3 -34
- package/src/auth/permission-validator.test.ts +209 -0
- package/src/auth/permission-validator.ts +135 -0
- package/src/context/context.test.ts +585 -94
- package/src/context/context.ts +348 -32
- package/src/context/legacy-context.test.ts +141 -0
- package/src/errors/errors.ts +73 -0
- package/src/events/event-manager.spec.ts +54 -8
- package/src/events/event-manager.ts +40 -24
- package/src/http/headers.spec.ts +48 -0
- package/src/http/headers.ts +16 -0
- package/src/http/request-context.spec.ts +39 -0
- package/src/http/request-context.ts +43 -0
- package/src/http/request.spec.ts +52 -0
- package/src/http/request.ts +22 -0
- package/src/http/url.spec.ts +87 -0
- package/src/http/url.ts +48 -0
- package/src/index.ts +9 -3
- package/src/schema/application.test.ts +286 -0
- package/src/schema/application.ts +150 -0
- package/src/schema/define-model.test.ts +48 -2
- package/src/schema/define-model.ts +40 -9
- package/src/schema/index.ts +7 -0
- package/src/schema/labels.test.ts +60 -0
- package/src/schema/labels.ts +30 -0
- package/src/schema/module.test.ts +39 -0
- package/src/schema/module.ts +6 -0
- package/src/schema/properties.ts +40 -0
- package/src/schema/response.test.ts +101 -0
- package/src/schema/response.ts +93 -0
- package/src/schema/transform-model.ts +1 -1
- package/src/schema/types.test.ts +28 -0
- package/src/schema/types.ts +135 -15
- package/src/typescript/constant-manipulation/snake-case.md +496 -0
- package/src/typescript/constant-manipulation/snake-case.ts +76 -0
- package/src/typescript/index.ts +1 -0
- package/src/typescript/objects.ts +8 -5
- package/tsconfig.json +4 -1
- package/dist/context/index.d.ts +0 -3
- package/dist/interfaces/IDatastoreProvider.d.ts +0 -16
- package/dist/interfaces/IStore.d.ts +0 -4
- package/dist/interfaces/index.d.ts +0 -2
- package/dist/server/index.d.ts +0 -2
- package/src/context/index.ts +0 -3
- package/src/interfaces/IDatastoreProvider.ts +0 -23
- package/src/interfaces/IStore.ts +0 -4
- package/src/interfaces/index.ts +0 -2
- package/src/server/index.ts +0 -3
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { EventManager } from './event-manager'
|
|
1
|
+
import { EventManager, type IEvent } from './event-manager'
|
|
2
2
|
import { describe, it, expect, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
class TestEvent {
|
|
4
|
+
class TestEvent implements IEvent {
|
|
5
|
+
constructor(public readonly type: string) {}
|
|
6
|
+
}
|
|
5
7
|
|
|
6
8
|
describe('Event manager', () => {
|
|
7
9
|
it('should add listeners', async () => {
|
|
8
|
-
const eventManager = new EventManager
|
|
9
|
-
const testEvent = new TestEvent()
|
|
10
|
+
const eventManager = new EventManager()
|
|
11
|
+
const testEvent = new TestEvent('test')
|
|
10
12
|
|
|
11
13
|
const mockListener = vi.fn((event: TestEvent) => {
|
|
12
14
|
expect(event).toBe(testEvent)
|
|
@@ -14,14 +16,14 @@ describe('Event manager', () => {
|
|
|
14
16
|
|
|
15
17
|
eventManager.on('test', mockListener)
|
|
16
18
|
|
|
17
|
-
await eventManager.emitAll(
|
|
19
|
+
await eventManager.emitAll(testEvent)
|
|
18
20
|
|
|
19
21
|
expect(mockListener.mock.calls.length).toBe(1)
|
|
20
22
|
})
|
|
21
23
|
|
|
22
24
|
it('should remove listeners', async () => {
|
|
23
|
-
const eventManager = new EventManager
|
|
24
|
-
const testEvent = new TestEvent()
|
|
25
|
+
const eventManager = new EventManager()
|
|
26
|
+
const testEvent = new TestEvent('test')
|
|
25
27
|
|
|
26
28
|
const cb = (event: TestEvent) => {
|
|
27
29
|
expect(event).toBe(testEvent)
|
|
@@ -41,7 +43,7 @@ describe('Event manager', () => {
|
|
|
41
43
|
|
|
42
44
|
removeListener()
|
|
43
45
|
|
|
44
|
-
await eventManager.emitAll(
|
|
46
|
+
await eventManager.emitAll(testEvent)
|
|
45
47
|
|
|
46
48
|
expect(listener1.mock.calls.length).toBe(1)
|
|
47
49
|
expect(listener2.mock.calls.length).toBe(1)
|
|
@@ -49,4 +51,48 @@ describe('Event manager', () => {
|
|
|
49
51
|
expect(listener4.mock.calls.length).toBe(1)
|
|
50
52
|
expect(listener5.mock.calls.length).toBe(1)
|
|
51
53
|
})
|
|
54
|
+
|
|
55
|
+
it('should support listening to multiple events', async () => {
|
|
56
|
+
const eventManager = new EventManager()
|
|
57
|
+
|
|
58
|
+
const cb = (event: TestEvent) => {
|
|
59
|
+
expect(event).toBeInstanceOf(TestEvent)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const listener1 = vi.fn(cb)
|
|
63
|
+
const listener2 = vi.fn(cb)
|
|
64
|
+
|
|
65
|
+
eventManager.on(['event-1', 'event-2'], listener1)
|
|
66
|
+
eventManager.on(['event-3', 'event-4', 'event-5'], listener2)
|
|
67
|
+
|
|
68
|
+
await eventManager.emitAll(new TestEvent('event-1'))
|
|
69
|
+
await eventManager.emitAll(new TestEvent('event-2'))
|
|
70
|
+
await eventManager.emitAll(new TestEvent('event-3'))
|
|
71
|
+
await eventManager.emitAll(new TestEvent('event-4'))
|
|
72
|
+
await eventManager.emitAll(new TestEvent('event-5'))
|
|
73
|
+
|
|
74
|
+
expect(listener1.mock.calls.length).toBe(2)
|
|
75
|
+
expect(listener2.mock.calls.length).toBe(3)
|
|
76
|
+
expect(eventManager.getEvents().length).toBe(5)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should be able to subscribe to all events', async () => {
|
|
80
|
+
const eventManager = new EventManager()
|
|
81
|
+
|
|
82
|
+
const cb = (event: TestEvent) => {
|
|
83
|
+
expect(event).toBeInstanceOf(TestEvent)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const listener1 = vi.fn(cb)
|
|
87
|
+
const listener2 = vi.fn(cb)
|
|
88
|
+
|
|
89
|
+
eventManager.on(['*', 'event-1'], listener1) // This should only call the listener once
|
|
90
|
+
eventManager.on('*', listener2)
|
|
91
|
+
|
|
92
|
+
await eventManager.emitAll(new TestEvent('event-1'))
|
|
93
|
+
await eventManager.emitAll(new TestEvent('event-2'))
|
|
94
|
+
|
|
95
|
+
expect(listener1.mock.calls.length).toBe(2)
|
|
96
|
+
expect(listener2.mock.calls.length).toBe(2)
|
|
97
|
+
})
|
|
52
98
|
})
|
|
@@ -1,50 +1,66 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface IEvent {
|
|
2
|
+
type: string
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export type Listener = (event: IEvent) => any
|
|
2
6
|
|
|
3
|
-
export class EventManager<
|
|
7
|
+
export class EventManager<E extends IEvent = IEvent> {
|
|
4
8
|
protected readonly listeners: {
|
|
5
9
|
[key: string]: Listener[]
|
|
6
10
|
} = {}
|
|
7
11
|
|
|
8
12
|
getListeners(event: string) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return
|
|
13
|
+
const eventListeners = this.getListenerArray(event)
|
|
14
|
+
const globalListeners = this.listeners['*'] || []
|
|
15
|
+
|
|
16
|
+
return [...new Set([...eventListeners, ...globalListeners])]
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
getEvents() {
|
|
20
|
+
return Object.keys(this.listeners)
|
|
21
|
+
}
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const willRemove = index > -1
|
|
23
|
+
on(event: string | string[], listener: Listener) {
|
|
24
|
+
const events = Array.isArray(event) ? event : [event]
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
events.forEach((e) => {
|
|
27
|
+
this.getListenerArray(e).push(listener)
|
|
28
|
+
})
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
return () => {
|
|
31
|
+
events.forEach((e) => {
|
|
32
|
+
const index = this.getListeners(e).indexOf(listener)
|
|
33
|
+
if (index > -1) {
|
|
34
|
+
this.getListenerArray(e).splice(index, 1)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
27
37
|
}
|
|
28
38
|
}
|
|
29
39
|
|
|
30
|
-
async emitAsync(event:
|
|
31
|
-
await this.getListeners(event)
|
|
40
|
+
async emitAsync(event: E) {
|
|
41
|
+
await this.getListeners(event.type)
|
|
32
42
|
.reduce(async (promise, listener) => {
|
|
33
43
|
await promise
|
|
34
|
-
return await listener(
|
|
44
|
+
return await listener(event)
|
|
35
45
|
}, Promise.resolve())
|
|
36
46
|
.catch((e) => {
|
|
37
47
|
console.error('Error in event listener', e)
|
|
38
48
|
})
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
async emitAll(event:
|
|
42
|
-
await Promise.all(
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
async emitAll(event: E) {
|
|
52
|
+
await Promise.all(this.getListeners(event.type).map((listener) => listener(event)))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
emit(event: E) {
|
|
56
|
+
this.getListeners(event.type).forEach((listener) => listener(event))
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
this.
|
|
59
|
+
protected getListenerArray(event: string) {
|
|
60
|
+
if (!this.listeners[event]) {
|
|
61
|
+
this.listeners[event] = []
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.listeners[event]
|
|
49
65
|
}
|
|
50
66
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Context } from '../context/context'
|
|
2
|
+
import { useHeaders } from './headers'
|
|
3
|
+
import { createRequest } from 'node-mocks-http'
|
|
4
|
+
import { provideRequest } from './request'
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
describe('Http Headers', () => {
|
|
8
|
+
const request = createRequest({
|
|
9
|
+
method: 'GET',
|
|
10
|
+
url: 'https://example.com/test',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
'X-Test': 'test',
|
|
14
|
+
Authorization: 'Bearer 123',
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('Should get an empty object by default', () => {
|
|
19
|
+
const context = new Context()
|
|
20
|
+
|
|
21
|
+
const headers = useHeaders(context)
|
|
22
|
+
|
|
23
|
+
expect(Object.keys(headers).length).toEqual(0)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Provide some sample headers
|
|
27
|
+
it('Should provide headers', () => {
|
|
28
|
+
const context = new Context()
|
|
29
|
+
|
|
30
|
+
provideRequest(context, request)
|
|
31
|
+
|
|
32
|
+
const headers = useHeaders(context)
|
|
33
|
+
|
|
34
|
+
expect(headers['content-type']).toEqual('application/json')
|
|
35
|
+
expect(headers['x-test']).toEqual('test')
|
|
36
|
+
expect(headers['authorization']).toEqual('Bearer 123')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should use a single header', () => {
|
|
40
|
+
const context = new Context()
|
|
41
|
+
|
|
42
|
+
provideRequest(context, request)
|
|
43
|
+
|
|
44
|
+
const headers = useHeaders(context)
|
|
45
|
+
|
|
46
|
+
expect(headers['content-type']).toEqual('application/json')
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Context } from '../context/context'
|
|
2
|
+
import { useRequest } from './request'
|
|
3
|
+
|
|
4
|
+
export type HeaderMap = Record<string, string>
|
|
5
|
+
|
|
6
|
+
export function useHeaders(context: Context) {
|
|
7
|
+
const request = useRequest(context)
|
|
8
|
+
|
|
9
|
+
return request?.headers ?? {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useHeader(context: Context, header: string) {
|
|
13
|
+
const headers = useHeaders(context)
|
|
14
|
+
|
|
15
|
+
return headers[header]
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { Context, type ContextMiddleware } from '../context/context'
|
|
3
|
+
import { provideRequestMiddleware, useRequestMiddleware } from './request-context'
|
|
4
|
+
|
|
5
|
+
describe('Http Request Context', () => {
|
|
6
|
+
let context: Context
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
context = new Context()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('Should have nothing by default', () => {
|
|
13
|
+
const initialMiddleware = useRequestMiddleware(context)
|
|
14
|
+
expect(initialMiddleware.length).toEqual(0)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('Should provide a middleware', () => {
|
|
18
|
+
const middleware1: ContextMiddleware = (context) => {
|
|
19
|
+
context.provide('test', 'test')
|
|
20
|
+
}
|
|
21
|
+
const middleware2: ContextMiddleware = (context) => {
|
|
22
|
+
context.provide('test2', 'test2')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
provideRequestMiddleware(context, middleware1, middleware2)
|
|
26
|
+
|
|
27
|
+
const mwList = useRequestMiddleware(context)
|
|
28
|
+
|
|
29
|
+
expect(mwList.length).toEqual(2)
|
|
30
|
+
|
|
31
|
+
mwList.forEach((mw) => mw(context))
|
|
32
|
+
|
|
33
|
+
const val1 = context.inject('test')
|
|
34
|
+
const val2 = context.inject('test2')
|
|
35
|
+
|
|
36
|
+
expect(val1).toEqual('test')
|
|
37
|
+
expect(val2).toEqual('test2')
|
|
38
|
+
})
|
|
39
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context, type ContextMiddleware } from '../context/context'
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'http'
|
|
3
|
+
|
|
4
|
+
export const REQUEST_CONTEXT_MIDDLEWARE = Symbol('declaro:request-context-middleware')
|
|
5
|
+
|
|
6
|
+
export function useRequestMiddleware(context: Context) {
|
|
7
|
+
const middleware = context.inject<ContextMiddleware[]>(REQUEST_CONTEXT_MIDDLEWARE)
|
|
8
|
+
|
|
9
|
+
return middleware ?? []
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function provideRequestMiddleware(context: Context, ...middleware: ContextMiddleware[]) {
|
|
13
|
+
const existingMiddleware = useRequestMiddleware(context)
|
|
14
|
+
|
|
15
|
+
const extendedMiddleware = [...existingMiddleware, ...middleware]
|
|
16
|
+
|
|
17
|
+
context.provide(REQUEST_CONTEXT_MIDDLEWARE, extendedMiddleware)
|
|
18
|
+
|
|
19
|
+
return extendedMiddleware
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const REQUEST_NODE_MIDDLEWARE = Symbol('declaro:request-node-middleware')
|
|
23
|
+
|
|
24
|
+
export type NodeListener = (req: IncomingMessage, res: ServerResponse) => void
|
|
25
|
+
export type NodePromisifiedHandler = (req: IncomingMessage, res: ServerResponse) => Promise<any>
|
|
26
|
+
export type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => any
|
|
27
|
+
export type AllNodeMiddleware = NodeListener | NodePromisifiedHandler | NodeMiddleware
|
|
28
|
+
|
|
29
|
+
export function useNodeMiddleware(context: Context) {
|
|
30
|
+
const middleware = context.inject<AllNodeMiddleware[]>(REQUEST_NODE_MIDDLEWARE)
|
|
31
|
+
|
|
32
|
+
return middleware ?? []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function provideNodeMiddleware(context: Context, ...middleware: AllNodeMiddleware[]) {
|
|
36
|
+
const existingMiddleware = useNodeMiddleware(context)
|
|
37
|
+
|
|
38
|
+
const extendedMiddleware = [...existingMiddleware, ...middleware]
|
|
39
|
+
|
|
40
|
+
context.provide(REQUEST_NODE_MIDDLEWARE, extendedMiddleware)
|
|
41
|
+
|
|
42
|
+
return extendedMiddleware
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Context } from '../context/context'
|
|
2
|
+
import { createRequest } from 'node-mocks-http'
|
|
3
|
+
import { provideRequest, useRequest, useRequestMethod } from './request'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
describe('Http Request', () => {
|
|
7
|
+
it('Should get undefined by default', () => {
|
|
8
|
+
const context = new Context()
|
|
9
|
+
|
|
10
|
+
const request = useRequest(context)
|
|
11
|
+
|
|
12
|
+
expect(request).toBeUndefined()
|
|
13
|
+
})
|
|
14
|
+
it('Should provide a request', () => {
|
|
15
|
+
const context = new Context()
|
|
16
|
+
|
|
17
|
+
const request = createRequest({
|
|
18
|
+
method: 'GET',
|
|
19
|
+
url: '/test',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
'X-Test': 'test',
|
|
23
|
+
Authorization: 'Bearer 123',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
provideRequest(context, request)
|
|
28
|
+
|
|
29
|
+
const request2 = useRequest(context)
|
|
30
|
+
|
|
31
|
+
expect(request2).toEqual(request)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('Should get the method', () => {
|
|
35
|
+
const context = new Context()
|
|
36
|
+
|
|
37
|
+
const request = createRequest({
|
|
38
|
+
method: 'GET',
|
|
39
|
+
url: '/test',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-Test': 'test',
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
provideRequest(context, request)
|
|
47
|
+
|
|
48
|
+
const method = useRequestMethod(context)
|
|
49
|
+
|
|
50
|
+
expect(method).toEqual('GET')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Context } from '../context/context'
|
|
2
|
+
import type { IncomingMessage } from 'http'
|
|
3
|
+
|
|
4
|
+
export const REQUEST = Symbol('declaro:request')
|
|
5
|
+
|
|
6
|
+
export interface Request extends IncomingMessage {}
|
|
7
|
+
|
|
8
|
+
export function provideRequest(context: Context, request: Request) {
|
|
9
|
+
context.provide(REQUEST, request)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useRequest(context: Context) {
|
|
13
|
+
const request = context.inject<Request | undefined>(REQUEST)
|
|
14
|
+
|
|
15
|
+
return request
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useRequestMethod(context: Context) {
|
|
19
|
+
const request = useRequest(context)
|
|
20
|
+
|
|
21
|
+
return request?.method
|
|
22
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Tests for url.ts:
|
|
2
|
+
// Create tests for the following functions:
|
|
3
|
+
// - useURL: make sure it returns undefined by default, and the URL object if provided
|
|
4
|
+
// - provideURL: make sure you can provide a URL object or a string
|
|
5
|
+
// - useURLString: make sure it returns the stringified URL
|
|
6
|
+
// - useURLDomain: make sure it returns the domain part of the URL provided
|
|
7
|
+
// - useURLPath
|
|
8
|
+
|
|
9
|
+
import { Context } from '../context/context'
|
|
10
|
+
import { useRequestDomain, useRequestPath, useRequestQuery, useRequestURL, useRequestURLString } from './url'
|
|
11
|
+
import { provideRequest } from './request'
|
|
12
|
+
import { createRequest } from 'node-mocks-http'
|
|
13
|
+
import { describe, expect, it } from 'vitest'
|
|
14
|
+
|
|
15
|
+
// - useURLQueryString
|
|
16
|
+
describe('Http URL', () => {
|
|
17
|
+
const testUrlString = 'https://example.com/test?a=1&b=2'
|
|
18
|
+
const testUrlObject = new URL(testUrlString)
|
|
19
|
+
const request = createRequest({
|
|
20
|
+
method: 'GET',
|
|
21
|
+
url: testUrlString,
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
'X-Test': 'test',
|
|
25
|
+
Authorization: 'Bearer 123',
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('Should get undefined by default', () => {
|
|
30
|
+
const context = new Context()
|
|
31
|
+
|
|
32
|
+
const url = useRequestURL(context)
|
|
33
|
+
|
|
34
|
+
expect(url).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('Should get the URL object if provided', () => {
|
|
38
|
+
const context = new Context()
|
|
39
|
+
|
|
40
|
+
provideRequest(context, request)
|
|
41
|
+
|
|
42
|
+
const url2 = useRequestURL(context)
|
|
43
|
+
|
|
44
|
+
expect(url2).toEqual(testUrlObject)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('Should get the stringified URL', () => {
|
|
48
|
+
const context = new Context()
|
|
49
|
+
|
|
50
|
+
provideRequest(context, request)
|
|
51
|
+
|
|
52
|
+
const url2 = useRequestURLString(context)
|
|
53
|
+
|
|
54
|
+
expect(url2).toEqual(testUrlString)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('Should get the domain part of the URL', () => {
|
|
58
|
+
const context = new Context()
|
|
59
|
+
|
|
60
|
+
provideRequest(context, request)
|
|
61
|
+
|
|
62
|
+
const domain = useRequestDomain(context)
|
|
63
|
+
|
|
64
|
+
expect(domain).toEqual('example.com')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('Should get the path part of the URL', () => {
|
|
68
|
+
const context = new Context()
|
|
69
|
+
|
|
70
|
+
provideRequest(context, request)
|
|
71
|
+
|
|
72
|
+
const path = useRequestPath(context)
|
|
73
|
+
|
|
74
|
+
expect(path).toEqual('/test')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('Should get the query string part of the URL', () => {
|
|
78
|
+
const context = new Context()
|
|
79
|
+
|
|
80
|
+
provideRequest(context, request)
|
|
81
|
+
|
|
82
|
+
const query = useRequestQuery(context)
|
|
83
|
+
|
|
84
|
+
expect(query['a']).toEqual('1')
|
|
85
|
+
expect(query['b']).toEqual('2')
|
|
86
|
+
})
|
|
87
|
+
})
|
package/src/http/url.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Context } from '../context/context'
|
|
2
|
+
import { URL } from 'whatwg-url'
|
|
3
|
+
import { useRequest } from './request'
|
|
4
|
+
|
|
5
|
+
export function useRequestURL(context: Context) {
|
|
6
|
+
const request = useRequest(context)
|
|
7
|
+
const url = request?.url && new URL(request?.url)
|
|
8
|
+
|
|
9
|
+
return url
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useRequestURLString(context: Context) {
|
|
13
|
+
const url = useRequestURL(context)
|
|
14
|
+
|
|
15
|
+
return url?.toString()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useRequestDomain(context: Context) {
|
|
19
|
+
const url = useRequestURL(context)
|
|
20
|
+
|
|
21
|
+
return url?.hostname
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useRequestPath(context: Context) {
|
|
25
|
+
const url = useRequestURL(context)
|
|
26
|
+
|
|
27
|
+
return url?.pathname
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useRequestQuery(context: Context): Record<string, string> {
|
|
31
|
+
const url = useRequestURL(context)
|
|
32
|
+
|
|
33
|
+
const qs = url?.searchParams
|
|
34
|
+
|
|
35
|
+
// convert qs to an object of key value pairs
|
|
36
|
+
const qsMap = [...qs?.entries()].reduce((qsMap, [key, value]) => {
|
|
37
|
+
qsMap[key] = value
|
|
38
|
+
return qsMap
|
|
39
|
+
}, {} as Record<string, string>)
|
|
40
|
+
|
|
41
|
+
return qsMap
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useFormattedQueryParam(context: Context, key: string) {
|
|
45
|
+
const qs = useRequestQuery(context)
|
|
46
|
+
|
|
47
|
+
const value = qs[key]
|
|
48
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
export * from './typescript'
|
|
2
2
|
export * from './app'
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './context'
|
|
3
|
+
export * from './context/context'
|
|
4
|
+
export * from './context/context-consumer'
|
|
5
|
+
export * from './context/validators'
|
|
5
6
|
export * from './dataflow'
|
|
6
7
|
export * from './events'
|
|
7
8
|
export * from './validation'
|
|
8
9
|
export * from './timing'
|
|
9
10
|
export * from './schema'
|
|
10
11
|
export * from './helpers'
|
|
11
|
-
export * from './
|
|
12
|
+
export * from './errors/errors'
|
|
12
13
|
export * from './schema/formats'
|
|
13
14
|
export * from './pipelines'
|
|
15
|
+
export * from './http/headers'
|
|
16
|
+
export * from './http/request-context'
|
|
17
|
+
export * from './http/request'
|
|
18
|
+
export * from './http/url'
|
|
19
|
+
export * from './auth/permission-validator'
|