@furystack/rest-service 12.3.5 → 14.0.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/CHANGELOG.md +55 -0
- package/README.md +47 -28
- package/esm/actions/get-current-user.spec.js +10 -6
- package/esm/actions/get-current-user.spec.js.map +1 -1
- package/esm/actions/index.d.ts +0 -1
- package/esm/actions/index.d.ts.map +1 -1
- package/esm/actions/index.js +0 -1
- package/esm/actions/index.js.map +1 -1
- package/esm/actions/is-authenticated.spec.js +10 -6
- package/esm/actions/is-authenticated.spec.js.map +1 -1
- package/esm/actions/logout-action.spec.js +11 -9
- package/esm/actions/logout-action.spec.js.map +1 -1
- package/esm/actions/logout.js +1 -1
- package/esm/actions/logout.js.map +1 -1
- package/esm/actions/password-login-action.js +1 -1
- package/esm/actions/password-login-action.js.map +1 -1
- package/esm/actions/password-login-action.spec.js +23 -28
- package/esm/actions/password-login-action.spec.js.map +1 -1
- package/esm/authenticate.js +1 -1
- package/esm/authenticate.js.map +1 -1
- package/esm/authenticate.spec.js +41 -33
- package/esm/authenticate.spec.js.map +1 -1
- package/esm/authorize.spec.js +34 -29
- package/esm/authorize.spec.js.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.d.ts +6 -11
- package/esm/endpoint-generators/create-delete-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.js +7 -11
- package/esm/endpoint-generators/create-delete-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-delete-endpoint.spec.js +14 -21
- package/esm/endpoint-generators/create-delete-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.d.ts +11 -11
- package/esm/endpoint-generators/create-get-collection-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.js +13 -12
- package/esm/endpoint-generators/create-get-collection-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-get-collection-endpoint.spec.js +43 -70
- package/esm/endpoint-generators/create-get-collection-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.d.ts +6 -11
- package/esm/endpoint-generators/create-get-entity-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.js +7 -11
- package/esm/endpoint-generators/create-get-entity-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-get-entity-endpoint.spec.js +16 -33
- package/esm/endpoint-generators/create-get-entity-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-get-openapi-document-action.d.ts +1 -15
- package/esm/endpoint-generators/create-get-openapi-document-action.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-openapi-document-action.js +1 -24
- package/esm/endpoint-generators/create-get-openapi-document-action.js.map +1 -1
- package/esm/endpoint-generators/create-get-schema-action.d.ts +1 -1
- package/esm/endpoint-generators/create-get-schema-action.d.ts.map +1 -1
- package/esm/endpoint-generators/create-get-schema-action.js.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.d.ts +7 -13
- package/esm/endpoint-generators/create-patch-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.js +8 -13
- package/esm/endpoint-generators/create-patch-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-patch-endpoint.spec.js +10 -17
- package/esm/endpoint-generators/create-patch-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.d.ts +7 -13
- package/esm/endpoint-generators/create-post-endpoint.d.ts.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.js +8 -13
- package/esm/endpoint-generators/create-post-endpoint.js.map +1 -1
- package/esm/endpoint-generators/create-post-endpoint.spec.js +41 -14
- package/esm/endpoint-generators/create-post-endpoint.spec.js.map +1 -1
- package/esm/endpoint-generators/utils.d.ts +17 -2
- package/esm/endpoint-generators/utils.d.ts.map +1 -1
- package/esm/endpoint-generators/utils.js +33 -20
- package/esm/endpoint-generators/utils.js.map +1 -1
- package/esm/endpoint-generators/with-schema-and-openapi-action.d.ts +0 -2
- package/esm/endpoint-generators/with-schema-and-openapi-action.d.ts.map +1 -1
- package/esm/get-schema-from-api.d.ts +1 -1
- package/esm/get-schema-from-api.d.ts.map +1 -1
- package/esm/get-schema-from-api.spec.js.map +1 -1
- package/esm/helpers.d.ts +53 -87
- package/esm/helpers.d.ts.map +1 -1
- package/esm/helpers.js +91 -92
- package/esm/helpers.js.map +1 -1
- package/esm/helpers.spec.d.ts +2 -1
- package/esm/helpers.spec.d.ts.map +1 -1
- package/esm/helpers.spec.js +41 -38
- package/esm/helpers.spec.js.map +1 -1
- package/esm/http-authentication-settings.d.ts +35 -12
- package/esm/http-authentication-settings.d.ts.map +1 -1
- package/esm/http-authentication-settings.js +21 -29
- package/esm/http-authentication-settings.js.map +1 -1
- package/esm/http-server-pool.d.ts +64 -0
- package/esm/http-server-pool.d.ts.map +1 -0
- package/esm/http-server-pool.js +95 -0
- package/esm/http-server-pool.js.map +1 -0
- package/esm/http-server-pool.spec.d.ts +2 -0
- package/esm/http-server-pool.spec.d.ts.map +1 -0
- package/esm/http-server-pool.spec.js +193 -0
- package/esm/http-server-pool.spec.js.map +1 -0
- package/esm/http-user-context.d.ts +31 -44
- package/esm/http-user-context.d.ts.map +1 -1
- package/esm/http-user-context.js +76 -95
- package/esm/http-user-context.js.map +1 -1
- package/esm/http-user-context.spec.d.ts +8 -2
- package/esm/http-user-context.spec.d.ts.map +1 -1
- package/esm/http-user-context.spec.js +189 -337
- package/esm/http-user-context.spec.js.map +1 -1
- package/esm/index.d.ts +6 -4
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +6 -4
- package/esm/index.js.map +1 -1
- package/esm/login-response-strategy.d.ts +11 -10
- package/esm/login-response-strategy.d.ts.map +1 -1
- package/esm/login-response-strategy.js +7 -6
- package/esm/login-response-strategy.js.map +1 -1
- package/esm/login-response-strategy.spec.js +25 -29
- package/esm/login-response-strategy.spec.js.map +1 -1
- package/esm/openapi/generate-openapi-document.d.ts +0 -8
- package/esm/openapi/generate-openapi-document.d.ts.map +1 -1
- package/esm/openapi/generate-openapi-document.js +0 -2
- package/esm/openapi/generate-openapi-document.js.map +1 -1
- package/esm/proxy-manager.spec.js +152 -176
- package/esm/proxy-manager.spec.js.map +1 -1
- package/esm/proxy-runtime.d.ts +27 -0
- package/esm/proxy-runtime.d.ts.map +1 -0
- package/esm/proxy-runtime.js +45 -0
- package/esm/proxy-runtime.js.map +1 -0
- package/esm/proxy-runtime.spec.d.ts +2 -0
- package/esm/proxy-runtime.spec.d.ts.map +1 -0
- package/esm/proxy-runtime.spec.js +39 -0
- package/esm/proxy-runtime.spec.js.map +1 -0
- package/esm/rest-api-runtime.d.ts +76 -0
- package/esm/rest-api-runtime.d.ts.map +1 -0
- package/esm/rest-api-runtime.js +131 -0
- package/esm/rest-api-runtime.js.map +1 -0
- package/esm/rest-api-runtime.spec.d.ts +2 -0
- package/esm/rest-api-runtime.spec.d.ts.map +1 -0
- package/esm/rest-api-runtime.spec.js +58 -0
- package/esm/rest-api-runtime.spec.js.map +1 -0
- package/esm/rest-service.integration.spec.d.ts +1 -1
- package/esm/rest-service.integration.spec.d.ts.map +1 -1
- package/esm/rest-service.integration.spec.js +60 -82
- package/esm/rest-service.integration.spec.js.map +1 -1
- package/esm/rest.integration.test.js +26 -84
- package/esm/rest.integration.test.js.map +1 -1
- package/esm/server-telemetry.d.ts +57 -0
- package/esm/server-telemetry.d.ts.map +1 -0
- package/esm/server-telemetry.js +26 -0
- package/esm/server-telemetry.js.map +1 -0
- package/esm/server-telemetry.spec.d.ts +2 -0
- package/esm/server-telemetry.spec.d.ts.map +1 -0
- package/esm/server-telemetry.spec.js +52 -0
- package/esm/server-telemetry.spec.js.map +1 -0
- package/esm/static-server-manager.spec.js +60 -125
- package/esm/static-server-manager.spec.js.map +1 -1
- package/esm/static-site-runtime.d.ts +21 -0
- package/esm/static-site-runtime.d.ts.map +1 -0
- package/esm/static-site-runtime.js +48 -0
- package/esm/static-site-runtime.js.map +1 -0
- package/esm/static-site-runtime.spec.d.ts +2 -0
- package/esm/static-site-runtime.spec.d.ts.map +1 -0
- package/esm/static-site-runtime.spec.js +30 -0
- package/esm/static-site-runtime.spec.js.map +1 -0
- package/esm/user-store.d.ts +39 -0
- package/esm/user-store.d.ts.map +1 -0
- package/esm/user-store.js +62 -0
- package/esm/user-store.js.map +1 -0
- package/esm/user-store.spec.d.ts +2 -0
- package/esm/user-store.spec.d.ts.map +1 -0
- package/esm/user-store.spec.js +72 -0
- package/esm/user-store.spec.js.map +1 -0
- package/esm/validate.integration.spec.js +26 -83
- package/esm/validate.integration.spec.js.map +1 -1
- package/package.json +8 -8
- package/src/actions/get-current-user.spec.ts +11 -10
- package/src/actions/index.ts +0 -1
- package/src/actions/is-authenticated.spec.ts +12 -7
- package/src/actions/logout-action.spec.ts +12 -12
- package/src/actions/logout.ts +1 -1
- package/src/actions/password-login-action.spec.ts +30 -30
- package/src/actions/password-login-action.ts +1 -1
- package/src/authenticate.spec.ts +45 -51
- package/src/authenticate.ts +1 -1
- package/src/authorize.spec.ts +37 -39
- package/src/endpoint-generators/create-delete-endpoint.spec.ts +16 -24
- package/src/endpoint-generators/create-delete-endpoint.ts +11 -17
- package/src/endpoint-generators/create-get-collection-endpoint.spec.ts +45 -78
- package/src/endpoint-generators/create-get-collection-endpoint.ts +17 -19
- package/src/endpoint-generators/create-get-entity-endpoint.spec.ts +17 -33
- package/src/endpoint-generators/create-get-entity-endpoint.ts +11 -17
- package/src/endpoint-generators/create-get-openapi-document-action.ts +2 -38
- package/src/endpoint-generators/create-get-schema-action.ts +1 -1
- package/src/endpoint-generators/create-patch-endpoint.spec.ts +11 -18
- package/src/endpoint-generators/create-patch-endpoint.ts +12 -23
- package/src/endpoint-generators/create-post-endpoint.spec.ts +45 -14
- package/src/endpoint-generators/create-post-endpoint.ts +13 -23
- package/src/endpoint-generators/utils.ts +36 -28
- package/src/endpoint-generators/with-schema-and-openapi-action.ts +0 -3
- package/src/get-schema-from-api.spec.ts +1 -1
- package/src/get-schema-from-api.ts +1 -1
- package/src/helpers.spec.ts +51 -41
- package/src/helpers.ts +140 -109
- package/src/http-authentication-settings.ts +51 -25
- package/src/http-server-pool.spec.ts +220 -0
- package/src/http-server-pool.ts +169 -0
- package/src/http-user-context.spec.ts +203 -383
- package/src/http-user-context.ts +114 -91
- package/src/index.ts +6 -4
- package/src/login-response-strategy.spec.ts +32 -31
- package/src/login-response-strategy.ts +13 -12
- package/src/openapi/generate-openapi-document.ts +0 -3
- package/src/proxy-manager.spec.ts +152 -182
- package/src/proxy-runtime.spec.ts +50 -0
- package/src/proxy-runtime.ts +75 -0
- package/src/rest-api-runtime.spec.ts +77 -0
- package/src/rest-api-runtime.ts +204 -0
- package/src/rest-service.integration.spec.ts +68 -87
- package/src/rest.integration.test.ts +27 -87
- package/src/server-telemetry.spec.ts +56 -0
- package/src/server-telemetry.ts +51 -0
- package/src/static-server-manager.spec.ts +63 -151
- package/src/static-site-runtime.spec.ts +39 -0
- package/src/static-site-runtime.ts +75 -0
- package/src/user-store.spec.ts +86 -0
- package/src/user-store.ts +68 -0
- package/src/validate.integration.spec.ts +39 -97
- package/esm/actions/login-action.spec.d.ts +0 -2
- package/esm/actions/login-action.spec.d.ts.map +0 -1
- package/esm/actions/login-action.spec.js +0 -34
- package/esm/actions/login-action.spec.js.map +0 -1
- package/esm/actions/login.d.ts +0 -19
- package/esm/actions/login.d.ts.map +0 -1
- package/esm/actions/login.js +0 -27
- package/esm/actions/login.js.map +0 -1
- package/esm/api-manager.d.ts +0 -104
- package/esm/api-manager.d.ts.map +0 -1
- package/esm/api-manager.js +0 -180
- package/esm/api-manager.js.map +0 -1
- package/esm/proxy-manager.d.ts +0 -53
- package/esm/proxy-manager.d.ts.map +0 -1
- package/esm/proxy-manager.js +0 -84
- package/esm/proxy-manager.js.map +0 -1
- package/esm/server-manager.d.ts +0 -48
- package/esm/server-manager.d.ts.map +0 -1
- package/esm/server-manager.js +0 -100
- package/esm/server-manager.js.map +0 -1
- package/esm/static-server-manager.d.ts +0 -19
- package/esm/static-server-manager.d.ts.map +0 -1
- package/esm/static-server-manager.js +0 -65
- package/esm/static-server-manager.js.map +0 -1
- package/esm/swagger/generate-swagger-json.spec.d.ts +0 -2
- package/esm/swagger/generate-swagger-json.spec.d.ts.map +0 -1
- package/esm/swagger/generate-swagger-json.spec.js +0 -215
- package/esm/swagger/generate-swagger-json.spec.js.map +0 -1
- package/src/actions/login-action.spec.ts +0 -42
- package/src/actions/login.ts +0 -32
- package/src/api-manager.ts +0 -308
- package/src/proxy-manager.ts +0 -102
- package/src/server-manager.ts +0 -135
- package/src/static-server-manager.ts +0 -90
- package/src/swagger/generate-swagger-json.spec.ts +0 -250
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { Injector } from '@furystack/inject'
|
|
2
2
|
import type { ApiDocumentMetadata, OpenApiDocument, RestApi, SecuritySchemeObject } from '@furystack/rest'
|
|
3
3
|
|
|
4
|
-
import type { RestApiImplementation } from '../api-manager.js'
|
|
5
4
|
import { getSchemaFromApi } from '../get-schema-from-api.js'
|
|
6
5
|
import { HttpAuthenticationSettings } from '../http-authentication-settings.js'
|
|
7
6
|
import { mapProvidersToSecuritySchemes } from '../openapi/auth-provider-to-security-scheme.js'
|
|
8
7
|
import { generateOpenApiDocument } from '../openapi/generate-openapi-document.js'
|
|
9
8
|
import { JsonResult, type RequestAction } from '../request-action-implementation.js'
|
|
9
|
+
import type { RestApiImplementation } from '../rest-api-runtime.js'
|
|
10
10
|
|
|
11
11
|
const getSecuritySchemesFromInjector = (injector: Injector): Record<string, SecuritySchemeObject> | undefined => {
|
|
12
12
|
try {
|
|
13
|
-
const settings = injector.
|
|
13
|
+
const settings = injector.get(HttpAuthenticationSettings)
|
|
14
14
|
if (settings.authenticationProviders.length > 0) {
|
|
15
15
|
return mapProvidersToSecuritySchemes(settings.authenticationProviders)
|
|
16
16
|
}
|
|
@@ -58,39 +58,3 @@ export const CreateGetOpenApiDocumentAction = <T extends RestApiImplementation<R
|
|
|
58
58
|
return JsonResult(cachedDoc, 200)
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Generates a backward-compatible `/swagger.json` endpoint that serves the same OpenAPI document
|
|
64
|
-
* as `CreateGetOpenApiDocumentAction` but includes `Deprecation` and `Link` headers
|
|
65
|
-
* directing clients to use `/openapi.json` instead.
|
|
66
|
-
*
|
|
67
|
-
* @deprecated Use CreateGetOpenApiDocumentAction and `/openapi.json` instead.
|
|
68
|
-
*/
|
|
69
|
-
export const CreateDeprecatedSwaggerRedirect = <T extends RestApiImplementation<RestApi>>(
|
|
70
|
-
api: T,
|
|
71
|
-
name = 'FuryStack API',
|
|
72
|
-
description = 'API documentation generated from FuryStack API schema',
|
|
73
|
-
version = '1.0.0',
|
|
74
|
-
): RequestAction<{ result: OpenApiDocument }> => {
|
|
75
|
-
const { endpoints } = getSchemaFromApi({ api, name, description, version })
|
|
76
|
-
let cachedDoc: OpenApiDocument | undefined
|
|
77
|
-
return async ({ injector }) => {
|
|
78
|
-
if (!cachedDoc) {
|
|
79
|
-
const securitySchemes = getSecuritySchemesFromInjector(injector)
|
|
80
|
-
cachedDoc = buildOpenApiDoc(
|
|
81
|
-
endpoints,
|
|
82
|
-
name,
|
|
83
|
-
description,
|
|
84
|
-
version,
|
|
85
|
-
securitySchemes ? { securitySchemes } : undefined,
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
return JsonResult(cachedDoc, 200, {
|
|
89
|
-
Deprecation: 'true',
|
|
90
|
-
Link: '</openapi.json>; rel="successor-version"',
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** @deprecated Use CreateGetOpenApiDocumentAction instead */
|
|
96
|
-
export const CreateGetSwaggerJsonAction = CreateGetOpenApiDocumentAction
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RequestError } from '@furystack/rest'
|
|
2
|
-
import type { RestApiImplementation } from '../api-manager.js'
|
|
3
2
|
import { getSchemaFromApi } from '../get-schema-from-api.js'
|
|
4
3
|
import { JsonResult, type RequestAction } from '../request-action-implementation.js'
|
|
4
|
+
import type { RestApiImplementation } from '../rest-api-runtime.js'
|
|
5
5
|
|
|
6
6
|
export type GetSchemaResult = ReturnType<typeof getSchemaFromApi>
|
|
7
7
|
|
|
@@ -1,42 +1,35 @@
|
|
|
1
1
|
import { getPort } from '@furystack/core/port-generator'
|
|
2
|
-
import {
|
|
3
|
-
import { getDataSetFor } from '@furystack/repository'
|
|
2
|
+
import { createInjector } from '@furystack/inject'
|
|
4
3
|
import type { PatchEndpoint } from '@furystack/rest'
|
|
5
4
|
import { usingAsync } from '@furystack/utils'
|
|
6
5
|
import { describe, expect, it } from 'vitest'
|
|
7
6
|
import { useRestService } from '../helpers.js'
|
|
8
7
|
import { createPatchEndpoint } from './create-patch-endpoint.js'
|
|
9
|
-
import { MockClass
|
|
8
|
+
import type { MockClass } from './utils.js'
|
|
9
|
+
import { MockDataSet, setupContext } from './utils.js'
|
|
10
10
|
|
|
11
11
|
describe('createPatchEndpoint', () => {
|
|
12
|
-
it('
|
|
13
|
-
await usingAsync(
|
|
12
|
+
it('updates the entity and responds 200', async () => {
|
|
13
|
+
await usingAsync(createInjector(), async (i) => {
|
|
14
14
|
setupContext(i)
|
|
15
15
|
const port = getPort()
|
|
16
16
|
await useRestService<{ PATCH: { '/:id': PatchEndpoint<MockClass, 'id'> } }>({
|
|
17
17
|
injector: i,
|
|
18
18
|
root: '/api',
|
|
19
19
|
port,
|
|
20
|
-
api: {
|
|
21
|
-
PATCH: {
|
|
22
|
-
'/:id': createPatchEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
23
|
-
},
|
|
24
|
-
},
|
|
20
|
+
api: { PATCH: { '/:id': createPatchEndpoint(MockDataSet) } },
|
|
25
21
|
})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
expect(countBeforeDelete).toBe(1)
|
|
22
|
+
const dataSet = i.get(MockDataSet)
|
|
23
|
+
await dataSet.add(i, { id: 'mock', value: 'mock' })
|
|
24
|
+
expect(await dataSet.count(i)).toBe(1)
|
|
30
25
|
|
|
31
26
|
const response = await fetch(`http://127.0.0.1:${port}/api/mock`, {
|
|
32
27
|
method: 'PATCH',
|
|
33
28
|
body: JSON.stringify({ value: 'updated' }),
|
|
34
29
|
})
|
|
35
30
|
expect(response.status).toBe(200)
|
|
36
|
-
|
|
37
|
-
expect(
|
|
38
|
-
const updated = await getDataSetFor(i, MockClass, 'id').get(i, 'mock')
|
|
39
|
-
expect(updated?.value).toBe('updated')
|
|
31
|
+
expect(await response.json()).toEqual({})
|
|
32
|
+
expect((await dataSet.get(i, 'mock'))?.value).toBe('updated')
|
|
40
33
|
})
|
|
41
34
|
})
|
|
42
35
|
})
|
|
@@ -1,37 +1,26 @@
|
|
|
1
|
-
import type { Constructable } from '@furystack/inject'
|
|
2
|
-
import type { PatchEndpoint } from '@furystack/rest'
|
|
3
|
-
import '@furystack/repository'
|
|
4
|
-
import type { RequestAction } from '../request-action-implementation.js'
|
|
5
|
-
import { JsonResult } from '../request-action-implementation.js'
|
|
6
|
-
import { getRepository } from '@furystack/repository'
|
|
7
1
|
import type { WithOptionalId } from '@furystack/core'
|
|
2
|
+
import type { DataSetToken } from '@furystack/repository'
|
|
3
|
+
import type { PatchEndpoint } from '@furystack/rest'
|
|
8
4
|
import { readPostBody } from '../read-post-body.js'
|
|
5
|
+
import { JsonResult, type RequestAction } from '../request-action-implementation.js'
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
|
-
* Creates a PATCH endpoint
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
* @param options.primaryKey The field name that is used as primary key on the model
|
|
15
|
-
* @returns a boolean that indicates the success
|
|
8
|
+
* Creates a PATCH endpoint that applies a partial update to an entity
|
|
9
|
+
* identified by the URL's `:id` parameter. The DataSet is resolved from the
|
|
10
|
+
* supplied {@link DataSetToken}.
|
|
16
11
|
*/
|
|
17
12
|
export const createPatchEndpoint = <
|
|
18
13
|
T extends object,
|
|
19
14
|
TPrimaryKey extends keyof T,
|
|
20
15
|
TWritableData = WithOptionalId<T, TPrimaryKey>,
|
|
21
|
-
>(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}) => {
|
|
25
|
-
const endpoint: RequestAction<PatchEndpoint<T, TPrimaryKey, TWritableData>> = async ({
|
|
26
|
-
injector,
|
|
27
|
-
request,
|
|
28
|
-
getUrlParams,
|
|
29
|
-
}) => {
|
|
16
|
+
>(
|
|
17
|
+
dataSet: DataSetToken<T, TPrimaryKey, TWritableData>,
|
|
18
|
+
): RequestAction<PatchEndpoint<T, TPrimaryKey, TWritableData>> => {
|
|
19
|
+
return async ({ injector, request, getUrlParams }) => {
|
|
30
20
|
const { id } = getUrlParams()
|
|
31
21
|
const patchData = await readPostBody<T>(request)
|
|
32
|
-
const
|
|
33
|
-
await
|
|
22
|
+
const ds = injector.get(dataSet)
|
|
23
|
+
await ds.update(injector, id, patchData)
|
|
34
24
|
return JsonResult({})
|
|
35
25
|
}
|
|
36
|
-
return endpoint
|
|
37
26
|
}
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
import { getPort } from '@furystack/core/port-generator'
|
|
2
|
-
import {
|
|
3
|
-
import { getDataSetFor } from '@furystack/repository'
|
|
2
|
+
import { createInjector } from '@furystack/inject'
|
|
4
3
|
import type { PostEndpoint } from '@furystack/rest'
|
|
5
4
|
import { usingAsync } from '@furystack/utils'
|
|
6
5
|
import { describe, expect, it } from 'vitest'
|
|
7
6
|
import { useRestService } from '../helpers.js'
|
|
8
7
|
import { createPostEndpoint } from './create-post-endpoint.js'
|
|
9
|
-
import { MockClass
|
|
8
|
+
import type { MockClass } from './utils.js'
|
|
9
|
+
import { MockDataSet, MockStore, setupContext } from './utils.js'
|
|
10
10
|
|
|
11
11
|
describe('createPostEndpoint', () => {
|
|
12
|
-
it('
|
|
13
|
-
await usingAsync(
|
|
12
|
+
it('creates the entity and responds 201', async () => {
|
|
13
|
+
await usingAsync(createInjector(), async (i) => {
|
|
14
14
|
setupContext(i)
|
|
15
15
|
const port = getPort()
|
|
16
16
|
await useRestService<{ POST: { '/': PostEndpoint<MockClass, 'id'> } }>({
|
|
17
17
|
injector: i,
|
|
18
18
|
root: '/api',
|
|
19
19
|
port,
|
|
20
|
-
api: {
|
|
21
|
-
POST: {
|
|
22
|
-
'/': createPostEndpoint({ model: MockClass, primaryKey: 'id' }),
|
|
23
|
-
},
|
|
24
|
-
},
|
|
20
|
+
api: { POST: { '/': createPostEndpoint(MockDataSet) } },
|
|
25
21
|
})
|
|
26
22
|
const entityToPost = { id: 'mock', value: 'posted' }
|
|
27
23
|
const response = await fetch(`http://127.0.0.1:${port}/api`, {
|
|
@@ -29,10 +25,45 @@ describe('createPostEndpoint', () => {
|
|
|
29
25
|
body: JSON.stringify(entityToPost),
|
|
30
26
|
})
|
|
31
27
|
expect(response.status).toBe(201)
|
|
32
|
-
|
|
33
|
-
expect(
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
expect(await response.json()).toEqual(entityToPost)
|
|
29
|
+
expect((await i.get(MockDataSet).get(i, entityToPost.id))?.value).toBe('posted')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('responds 404 when the underlying data set returned no created entity', async () => {
|
|
34
|
+
await usingAsync(createInjector(), async (i) => {
|
|
35
|
+
setupContext(i)
|
|
36
|
+
// Swap the dataset binding for a stub whose `add` comes back empty --
|
|
37
|
+
// this is the only way to exercise the `!created.length` branch.
|
|
38
|
+
i.bind(MockDataSet, () => ({ add: async () => ({ created: [] as MockClass[] }) }) as never)
|
|
39
|
+
const port = getPort()
|
|
40
|
+
await useRestService<{ POST: { '/': PostEndpoint<MockClass, 'id'> } }>({
|
|
41
|
+
injector: i,
|
|
42
|
+
root: '/api',
|
|
43
|
+
port,
|
|
44
|
+
api: { POST: { '/': createPostEndpoint(MockDataSet) } },
|
|
45
|
+
})
|
|
46
|
+
const response = await fetch(`http://127.0.0.1:${port}/api`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: JSON.stringify({ id: 'mock', value: 'x' }),
|
|
49
|
+
})
|
|
50
|
+
expect(response.status).toBe(404)
|
|
51
|
+
expect(await response.json()).toEqual({ message: 'Entity not found' })
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('endpoint-generators/utils', () => {
|
|
57
|
+
it('MockStore throws from its default factory when setupContext was not called', async () => {
|
|
58
|
+
await usingAsync(createInjector(), async (i) => {
|
|
59
|
+
expect(() => i.get(MockStore)).toThrow(/MockStore is not configured/)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('setupContext binds MockStore in-memory so the default factory is bypassed', async () => {
|
|
64
|
+
await usingAsync(createInjector(), async (i) => {
|
|
65
|
+
setupContext(i)
|
|
66
|
+
expect(i.get(MockStore)).toBeDefined()
|
|
36
67
|
})
|
|
37
68
|
})
|
|
38
69
|
})
|
|
@@ -1,39 +1,29 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { WithOptionalId } from '@furystack/core'
|
|
2
|
+
import type { DataSetToken } from '@furystack/repository'
|
|
2
3
|
import type { PostEndpoint } from '@furystack/rest'
|
|
3
4
|
import { RequestError } from '@furystack/rest'
|
|
4
|
-
import '@furystack/repository'
|
|
5
|
-
import type { RequestAction } from '../request-action-implementation.js'
|
|
6
|
-
import { JsonResult } from '../request-action-implementation.js'
|
|
7
|
-
import type { WithOptionalId } from '@furystack/core'
|
|
8
|
-
import { getRepository } from '@furystack/repository'
|
|
9
5
|
import { readPostBody } from '../read-post-body.js'
|
|
6
|
+
import { JsonResult, type RequestAction } from '../request-action-implementation.js'
|
|
7
|
+
|
|
10
8
|
/**
|
|
11
|
-
* Creates a POST endpoint
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @param options.primaryKey The field name used as primary key
|
|
15
|
-
* @returns a boolean that indicates the success
|
|
9
|
+
* Creates a POST endpoint that inserts a new entity into the DataSet
|
|
10
|
+
* resolved from `dataSet`. Returns `201 Created` on success; throws a
|
|
11
|
+
* `RequestError(404)` when the underlying store returns no created entity.
|
|
16
12
|
*/
|
|
17
13
|
export const createPostEndpoint = <
|
|
18
14
|
T extends object,
|
|
19
15
|
TPrimaryKey extends keyof T,
|
|
20
16
|
TWritableData = WithOptionalId<T, TPrimaryKey>,
|
|
21
|
-
>(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}) => {
|
|
25
|
-
|
|
26
|
-
const dataSet = getRepository(injector).getDataSetFor<T, TPrimaryKey, TWritableData>(
|
|
27
|
-
options.model,
|
|
28
|
-
options.primaryKey,
|
|
29
|
-
)
|
|
30
|
-
|
|
17
|
+
>(
|
|
18
|
+
dataSet: DataSetToken<T, TPrimaryKey, TWritableData>,
|
|
19
|
+
): RequestAction<PostEndpoint<T, TPrimaryKey, TWritableData>> => {
|
|
20
|
+
return async ({ injector, request }) => {
|
|
21
|
+
const ds = injector.get(dataSet)
|
|
31
22
|
const entityToCreate = await readPostBody<TWritableData>(request)
|
|
32
|
-
const { created } = await
|
|
23
|
+
const { created } = await ds.add(injector, entityToCreate)
|
|
33
24
|
if (!created || !created.length) {
|
|
34
25
|
throw new RequestError('Entity not found', 404)
|
|
35
26
|
}
|
|
36
27
|
return JsonResult(created[0], 201)
|
|
37
28
|
}
|
|
38
|
-
return endpoint
|
|
39
29
|
}
|
|
@@ -1,37 +1,45 @@
|
|
|
1
|
+
import { defineStore, InMemoryStore, User } from '@furystack/core'
|
|
1
2
|
import type { Injector } from '@furystack/inject'
|
|
2
|
-
import {
|
|
3
|
+
import { defineDataSet } from '@furystack/repository'
|
|
3
4
|
import { DefaultSession } from '../models/default-session.js'
|
|
4
|
-
import '
|
|
5
|
-
import '../helpers.js'
|
|
6
|
-
import { getRepository } from '@furystack/repository'
|
|
5
|
+
import { SessionStore, UserStore } from '../user-store.js'
|
|
7
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Mock entity used by endpoint-generator specs.
|
|
9
|
+
*/
|
|
8
10
|
export class MockClass {
|
|
9
11
|
declare id: string
|
|
10
12
|
declare value: string
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Store token for {@link MockClass}. The default factory throws so each spec
|
|
17
|
+
* explicitly opts into an in-memory backing via `setupContext`.
|
|
18
|
+
*/
|
|
19
|
+
export const MockStore = defineStore({
|
|
20
|
+
name: 'test/MockStore',
|
|
21
|
+
model: MockClass,
|
|
22
|
+
primaryKey: 'id',
|
|
23
|
+
factory: () => {
|
|
24
|
+
throw new Error('MockStore is not configured in this injector')
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Data set token over {@link MockStore}.
|
|
30
|
+
*/
|
|
31
|
+
export const MockDataSet = defineDataSet({
|
|
32
|
+
name: 'test/MockDataSet',
|
|
33
|
+
store: MockStore,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Binds in-memory implementations of {@link UserStore}, {@link SessionStore}
|
|
38
|
+
* and {@link MockStore} on the provided injector. Endpoint-generator specs
|
|
39
|
+
* and integration tests call this at setup time.
|
|
40
|
+
*/
|
|
41
|
+
export const setupContext = (i: Injector): void => {
|
|
42
|
+
i.bind(UserStore, () => new InMemoryStore({ model: User, primaryKey: 'username' }))
|
|
43
|
+
i.bind(SessionStore, () => new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }))
|
|
44
|
+
i.bind(MockStore, () => new InMemoryStore({ model: MockClass, primaryKey: 'id' }))
|
|
37
45
|
}
|
|
@@ -9,6 +9,3 @@ export type WithSchemaAndOpenApiAction<T extends RestApi> = T & {
|
|
|
9
9
|
'/openapi.json': { result: OpenApiDocument; headers: { accept: 'application/json' } }
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
/** @deprecated Use WithSchemaAndOpenApiAction instead */
|
|
14
|
-
export type WithSchemaAndSwaggerAction<T extends RestApi> = WithSchemaAndOpenApiAction<T>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import type { RestApiImplementation } from './api-manager.js'
|
|
3
2
|
import { defaultSchema, getSchemaFromApi } from './get-schema-from-api.js'
|
|
3
|
+
import type { RestApiImplementation } from './rest-api-runtime.js'
|
|
4
4
|
import { JsonResult, type RequestAction } from './request-action-implementation.js'
|
|
5
5
|
import validationSchema from './validate.integration.spec.schema.json' with { type: 'json' }
|
|
6
6
|
import { Validate } from './validate.js'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiEndpointDefinition, ApiEndpointSchema, Method, RestApi, Schema } from '@furystack/rest'
|
|
2
|
-
import type { RestApiImplementation } from './api-manager.js'
|
|
3
2
|
import type { RequestAction } from './request-action-implementation.js'
|
|
3
|
+
import type { RestApiImplementation } from './rest-api-runtime.js'
|
|
4
4
|
|
|
5
5
|
export const defaultSchema: Schema = {
|
|
6
6
|
definitions: {
|
package/src/helpers.spec.ts
CHANGED
|
@@ -1,56 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { User } from '@furystack/core'
|
|
2
|
+
import { InMemoryStore, User as UserModel } from '@furystack/core'
|
|
2
3
|
import { getPort } from '@furystack/core/port-generator'
|
|
3
|
-
import { Injector } from '@furystack/inject'
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { createInjector, type Injector } from '@furystack/inject'
|
|
5
|
+
import {
|
|
6
|
+
PasswordCredential,
|
|
7
|
+
PasswordCredentialStore,
|
|
8
|
+
PasswordResetToken,
|
|
9
|
+
PasswordResetTokenStore,
|
|
10
|
+
usePasswordPolicy,
|
|
11
|
+
} from '@furystack/security'
|
|
6
12
|
import { usingAsync } from '@furystack/utils'
|
|
7
13
|
import { describe, expect, it } from 'vitest'
|
|
8
|
-
import { ApiManager } from './api-manager.js'
|
|
9
14
|
import { useHttpAuthentication, useProxy, useRestService, useStaticFiles } from './helpers.js'
|
|
10
15
|
import { HttpAuthenticationSettings } from './http-authentication-settings.js'
|
|
16
|
+
import { HttpServerPoolToken } from './http-server-pool.js'
|
|
11
17
|
import { DefaultSession } from './models/default-session.js'
|
|
12
|
-
import {
|
|
13
|
-
import { StaticServerManager } from './static-server-manager.js'
|
|
18
|
+
import { SessionStore, UserStore } from './user-store.js'
|
|
14
19
|
|
|
15
20
|
const setupAuthStores = (i: Injector) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const repo = getRepository(i)
|
|
21
|
-
repo.createDataSet(User, 'username')
|
|
22
|
-
repo.createDataSet(DefaultSession, 'sessionId')
|
|
23
|
-
repo.createDataSet(PasswordCredential, 'userName')
|
|
24
|
-
repo.createDataSet(PasswordResetToken, 'token')
|
|
21
|
+
i.bind(UserStore, () => new InMemoryStore({ model: UserModel, primaryKey: 'username' }))
|
|
22
|
+
i.bind(SessionStore, () => new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }))
|
|
23
|
+
i.bind(PasswordCredentialStore, () => new InMemoryStore({ model: PasswordCredential, primaryKey: 'userName' }))
|
|
24
|
+
i.bind(PasswordResetTokenStore, () => new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' }))
|
|
25
25
|
usePasswordPolicy(i)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
describe('
|
|
28
|
+
describe('rest-service setup helpers', () => {
|
|
29
29
|
describe('useHttpAuthentication', () => {
|
|
30
|
-
it('
|
|
31
|
-
await usingAsync(
|
|
30
|
+
it('binds HttpAuthenticationSettings on the injector', async () => {
|
|
31
|
+
await usingAsync(createInjector(), async (i) => {
|
|
32
32
|
setupAuthStores(i)
|
|
33
33
|
useHttpAuthentication(i)
|
|
34
|
-
|
|
34
|
+
const settings = i.get(HttpAuthenticationSettings)
|
|
35
|
+
expect(settings).toBeDefined()
|
|
35
36
|
})
|
|
36
37
|
})
|
|
37
38
|
|
|
38
|
-
it('
|
|
39
|
-
await usingAsync(
|
|
39
|
+
it('registers basic-auth and cookie-auth providers by default', async () => {
|
|
40
|
+
await usingAsync(createInjector(), async (i) => {
|
|
40
41
|
setupAuthStores(i)
|
|
41
42
|
useHttpAuthentication(i)
|
|
42
|
-
const settings = i.
|
|
43
|
+
const settings = i.get(HttpAuthenticationSettings)
|
|
43
44
|
const providerNames = settings.authenticationProviders.map((p) => p.name)
|
|
44
45
|
expect(providerNames).toContain('basic-auth')
|
|
45
46
|
expect(providerNames).toContain('cookie-auth')
|
|
46
47
|
})
|
|
47
48
|
})
|
|
48
49
|
|
|
49
|
-
it('
|
|
50
|
-
await usingAsync(
|
|
50
|
+
it('omits the basic-auth provider when enableBasicAuth is false', async () => {
|
|
51
|
+
await usingAsync(createInjector(), async (i) => {
|
|
51
52
|
setupAuthStores(i)
|
|
52
53
|
useHttpAuthentication(i, { enableBasicAuth: false })
|
|
53
|
-
const settings = i.
|
|
54
|
+
const settings = i.get(HttpAuthenticationSettings)
|
|
54
55
|
const providerNames = settings.authenticationProviders.map((p) => p.name)
|
|
55
56
|
expect(providerNames).not.toContain('basic-auth')
|
|
56
57
|
expect(providerNames).toContain('cookie-auth')
|
|
@@ -59,41 +60,50 @@ describe('Injector extensions', () => {
|
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
describe('useRestService()', () => {
|
|
62
|
-
it('
|
|
63
|
-
await usingAsync(
|
|
63
|
+
it('acquires a pooled HTTP server and attaches a serverApi', async () => {
|
|
64
|
+
await usingAsync(createInjector(), async (i) => {
|
|
64
65
|
const port = getPort()
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const serverApi = await useRestService({ injector: i, api: {}, root: '/', port })
|
|
67
|
+
expect(serverApi).toBeDefined()
|
|
68
|
+
const pool = i.get(HttpServerPoolToken)
|
|
69
|
+
const record = await pool.acquire({ port })
|
|
70
|
+
expect(record.apis).toContain(serverApi)
|
|
68
71
|
})
|
|
69
72
|
})
|
|
70
73
|
})
|
|
71
74
|
|
|
72
75
|
describe('useStaticFiles()', () => {
|
|
73
|
-
it('
|
|
74
|
-
await usingAsync(
|
|
76
|
+
it('attaches a static-file serverApi to the pooled HTTP server', async () => {
|
|
77
|
+
await usingAsync(createInjector(), async (i) => {
|
|
75
78
|
const port = getPort()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
const serverApi = await useStaticFiles({ injector: i, baseUrl: '/', path: '.', port })
|
|
80
|
+
expect(serverApi).toBeDefined()
|
|
81
|
+
const pool = i.get(HttpServerPoolToken)
|
|
82
|
+
const record = await pool.acquire({ port })
|
|
83
|
+
expect(record.apis).toContain(serverApi)
|
|
79
84
|
})
|
|
80
85
|
})
|
|
81
86
|
})
|
|
82
87
|
|
|
83
88
|
describe('useProxy()', () => {
|
|
84
|
-
it('
|
|
85
|
-
await usingAsync(
|
|
89
|
+
it('attaches a proxy serverApi to the pooled HTTP server', async () => {
|
|
90
|
+
await usingAsync(createInjector(), async (i) => {
|
|
86
91
|
const sourcePort = getPort()
|
|
87
92
|
const targetPort = getPort()
|
|
88
|
-
|
|
89
|
-
await useProxy({
|
|
93
|
+
const serverApi = await useProxy({
|
|
90
94
|
injector: i,
|
|
91
95
|
sourceBaseUrl: '/api',
|
|
92
96
|
targetBaseUrl: `http://localhost:${targetPort}`,
|
|
93
97
|
sourcePort,
|
|
94
98
|
})
|
|
95
|
-
expect(
|
|
99
|
+
expect(serverApi).toBeDefined()
|
|
100
|
+
const pool = i.get(HttpServerPoolToken)
|
|
101
|
+
const record = await pool.acquire({ port: sourcePort })
|
|
102
|
+
expect(record.apis).toContain(serverApi)
|
|
96
103
|
})
|
|
97
104
|
})
|
|
98
105
|
})
|
|
99
106
|
})
|
|
107
|
+
|
|
108
|
+
// Re-export to keep ESLint happy about unused User import elsewhere.
|
|
109
|
+
export type { User }
|