@furystack/rest-service 8.0.0 → 9.0.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/esm/actions/login.d.ts +0 -1
- package/esm/actions/login.d.ts.map +1 -1
- package/esm/actions/login.js +0 -1
- package/esm/actions/login.js.map +1 -1
- package/esm/add-cors-header.d.ts +14 -0
- package/esm/add-cors-header.d.ts.map +1 -0
- package/esm/add-cors-header.js +23 -0
- package/esm/add-cors-header.js.map +1 -0
- package/esm/add-cors-header.spec.js +7 -8
- package/esm/add-cors-header.spec.js.map +1 -1
- package/esm/api-manager.d.ts.map +1 -1
- package/esm/api-manager.js +4 -5
- package/esm/api-manager.js.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.d.ts +0 -1
- package/esm/endpoint-generators/create-delete-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.js +0 -1
- package/esm/endpoint-generators/create-delete-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.spec.js +4 -2
- package/esm/endpoint-generators/create-delete-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.d.ts +0 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.js +0 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.spec.js +16 -10
- package/esm/endpoint-generators/create-get-collection-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.d.ts +0 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.js +0 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.spec.js +10 -6
- package/esm/endpoint-generators/create-get-entity-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.d.ts +0 -1
- package/esm/endpoint-generators/create-patch-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.js +2 -2
- package/esm/endpoint-generators/create-patch-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.spec.js +4 -2
- package/esm/endpoint-generators/create-patch-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.d.ts +0 -1
- package/esm/endpoint-generators/create-post-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.js +2 -2
- package/esm/endpoint-generators/create-post-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.spec.js +4 -2
- package/esm/endpoint-generators/create-post-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/utils.d.ts.map +1 -1
- package/esm/endpoint-generators/utils.js +0 -2
- package/esm/endpoint-generators/utils.js.map +1 -1
- package/esm/http-user-context.d.ts.map +1 -1
- package/esm/http-user-context.js +0 -3
- package/esm/http-user-context.js.map +1 -1
- package/esm/http-user-context.spec.js +1 -2
- package/esm/http-user-context.spec.js.map +1 -1
- package/esm/incoming-message-extensions.d.ts +0 -2
- package/esm/incoming-message-extensions.d.ts.map +1 -1
- package/esm/incoming-message-extensions.js +1 -10
- package/esm/incoming-message-extensions.js.map +1 -1
- package/esm/index.d.ts +2 -2
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -2
- package/esm/index.js.map +1 -1
- package/esm/models/default-session.d.ts.map +1 -1
- package/esm/models/default-session.js +0 -8
- package/esm/models/default-session.js.map +1 -1
- package/esm/read-post-body.d.ts +12 -0
- package/esm/read-post-body.d.ts.map +1 -0
- package/esm/read-post-body.js +33 -0
- package/esm/read-post-body.js.map +1 -0
- package/esm/rest-service.integration.spec.js +23 -30
- package/esm/rest-service.integration.spec.js.map +1 -1
- package/esm/rest.integration.test.js +1 -1
- package/esm/static-server-manager.d.ts.map +1 -1
- package/esm/static-server-manager.js +0 -1
- package/esm/static-server-manager.js.map +1 -1
- package/esm/static-server-manager.spec.js +2 -15
- package/esm/static-server-manager.spec.js.map +1 -1
- package/esm/validate.integration.spec.js +2 -2
- package/esm/validate.integration.spec.js.map +1 -1
- package/package.json +10 -10
- package/src/actions/login.ts +0 -1
- package/src/add-cors-header.spec.ts +7 -8
- package/src/add-cors-header.ts +32 -0
- package/src/api-manager.ts +5 -5
- package/src/endpoint-generators/create-delete-endpoint.spec.ts +4 -2
- package/src/endpoint-generators/create-delete-endpoint.ts +0 -1
- package/src/endpoint-generators/create-get-collection-endpoint.spec.ts +16 -10
- package/src/endpoint-generators/create-get-collection-endpoint.ts +0 -1
- package/src/endpoint-generators/create-get-entity-endpoint.spec.ts +10 -6
- package/src/endpoint-generators/create-get-entity-endpoint.ts +0 -1
- package/src/endpoint-generators/create-patch-endpoint.spec.ts +4 -2
- package/src/endpoint-generators/create-patch-endpoint.ts +2 -2
- package/src/endpoint-generators/create-post-endpoint.spec.ts +4 -2
- package/src/endpoint-generators/create-post-endpoint.ts +2 -2
- package/src/endpoint-generators/utils.ts +2 -2
- package/src/http-user-context.spec.ts +1 -2
- package/src/http-user-context.ts +3 -3
- package/src/incoming-message-extensions.ts +0 -15
- package/src/index.ts +2 -2
- package/src/models/default-session.ts +2 -2
- package/src/read-post-body.ts +36 -0
- package/src/rest-service.integration.spec.ts +24 -34
- package/src/rest.integration.test.ts +1 -1
- package/src/static-server-manager.spec.ts +2 -18
- package/src/static-server-manager.ts +1 -1
- package/src/validate.integration.spec.ts +2 -2
- package/esm/incoming-message-extensions.spec.d.ts +0 -2
- package/esm/incoming-message-extensions.spec.d.ts.map +0 -1
- package/esm/incoming-message-extensions.spec.js +0 -38
- package/esm/incoming-message-extensions.spec.js.map +0 -1
- package/esm/utils.d.ts +0 -25
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -70
- package/esm/utils.js.map +0 -1
- package/src/incoming-message-extensions.spec.ts +0 -42
- package/src/utils.ts +0 -68
|
@@ -8,6 +8,7 @@ import type { FindOptions } from '@furystack/core'
|
|
|
8
8
|
import { getDataSetFor, getRepository } from '@furystack/repository'
|
|
9
9
|
import { useRestService } from '../helpers.js'
|
|
10
10
|
import { describe, it, expect } from 'vitest'
|
|
11
|
+
import { getPort } from '@furystack/core/port-generator'
|
|
11
12
|
|
|
12
13
|
const addMockEntities = async (i: Injector) =>
|
|
13
14
|
await getRepository(i)
|
|
@@ -24,10 +25,11 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
24
25
|
it('Should return the collection without filter / order', async () => {
|
|
25
26
|
await usingAsync(new Injector(), async (i) => {
|
|
26
27
|
setupContext(i)
|
|
28
|
+
const port = getPort()
|
|
27
29
|
await useRestService<{ GET: { '/entities': GetCollectionEndpoint<MockClass> } }>({
|
|
28
30
|
injector: i,
|
|
29
31
|
root: '/api',
|
|
30
|
-
port
|
|
32
|
+
port,
|
|
31
33
|
api: {
|
|
32
34
|
GET: {
|
|
33
35
|
'/entities': createGetCollectionEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -39,7 +41,7 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
39
41
|
const count = await getDataSetFor(i, MockClass, 'id').count(i)
|
|
40
42
|
const allEntities = await getDataSetFor(i, MockClass, 'id').find(i, {})
|
|
41
43
|
|
|
42
|
-
const response = await fetch(
|
|
44
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/entities`, { method: 'GET' })
|
|
43
45
|
expect(response.ok).toBe(true)
|
|
44
46
|
const json: GetCollectionResult<MockClass> = await response.json()
|
|
45
47
|
expect(response.status).toBe(200)
|
|
@@ -51,10 +53,11 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
51
53
|
it('Should return entities in order', async () => {
|
|
52
54
|
await usingAsync(new Injector(), async (i) => {
|
|
53
55
|
setupContext(i)
|
|
56
|
+
const port = getPort()
|
|
54
57
|
await useRestService<{ GET: { '/entities': GetCollectionEndpoint<MockClass> } }>({
|
|
55
58
|
injector: i,
|
|
56
59
|
root: '/api',
|
|
57
|
-
port
|
|
60
|
+
port,
|
|
58
61
|
api: {
|
|
59
62
|
GET: {
|
|
60
63
|
'/entities': createGetCollectionEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -65,7 +68,7 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
65
68
|
const findOptions: FindOptions<MockClass, Array<keyof MockClass>> = { order: { value: 'ASC' } }
|
|
66
69
|
const count = await getDataSetFor(i, MockClass, 'id').count(i, findOptions.filter)
|
|
67
70
|
const orderedEntities = await getDataSetFor(i, MockClass, 'id').find(i, findOptions)
|
|
68
|
-
const response = await fetch(`http://127.0.0.1
|
|
71
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/entities?${serializeToQueryString({ findOptions })}`, {
|
|
69
72
|
method: 'GET',
|
|
70
73
|
})
|
|
71
74
|
expect(response.ok).toBe(true)
|
|
@@ -79,10 +82,11 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
79
82
|
it('Should return entities with filtering', async () => {
|
|
80
83
|
await usingAsync(new Injector(), async (i) => {
|
|
81
84
|
setupContext(i)
|
|
85
|
+
const port = getPort()
|
|
82
86
|
await useRestService<{ GET: { '/entities': GetCollectionEndpoint<MockClass> } }>({
|
|
83
87
|
injector: i,
|
|
84
88
|
root: '/api',
|
|
85
|
-
port
|
|
89
|
+
port,
|
|
86
90
|
api: {
|
|
87
91
|
GET: {
|
|
88
92
|
'/entities': createGetCollectionEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -99,7 +103,7 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
99
103
|
|
|
100
104
|
expect(filteredEntities).not.toContainEqual({ id: 'mock2', value: '3' })
|
|
101
105
|
|
|
102
|
-
const response = await fetch(`http://127.0.0.1
|
|
106
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/entities?${serializeToQueryString({ findOptions })}`, {
|
|
103
107
|
method: 'GET',
|
|
104
108
|
})
|
|
105
109
|
expect(response.ok).toBe(true)
|
|
@@ -113,10 +117,11 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
113
117
|
it('Should return entities with selecting specific fields', async () => {
|
|
114
118
|
await usingAsync(new Injector(), async (i) => {
|
|
115
119
|
setupContext(i)
|
|
120
|
+
const port = getPort()
|
|
116
121
|
await useRestService<{ GET: { '/entities': GetCollectionEndpoint<MockClass> } }>({
|
|
117
122
|
injector: i,
|
|
118
123
|
root: '/api',
|
|
119
|
-
port
|
|
124
|
+
port,
|
|
120
125
|
api: {
|
|
121
126
|
GET: {
|
|
122
127
|
'/entities': createGetCollectionEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -133,7 +138,7 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
133
138
|
|
|
134
139
|
selectedEntities.forEach((e) => expect(e.value).toBeUndefined())
|
|
135
140
|
|
|
136
|
-
const response = await fetch(`http://127.0.0.1
|
|
141
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/entities?${serializeToQueryString({ findOptions })}`, {
|
|
137
142
|
method: 'GET',
|
|
138
143
|
})
|
|
139
144
|
|
|
@@ -148,10 +153,11 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
148
153
|
it('Should return entities with top/skip', async () => {
|
|
149
154
|
await usingAsync(new Injector(), async (i) => {
|
|
150
155
|
setupContext(i)
|
|
156
|
+
const port = getPort()
|
|
151
157
|
await useRestService<{ GET: { '/entities': GetCollectionEndpoint<MockClass> } }>({
|
|
152
158
|
injector: i,
|
|
153
159
|
root: '/api',
|
|
154
|
-
port
|
|
160
|
+
port,
|
|
155
161
|
api: {
|
|
156
162
|
GET: {
|
|
157
163
|
'/entities': createGetCollectionEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -170,7 +176,7 @@ describe('createGetCollectionEndpoint', () => {
|
|
|
170
176
|
expect(topSkipEntities).not.toContainEqual({ id: 'mock1', value: '4' })
|
|
171
177
|
expect(topSkipEntities).not.toContainEqual({ id: 'mock4', value: '1' })
|
|
172
178
|
|
|
173
|
-
const response = await fetch(`http://127.0.0.1
|
|
179
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/entities?${serializeToQueryString({ findOptions })}`, {
|
|
174
180
|
method: 'GET',
|
|
175
181
|
})
|
|
176
182
|
expect(response.status).toBe(200)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Constructable } from '@furystack/inject'
|
|
2
2
|
import type { GetCollectionEndpoint } from '@furystack/rest'
|
|
3
|
-
import '@furystack/repository'
|
|
4
3
|
import type { RequestAction } from '../request-action-implementation.js'
|
|
5
4
|
import { JsonResult } from '../request-action-implementation.js'
|
|
6
5
|
import { getRepository } from '@furystack/repository'
|
|
@@ -7,15 +7,17 @@ import { createGetEntityEndpoint } from './create-get-entity-endpoint.js'
|
|
|
7
7
|
import { getDataSetFor } from '@furystack/repository'
|
|
8
8
|
import { useRestService } from '../helpers.js'
|
|
9
9
|
import { describe, it, expect } from 'vitest'
|
|
10
|
+
import { getPort } from '@furystack/core/port-generator'
|
|
10
11
|
|
|
11
12
|
describe('createGetEntityEndpoint', () => {
|
|
12
13
|
it('Should return the entity', async () => {
|
|
13
14
|
await usingAsync(new Injector(), async (i) => {
|
|
14
15
|
setupContext(i)
|
|
16
|
+
const port = getPort()
|
|
15
17
|
await useRestService<{ GET: { '/:id': GetEntityEndpoint<MockClass, 'id'> } }>({
|
|
16
18
|
injector: i,
|
|
17
19
|
root: '/api',
|
|
18
|
-
port
|
|
20
|
+
port,
|
|
19
21
|
api: {
|
|
20
22
|
GET: {
|
|
21
23
|
'/:id': createGetEntityEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -25,7 +27,7 @@ describe('createGetEntityEndpoint', () => {
|
|
|
25
27
|
const mockEntity: MockClass = { id: 'mock', value: 'mock' }
|
|
26
28
|
await getDataSetFor(i, MockClass, 'id').add(i, mockEntity)
|
|
27
29
|
|
|
28
|
-
const response = await fetch(
|
|
30
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/mock`, { method: 'GET' })
|
|
29
31
|
expect(response.status).toBe(200)
|
|
30
32
|
const body = await response.json()
|
|
31
33
|
expect(body).toEqual(mockEntity)
|
|
@@ -35,10 +37,11 @@ describe('createGetEntityEndpoint', () => {
|
|
|
35
37
|
it('Should return the entity with the selected fields', async () => {
|
|
36
38
|
await usingAsync(new Injector(), async (i) => {
|
|
37
39
|
setupContext(i)
|
|
40
|
+
const port = getPort()
|
|
38
41
|
await useRestService<{ GET: { '/:id': GetEntityEndpoint<MockClass, 'id'> } }>({
|
|
39
42
|
injector: i,
|
|
40
43
|
root: '/api',
|
|
41
|
-
port
|
|
44
|
+
port,
|
|
42
45
|
api: {
|
|
43
46
|
GET: {
|
|
44
47
|
'/:id': createGetEntityEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -48,7 +51,7 @@ describe('createGetEntityEndpoint', () => {
|
|
|
48
51
|
const mockEntity: MockClass = { id: 'mock', value: 'mock' }
|
|
49
52
|
await getDataSetFor(i, MockClass, 'id').add(i, mockEntity)
|
|
50
53
|
|
|
51
|
-
const response = await fetch(`http://127.0.0.1
|
|
54
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/mock?${serializeToQueryString({ select: ['id'] })}`, {
|
|
52
55
|
method: 'GET',
|
|
53
56
|
})
|
|
54
57
|
expect(response.status).toBe(200)
|
|
@@ -60,17 +63,18 @@ describe('createGetEntityEndpoint', () => {
|
|
|
60
63
|
it('Should return 404 if no entity has been found', async () => {
|
|
61
64
|
await usingAsync(new Injector(), async (i) => {
|
|
62
65
|
setupContext(i)
|
|
66
|
+
const port = getPort()
|
|
63
67
|
await useRestService<{ GET: { '/:id': GetEntityEndpoint<MockClass, 'id'> } }>({
|
|
64
68
|
injector: i,
|
|
65
69
|
root: '/api',
|
|
66
|
-
port
|
|
70
|
+
port,
|
|
67
71
|
api: {
|
|
68
72
|
GET: {
|
|
69
73
|
'/:id': createGetEntityEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
70
74
|
},
|
|
71
75
|
},
|
|
72
76
|
})
|
|
73
|
-
const result = await fetch(`http://127.0.0.1
|
|
77
|
+
const result = await fetch(`http://127.0.0.1:${port}/api/mock`, { method: 'GET' })
|
|
74
78
|
expect(result.status).toBe(404)
|
|
75
79
|
const body = await result.json()
|
|
76
80
|
expect(body).toEqual({ message: 'Entity not found' })
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Constructable } from '@furystack/inject'
|
|
2
2
|
import type { GetEntityEndpoint } from '@furystack/rest'
|
|
3
3
|
import { RequestError } from '@furystack/rest'
|
|
4
|
-
import '@furystack/repository'
|
|
5
4
|
import type { RequestAction } from '../request-action-implementation.js'
|
|
6
5
|
import { JsonResult } from '../request-action-implementation.js'
|
|
7
6
|
import { getRepository } from '@furystack/repository'
|
|
@@ -6,15 +6,17 @@ import { MockClass, setupContext } from './utils.js'
|
|
|
6
6
|
import { getDataSetFor } from '@furystack/repository'
|
|
7
7
|
import { useRestService } from '../helpers.js'
|
|
8
8
|
import { describe, it, expect } from 'vitest'
|
|
9
|
+
import { getPort } from '@furystack/core/port-generator'
|
|
9
10
|
|
|
10
11
|
describe('createPatchEndpoint', () => {
|
|
11
12
|
it('Should update the entity and report the success', async () => {
|
|
12
13
|
await usingAsync(new Injector(), async (i) => {
|
|
13
14
|
setupContext(i)
|
|
15
|
+
const port = getPort()
|
|
14
16
|
await useRestService<{ PATCH: { '/:id': PatchEndpoint<MockClass, 'id'> } }>({
|
|
15
17
|
injector: i,
|
|
16
18
|
root: '/api',
|
|
17
|
-
port
|
|
19
|
+
port,
|
|
18
20
|
api: {
|
|
19
21
|
PATCH: {
|
|
20
22
|
'/:id': createPatchEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -26,7 +28,7 @@ describe('createPatchEndpoint', () => {
|
|
|
26
28
|
const countBeforeDelete = await getDataSetFor(i, MockClass, 'id').count(i)
|
|
27
29
|
expect(countBeforeDelete).toBe(1)
|
|
28
30
|
|
|
29
|
-
const response = await fetch(
|
|
31
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/mock`, {
|
|
30
32
|
method: 'PATCH',
|
|
31
33
|
body: JSON.stringify({ value: 'updated' }),
|
|
32
34
|
})
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Constructable } from '@furystack/inject'
|
|
2
2
|
import type { PatchEndpoint } from '@furystack/rest'
|
|
3
3
|
import '@furystack/repository'
|
|
4
|
-
import '../incoming-message-extensions.js'
|
|
5
4
|
import type { RequestAction } from '../request-action-implementation.js'
|
|
6
5
|
import { JsonResult } from '../request-action-implementation.js'
|
|
7
6
|
import { getRepository } from '@furystack/repository'
|
|
8
7
|
import type { WithOptionalId } from '@furystack/core'
|
|
8
|
+
import { readPostBody } from '../read-post-body.js'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Creates a PATCH endpoint for updating entities
|
|
@@ -28,7 +28,7 @@ export const createPatchEndpoint = <
|
|
|
28
28
|
getUrlParams,
|
|
29
29
|
}) => {
|
|
30
30
|
const { id } = getUrlParams()
|
|
31
|
-
const patchData = await
|
|
31
|
+
const patchData = await readPostBody<T>(request)
|
|
32
32
|
const dataSet = getRepository(injector).getDataSetFor(options.model, options.primaryKey)
|
|
33
33
|
await dataSet.update(injector, id, patchData)
|
|
34
34
|
return JsonResult({})
|
|
@@ -6,15 +6,17 @@ import { MockClass, setupContext } from './utils.js'
|
|
|
6
6
|
import { useRestService } from '../helpers.js'
|
|
7
7
|
import { getDataSetFor } from '@furystack/repository'
|
|
8
8
|
import { describe, it, expect } from 'vitest'
|
|
9
|
+
import { getPort } from '@furystack/core/port-generator'
|
|
9
10
|
|
|
10
11
|
describe('createPostEndpoint', () => {
|
|
11
12
|
it('Should create the entity and report the success', async () => {
|
|
12
13
|
await usingAsync(new Injector(), async (i) => {
|
|
13
14
|
setupContext(i)
|
|
15
|
+
const port = getPort()
|
|
14
16
|
await useRestService<{ POST: { '/': PostEndpoint<MockClass, 'id'> } }>({
|
|
15
17
|
injector: i,
|
|
16
18
|
root: '/api',
|
|
17
|
-
port
|
|
19
|
+
port,
|
|
18
20
|
api: {
|
|
19
21
|
POST: {
|
|
20
22
|
'/': createPostEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
@@ -22,7 +24,7 @@ describe('createPostEndpoint', () => {
|
|
|
22
24
|
},
|
|
23
25
|
})
|
|
24
26
|
const entityToPost = { id: 'mock', value: 'posted' }
|
|
25
|
-
const response = await fetch(
|
|
27
|
+
const response = await fetch(`http://127.0.0.1:${port}/api`, {
|
|
26
28
|
method: 'POST',
|
|
27
29
|
body: JSON.stringify(entityToPost),
|
|
28
30
|
})
|
|
@@ -2,11 +2,11 @@ import type { Constructable } from '@furystack/inject'
|
|
|
2
2
|
import type { PostEndpoint } from '@furystack/rest'
|
|
3
3
|
import { RequestError } from '@furystack/rest'
|
|
4
4
|
import '@furystack/repository'
|
|
5
|
-
import '../incoming-message-extensions.js'
|
|
6
5
|
import type { RequestAction } from '../request-action-implementation.js'
|
|
7
6
|
import { JsonResult } from '../request-action-implementation.js'
|
|
8
7
|
import type { WithOptionalId } from '@furystack/core'
|
|
9
8
|
import { getRepository } from '@furystack/repository'
|
|
9
|
+
import { readPostBody } from '../read-post-body.js'
|
|
10
10
|
/**
|
|
11
11
|
* Creates a POST endpoint for updating entities
|
|
12
12
|
* @param options The options for endpoint creation
|
|
@@ -28,7 +28,7 @@ export const createPostEndpoint = <
|
|
|
28
28
|
options.primaryKey,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
const entityToCreate = await
|
|
31
|
+
const entityToCreate = await readPostBody<TWritableData>(request)
|
|
32
32
|
const { created } = await dataSet.add(injector, entityToCreate)
|
|
33
33
|
if (!created || !created.length) {
|
|
34
34
|
throw new RequestError('Entity not found', 404)
|
|
@@ -19,8 +19,7 @@ export const prepareInjector = async (i: Injector) => {
|
|
|
19
19
|
const setupUser = async (i: Injector, userName: string, password: string) => {
|
|
20
20
|
const sm = i.getInstance(StoreManager)
|
|
21
21
|
const pw = i.getInstance(PasswordAuthenticator)
|
|
22
|
-
const
|
|
23
|
-
const cred = await hasher.createCredential(userName, password)
|
|
22
|
+
const cred = await pw.hasher.createCredential(userName, password)
|
|
24
23
|
await sm.getStoreFor(PasswordCredential, 'userName').add(cred)
|
|
25
24
|
await sm.getStoreFor(User, 'username').add({ username: userName, roles: [] })
|
|
26
25
|
}
|
package/src/http-user-context.ts
CHANGED
|
@@ -159,11 +159,11 @@ export class HttpUserContext {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
@Injected(HttpAuthenticationSettings)
|
|
162
|
-
public readonly authentication
|
|
162
|
+
public declare readonly authentication: HttpAuthenticationSettings<User, DefaultSession>
|
|
163
163
|
|
|
164
164
|
@Injected(StoreManager)
|
|
165
|
-
private readonly storeManager
|
|
165
|
+
private declare readonly storeManager: StoreManager
|
|
166
166
|
|
|
167
167
|
@Injected(PasswordAuthenticator)
|
|
168
|
-
private readonly authenticator
|
|
168
|
+
private declare readonly authenticator: PasswordAuthenticator
|
|
169
169
|
}
|
|
@@ -1,20 +1,5 @@
|
|
|
1
|
-
import http from 'http'
|
|
2
|
-
import { Utils } from './utils.js'
|
|
3
|
-
|
|
4
1
|
declare module 'http' {
|
|
5
2
|
export interface IncomingMessage {
|
|
6
|
-
readPostBodyRaw: () => Promise<string>
|
|
7
|
-
readPostBody: <T>() => Promise<T>
|
|
8
3
|
postBody: unknown
|
|
9
4
|
}
|
|
10
5
|
}
|
|
11
|
-
|
|
12
|
-
http.IncomingMessage.prototype.readPostBody = async function <T>() {
|
|
13
|
-
const utils = new Utils()
|
|
14
|
-
return await utils.readPostBody<T>(this)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
http.IncomingMessage.prototype.readPostBodyRaw = async function () {
|
|
18
|
-
const utils = new Utils()
|
|
19
|
-
return await utils.readPostBodyRaw(this)
|
|
20
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export * from './helpers.js'
|
|
2
|
+
export * from './add-cors-header.js'
|
|
2
3
|
export * from './api-manager.js'
|
|
3
4
|
export * from './actions/index.js'
|
|
4
5
|
export * from './authenticate.js'
|
|
5
6
|
export * from './authorize.js'
|
|
6
7
|
export * from './http-authentication-settings.js'
|
|
7
8
|
export * from './http-user-context.js'
|
|
8
|
-
export * from './incoming-message-extensions.js'
|
|
9
9
|
export * from './server-manager.js'
|
|
10
10
|
export * from './server-response-extensions.js'
|
|
11
|
-
export * from './utils.js'
|
|
12
11
|
export * from './models/index.js'
|
|
13
12
|
export * from './endpoint-generators/index.js'
|
|
14
13
|
export * from './schema-validator/index.js'
|
|
15
14
|
export * from './request-action-implementation.js'
|
|
16
15
|
export * from './validate.js'
|
|
17
16
|
export * from './mime-types.js'
|
|
17
|
+
export * from './read-post-body.js'
|
|
18
18
|
export * from './static-server-manager.js'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'http'
|
|
2
|
+
import './incoming-message-extensions.js'
|
|
3
|
+
|
|
4
|
+
export const readPostBodyRaw = async (incomingMessage: IncomingMessage) => {
|
|
5
|
+
if (!incomingMessage.readable) {
|
|
6
|
+
throw Error('Incoming message is not readable')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let body = ''
|
|
10
|
+
await new Promise<void>((resolve, reject) => {
|
|
11
|
+
incomingMessage.on('readable', () => {
|
|
12
|
+
const data = incomingMessage.read()
|
|
13
|
+
if (data) {
|
|
14
|
+
body += data
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
incomingMessage.on('end', () => {
|
|
18
|
+
resolve()
|
|
19
|
+
})
|
|
20
|
+
incomingMessage.on('error', (err) => {
|
|
21
|
+
reject(err)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
return body
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reads the post's body and returns a promise with a parsed value
|
|
29
|
+
* @param incomingMessage The incoming message instance
|
|
30
|
+
* @returns the parsed object from the post body
|
|
31
|
+
*/
|
|
32
|
+
export const readPostBody = async <T>(incomingMessage: IncomingMessage): Promise<T> => {
|
|
33
|
+
const body = incomingMessage.postBody || JSON.parse(await readPostBodyRaw(incomingMessage))
|
|
34
|
+
incomingMessage.postBody = body
|
|
35
|
+
return body
|
|
36
|
+
}
|
|
@@ -12,7 +12,7 @@ import { serializeValue } from '@furystack/rest'
|
|
|
12
12
|
import { getPort } from '@furystack/core/port-generator'
|
|
13
13
|
|
|
14
14
|
class UserWithPassword extends User {
|
|
15
|
-
password
|
|
15
|
+
declare password: string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
interface IntegrationTestApi extends RestApi {
|
|
@@ -29,9 +29,9 @@ interface IntegrationTestApi extends RestApi {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const createIntegrationApi = async () => {
|
|
33
|
+
const i = new Injector()
|
|
33
34
|
const port = getPort()
|
|
34
|
-
const hostName = 'localhost'
|
|
35
35
|
const root = 'test-api'
|
|
36
36
|
|
|
37
37
|
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' })).addStore(
|
|
@@ -44,8 +44,7 @@ const prepareInjector = async (i: Injector) => {
|
|
|
44
44
|
await useRestService<IntegrationTestApi>({
|
|
45
45
|
injector: i,
|
|
46
46
|
root,
|
|
47
|
-
port
|
|
48
|
-
hostName,
|
|
47
|
+
port,
|
|
49
48
|
cors: {
|
|
50
49
|
credentials: true,
|
|
51
50
|
origins: ['http://localhost:8080'],
|
|
@@ -71,23 +70,22 @@ const prepareInjector = async (i: Injector) => {
|
|
|
71
70
|
})
|
|
72
71
|
|
|
73
72
|
return {
|
|
74
|
-
apiUrl: `http
|
|
73
|
+
apiUrl: `http://127.0.0.1:${port}/${root}`,
|
|
75
74
|
port,
|
|
76
|
-
|
|
75
|
+
dispose: i.dispose.bind(i),
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
describe('@furystack/rest-service inregration tests', () => {
|
|
81
80
|
it('Should be started and disposed', async () => {
|
|
82
|
-
await usingAsync(
|
|
83
|
-
await prepareInjector(i)
|
|
84
|
-
})
|
|
81
|
+
await usingAsync(await createIntegrationApi(), async () => {})
|
|
85
82
|
})
|
|
86
83
|
|
|
87
84
|
it('Should respond with 404 when a route is not found', async () => {
|
|
88
|
-
await usingAsync(
|
|
89
|
-
const
|
|
90
|
-
|
|
85
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
86
|
+
const result = await fetch(PathHelper.joinPaths(apiUrl, 'some-route-that-does-not-exists'), {
|
|
87
|
+
method: 'GET',
|
|
88
|
+
})
|
|
91
89
|
expect(result.ok).toBe(false)
|
|
92
90
|
expect(result.status).toBe(404)
|
|
93
91
|
const responseText = await result.json()
|
|
@@ -96,8 +94,7 @@ describe('@furystack/rest-service inregration tests', () => {
|
|
|
96
94
|
})
|
|
97
95
|
|
|
98
96
|
it('Should respond with 401 for unauthorized request errors', async () => {
|
|
99
|
-
await usingAsync(
|
|
100
|
-
const { apiUrl } = await prepareInjector(i)
|
|
97
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
101
98
|
const result = await fetch(PathHelper.joinPaths(apiUrl, 'currentUser'))
|
|
102
99
|
expect(result.ok).toBe(false)
|
|
103
100
|
expect(result.status).toBe(401)
|
|
@@ -107,8 +104,7 @@ describe('@furystack/rest-service inregration tests', () => {
|
|
|
107
104
|
})
|
|
108
105
|
|
|
109
106
|
it('Should respond with 401 for unauthorized request errors', async () => {
|
|
110
|
-
await usingAsync(
|
|
111
|
-
const { apiUrl } = await prepareInjector(i)
|
|
107
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
112
108
|
const result = await fetch(PathHelper.joinPaths(apiUrl, 'currentUser'))
|
|
113
109
|
expect(result.ok).toBe(false)
|
|
114
110
|
expect(result.status).toBe(401)
|
|
@@ -118,8 +114,7 @@ describe('@furystack/rest-service inregration tests', () => {
|
|
|
118
114
|
})
|
|
119
115
|
|
|
120
116
|
it('Should respond with the correct result body', async () => {
|
|
121
|
-
await usingAsync(
|
|
122
|
-
const { apiUrl } = await prepareInjector(i)
|
|
117
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
123
118
|
const response = await fetch(PathHelper.joinPaths(apiUrl, 'isAuthenticated'))
|
|
124
119
|
expect(response.status).toBe(200)
|
|
125
120
|
const result = await response.json()
|
|
@@ -128,9 +123,8 @@ describe('@furystack/rest-service inregration tests', () => {
|
|
|
128
123
|
})
|
|
129
124
|
|
|
130
125
|
it('Should be able to read query parameters', async () => {
|
|
131
|
-
await usingAsync(
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
127
|
+
console.log('apiUrl', apiUrl)
|
|
134
128
|
const response = await fetch(PathHelper.joinPaths(apiUrl, `testQuery?param1=${serializeValue('foo')}`))
|
|
135
129
|
expect(response.status).toBe(200)
|
|
136
130
|
const result = await response.json()
|
|
@@ -139,9 +133,7 @@ describe('@furystack/rest-service inregration tests', () => {
|
|
|
139
133
|
})
|
|
140
134
|
|
|
141
135
|
it('Should be able to read url parameters', async () => {
|
|
142
|
-
await usingAsync(
|
|
143
|
-
const { apiUrl } = await prepareInjector(i)
|
|
144
|
-
|
|
136
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
145
137
|
const response = await fetch(PathHelper.joinPaths(apiUrl, 'testUrlParams/bar'))
|
|
146
138
|
expect(response.status).toBe(200)
|
|
147
139
|
const result = await response.json()
|
|
@@ -150,9 +142,7 @@ describe('@furystack/rest-service inregration tests', () => {
|
|
|
150
142
|
})
|
|
151
143
|
|
|
152
144
|
it('Should be able to read post body', async () => {
|
|
153
|
-
await usingAsync(
|
|
154
|
-
const { apiUrl } = await prepareInjector(i)
|
|
155
|
-
|
|
145
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
156
146
|
const response = await fetch(PathHelper.joinPaths(apiUrl, 'testPostBody'), {
|
|
157
147
|
method: 'POST',
|
|
158
148
|
body: JSON.stringify({ value: 'baz' }),
|
|
@@ -164,20 +154,20 @@ describe('@furystack/rest-service inregration tests', () => {
|
|
|
164
154
|
})
|
|
165
155
|
|
|
166
156
|
it('Should respond with OK to OPTIONS requests', async () => {
|
|
167
|
-
await usingAsync(
|
|
168
|
-
const { apiUrl } = await prepareInjector(i)
|
|
169
|
-
|
|
157
|
+
await usingAsync(await createIntegrationApi(), async ({ apiUrl }) => {
|
|
170
158
|
const response = await fetch(PathHelper.joinPaths(apiUrl, 'testPostBody'), {
|
|
171
159
|
method: 'OPTIONS',
|
|
160
|
+
}).catch((e) => {
|
|
161
|
+
console.log(e)
|
|
162
|
+
throw e
|
|
172
163
|
})
|
|
173
164
|
expect(response.status).toBe(200)
|
|
174
165
|
})
|
|
175
166
|
})
|
|
176
167
|
|
|
177
168
|
it('Should reject requests outside of the API Root', async () => {
|
|
178
|
-
await usingAsync(
|
|
179
|
-
|
|
180
|
-
await expect(fetch(PathHelper.joinPaths(`http://${hostName}:${port}`, 'not-my-api-root'))).rejects.toThrowError(
|
|
169
|
+
await usingAsync(await createIntegrationApi(), async ({ port }) => {
|
|
170
|
+
await expect(fetch(PathHelper.joinPaths(`http://127.0.0.1:${port}`, 'not-my-api-root'))).rejects.toThrowError(
|
|
181
171
|
'fetch failed',
|
|
182
172
|
)
|
|
183
173
|
})
|
|
@@ -3,23 +3,7 @@ import { sleepAsync, usingAsync } from '@furystack/utils'
|
|
|
3
3
|
import { ServerManager } from './server-manager.js'
|
|
4
4
|
import { StaticServerManager } from './static-server-manager.js'
|
|
5
5
|
import { describe, it, expect, vi } from 'vitest'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Generator for an incremental port number
|
|
9
|
-
* @param initialPort The initial port number
|
|
10
|
-
* @yields a port for testing
|
|
11
|
-
* @returns The Port number
|
|
12
|
-
*/
|
|
13
|
-
function* portGenerator(initialPort = 17000) {
|
|
14
|
-
let port = initialPort
|
|
15
|
-
|
|
16
|
-
while (true) {
|
|
17
|
-
yield port++
|
|
18
|
-
}
|
|
19
|
-
return port
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const getPort = () => portGenerator().next().value
|
|
6
|
+
import { getPort } from '@furystack/core/port-generator'
|
|
23
7
|
|
|
24
8
|
describe('StaticServerManager', () => {
|
|
25
9
|
describe('Top level routing', () => {
|
|
@@ -33,7 +17,7 @@ describe('StaticServerManager', () => {
|
|
|
33
17
|
port,
|
|
34
18
|
})
|
|
35
19
|
|
|
36
|
-
const result = await fetch(`http://
|
|
20
|
+
const result = await fetch(`http://127.0.0.1:${port}/not-found.html`)
|
|
37
21
|
expect(result.ok).toBe(false)
|
|
38
22
|
expect(result.status).toBe(404)
|
|
39
23
|
expect(result?.headers.get('content-type')).toBe('text/plain')
|
|
@@ -18,7 +18,7 @@ export interface StaticServerOptions {
|
|
|
18
18
|
@Injectable({ lifetime: 'singleton' })
|
|
19
19
|
export class StaticServerManager {
|
|
20
20
|
@Injected(ServerManager)
|
|
21
|
-
private readonly serverManager
|
|
21
|
+
private declare readonly serverManager: ServerManager
|
|
22
22
|
|
|
23
23
|
private async sendFile({
|
|
24
24
|
fullPath,
|