@jsonapi-serde/integration-taxum 1.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/LICENSE +22 -0
- package/README.md +22 -0
- package/dist/augment.d.ts +7 -0
- package/dist/augment.js +19 -0
- package/dist/augment.js.map +1 -0
- package/dist/error-handler.d.ts +14 -0
- package/dist/error-handler.js +96 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/extract.d.ts +28 -0
- package/dist/extract.js +66 -0
- package/dist/extract.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/layer.d.ts +19 -0
- package/dist/layer.js +49 -0
- package/dist/layer.js.map +1 -0
- package/dist/router.d.ts +4 -0
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2025-present, Ben Scholzen 'DASPRiD'
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
14
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
15
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
16
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
17
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
18
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
19
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
20
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
21
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
22
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# JSON:API Serde - Taxum Integration
|
|
2
|
+
|
|
3
|
+
[](https://github.com/DASPRiD/jsonapi-serde-js/actions/workflows/test.yml)
|
|
4
|
+
[](https://codecov.io/gh/DASPRiD/jsonapi-serde-js)
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
[Taxum](https://taxum.js.org/) integration for [@jsonapi-serde/server](https://github.com/dasprid/jsonapi-serde-js).
|
|
9
|
+
|
|
10
|
+
## Documentation
|
|
11
|
+
|
|
12
|
+
To check out docs, visit [jsonapi-serde.js.org](https://jsonapi-serde.js.org).
|
|
13
|
+
|
|
14
|
+
## Changelog
|
|
15
|
+
|
|
16
|
+
Detailed changes for each release are documented in the [CHANGELOG](https://github.com/dasprid/jsonapi-serde-js/blob/main/packages/integration/taxum/CHANGELOG.md)
|
|
17
|
+
|
|
18
|
+
## License
|
|
19
|
+
|
|
20
|
+
[BSD-3-Clause](https://github.com/dasprid/jsonapi-serde-js/blob/main/LICENSE)
|
|
21
|
+
|
|
22
|
+
Copyright (c) 2025-present, Ben Scholzen 'DASPRiD'
|
package/dist/augment.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { JsonApiDocument, JsonApiError } from "@jsonapi-serde/server/common";
|
|
2
|
+
import { HttpResponse, TO_HTTP_RESPONSE } from "@taxum/core/http";
|
|
3
|
+
import { JSON_API_VERIFY_ACCEPT_MEDIA_TYPE } from "./layer.js";
|
|
4
|
+
JsonApiDocument.prototype[TO_HTTP_RESPONSE] = function () {
|
|
5
|
+
const response = HttpResponse.builder()
|
|
6
|
+
.status(this.getStatus())
|
|
7
|
+
.header("content-type", this.getContentType())
|
|
8
|
+
.body(JSON.stringify(this.getBody()));
|
|
9
|
+
// We build a minimal new document here so that the GC can
|
|
10
|
+
// immediately dispose of the original document.
|
|
11
|
+
const miniDocument = new JsonApiDocument(EMPTY_TOP_LEVEL_MEMBERS, undefined, this.getMediaTypeOptions());
|
|
12
|
+
response.extensions.insert(JSON_API_VERIFY_ACCEPT_MEDIA_TYPE, miniDocument.verifyAcceptMediaType.bind(miniDocument));
|
|
13
|
+
return response;
|
|
14
|
+
};
|
|
15
|
+
JsonApiError.prototype[TO_HTTP_RESPONSE] = function () {
|
|
16
|
+
return this.toDocument()[TO_HTTP_RESPONSE]();
|
|
17
|
+
};
|
|
18
|
+
const EMPTY_TOP_LEVEL_MEMBERS = { meta: {} };
|
|
19
|
+
//# sourceMappingURL=augment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"augment.js","sourceRoot":"","sources":["../src/augment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAwB,MAAM,8BAA8B,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAuB,MAAM,kBAAkB,CAAC;AACvF,OAAO,EAAE,iCAAiC,EAAE,MAAM,YAAY,CAAC;AAO9D,eAAe,CAAC,SAAuC,CAAC,gBAAgB,CAAC,GAAG;IAGzE,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,EAAE;SAClC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;SACxB,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;SAC7C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAE1C,0DAA0D;IAC1D,gDAAgD;IAChD,MAAM,YAAY,GAAG,IAAI,eAAe,CACpC,uBAAuB,EACvB,SAAS,EACT,IAAI,CAAC,mBAAmB,EAAE,CAC7B,CAAC;IAEF,QAAQ,CAAC,UAAU,CAAC,MAAM,CACtB,iCAAiC,EACjC,YAAY,CAAC,qBAAqB,CAAC,IAAI,CAAC,YAAY,CAAC,CACxD,CAAC;IAEF,OAAO,QAAQ,CAAC;AACpB,CAAC,CAAC;AAED,YAAY,CAAC,SAAuC,CAAC,gBAAgB,CAAC,GAAG;IAGtE,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC;AACjD,CAAC,CAAC;AAEF,MAAM,uBAAuB,GAAoB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "./augment.js";
|
|
2
|
+
import { type ErrorHandler } from "@taxum/core/util";
|
|
3
|
+
export type JsonApiErrorHandlerOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Optional error logger callback.
|
|
6
|
+
*/
|
|
7
|
+
logError?: (error: unknown, exposed: boolean) => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Error handler which converts any error into a JSON:API error response.
|
|
11
|
+
*
|
|
12
|
+
* Supports optional error logging via the `logError` callback.
|
|
13
|
+
*/
|
|
14
|
+
export declare const jsonApiErrorHandler: (options?: JsonApiErrorHandlerOptions) => ErrorHandler;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import "./augment.js";
|
|
2
|
+
import { JsonApiError } from "@jsonapi-serde/server/common";
|
|
3
|
+
import { ValidationError } from "@taxum/core/extract";
|
|
4
|
+
import { TO_HTTP_RESPONSE } from "@taxum/core/http";
|
|
5
|
+
import { ClientError } from "@taxum/core/util";
|
|
6
|
+
/**
|
|
7
|
+
* Maps various error types to a JSON:API error object.
|
|
8
|
+
*
|
|
9
|
+
* Determines if the error details can be safely exposed to clients.
|
|
10
|
+
*/
|
|
11
|
+
const getJsonApiError = (error) => {
|
|
12
|
+
if (error instanceof ValidationError) {
|
|
13
|
+
return [
|
|
14
|
+
new JsonApiError(error.issues.map((issue) => {
|
|
15
|
+
return {
|
|
16
|
+
status: error.source === "body" ? "422" : "400",
|
|
17
|
+
code: "validation_failed",
|
|
18
|
+
title: "Validation failed",
|
|
19
|
+
detail: error.message,
|
|
20
|
+
source: getStandardSchemaSource(error.source, issue.path),
|
|
21
|
+
};
|
|
22
|
+
})),
|
|
23
|
+
true,
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
if (error instanceof ClientError) {
|
|
27
|
+
return [
|
|
28
|
+
new JsonApiError({
|
|
29
|
+
status: error.status.code.toString(),
|
|
30
|
+
code: error.status.phrase.toLowerCase().replace(/ /g, "_"),
|
|
31
|
+
title: error.status.phrase,
|
|
32
|
+
detail: error.message,
|
|
33
|
+
}),
|
|
34
|
+
true,
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
if (error instanceof Error &&
|
|
38
|
+
"status" in error &&
|
|
39
|
+
typeof error.status === "number" &&
|
|
40
|
+
error.status >= 400 &&
|
|
41
|
+
error.status < 500) {
|
|
42
|
+
return [
|
|
43
|
+
new JsonApiError({
|
|
44
|
+
status: error.status.toString(),
|
|
45
|
+
code: error.name
|
|
46
|
+
.replace(/Error$/, "")
|
|
47
|
+
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
|
48
|
+
.replace(/^_+|_+$/g, ""),
|
|
49
|
+
title: error.message,
|
|
50
|
+
}),
|
|
51
|
+
true,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
if (error instanceof JsonApiError) {
|
|
55
|
+
return [error, error.status < 500];
|
|
56
|
+
}
|
|
57
|
+
return [
|
|
58
|
+
new JsonApiError({
|
|
59
|
+
status: "500",
|
|
60
|
+
code: "internal_server_error",
|
|
61
|
+
title: "Internal Server Error",
|
|
62
|
+
}),
|
|
63
|
+
false,
|
|
64
|
+
];
|
|
65
|
+
};
|
|
66
|
+
const getStandardSchemaSource = (source, path) => {
|
|
67
|
+
if (!path || source === "path") {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (source === "body") {
|
|
71
|
+
return { pointer: `/${path.join("/")}` };
|
|
72
|
+
}
|
|
73
|
+
if (path.length === 0) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
if (source === "header") {
|
|
77
|
+
return typeof path[0] === "string" ? { header: path[0] } : undefined;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
parameter: `${path[0].toString()}${path
|
|
81
|
+
.slice(1)
|
|
82
|
+
.map((element) => `[${element.toString()}]`)
|
|
83
|
+
.join()}`,
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Error handler which converts any error into a JSON:API error response.
|
|
88
|
+
*
|
|
89
|
+
* Supports optional error logging via the `logError` callback.
|
|
90
|
+
*/
|
|
91
|
+
export const jsonApiErrorHandler = (options) => (error) => {
|
|
92
|
+
const [jsonApiError, exposed] = getJsonApiError(error);
|
|
93
|
+
options?.logError?.(error, exposed);
|
|
94
|
+
return jsonApiError[TO_HTTP_RESPONSE]();
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AttributesSchema, type IncludedTypeSchemas, type ParseQueryResult, type ParseResourceRequestOptions, type ParseResourceRequestResult, type QueryParser, type RelationshipsSchema, type SparseFieldSets } from "@jsonapi-serde/server/request";
|
|
2
|
+
import type { Extractor } from "@taxum/core/extract";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { $ZodType } from "zod/v4/core";
|
|
5
|
+
/**
|
|
6
|
+
* Extractor which extracts query params based on a JSON:API query parser.
|
|
7
|
+
*
|
|
8
|
+
* @see {@link QueryParser}
|
|
9
|
+
*/
|
|
10
|
+
export declare const jsonApiQuery: <TInclude extends readonly string[] | undefined, TSortFields extends readonly string[] | undefined, TSparseFieldSets extends SparseFieldSets | undefined, TFilterSchema extends $ZodType | undefined, TPageSchema extends $ZodType | undefined>(parse: QueryParser<TInclude, TSortFields, TSparseFieldSets, TFilterSchema, TPageSchema>) => Extractor<ParseQueryResult<TInclude, TSortFields, TSparseFieldSets, TFilterSchema, TPageSchema>>;
|
|
11
|
+
/**
|
|
12
|
+
* Extractor which extracts a JSON:API resource from the body.
|
|
13
|
+
*
|
|
14
|
+
* @see {@link parseResourceRequest}
|
|
15
|
+
*/
|
|
16
|
+
export declare const jsonApiResource: <TIdSchema extends $ZodType<string> | undefined, TType extends string, TAttributesSchema extends AttributesSchema | undefined, TRelationshipsSchema extends RelationshipsSchema | undefined, TIncludedTypeSchemas extends IncludedTypeSchemas | undefined>(options: ParseResourceRequestOptions<TIdSchema, TType, TAttributesSchema, TRelationshipsSchema, TIncludedTypeSchemas>, idPathParam?: string) => Extractor<ParseResourceRequestResult<NoInfer<TIdSchema>, NoInfer<TType>, NoInfer<TAttributesSchema>, NoInfer<TRelationshipsSchema>, NoInfer<TIncludedTypeSchemas>>>;
|
|
17
|
+
/**
|
|
18
|
+
* Extractor which extracts a to-one JSON:API relationship from the body.
|
|
19
|
+
*
|
|
20
|
+
* @see {@link parseRelationshipRequest}
|
|
21
|
+
*/
|
|
22
|
+
export declare const jsonApiRelationship: <TIdSchema extends $ZodType<string | null> | undefined>(type: string, idSchema?: TIdSchema) => Extractor<TIdSchema extends $ZodType<string | null> ? z.output<NoInfer<TIdSchema>> : string>;
|
|
23
|
+
/**
|
|
24
|
+
* Extractor which extracts to-many JSON:API relationships from the body.
|
|
25
|
+
*
|
|
26
|
+
* @see {@link parseRelationshipsRequest}
|
|
27
|
+
*/
|
|
28
|
+
export declare const jsonApiRelationships: (type: string, idSchema?: $ZodType<string>) => Extractor<string[]>;
|
package/dist/extract.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import consumers from "node:stream/consumers";
|
|
3
|
+
import { parseRelationshipRequest, parseRelationshipsRequest, parseResourceRequest, } from "@jsonapi-serde/server/request";
|
|
4
|
+
import { PATH_PARAMS } from "@taxum/core/routing";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
/**
|
|
7
|
+
* Extractor which extracts query params based on a JSON:API query parser.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link QueryParser}
|
|
10
|
+
*/
|
|
11
|
+
export const jsonApiQuery = (parse) => (req) => {
|
|
12
|
+
return parse(req.uri.searchParams);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Extractor which extracts a JSON:API resource from the body.
|
|
16
|
+
*
|
|
17
|
+
* @see {@link parseResourceRequest}
|
|
18
|
+
*/
|
|
19
|
+
export const jsonApiResource = (options, idPathParam) => async (req) => {
|
|
20
|
+
let idSchema = options.idSchema;
|
|
21
|
+
if (idPathParam) {
|
|
22
|
+
const pathParams = req.extensions.get(PATH_PARAMS);
|
|
23
|
+
assert(pathParams, "Path parameters missing");
|
|
24
|
+
assert(idPathParam in pathParams, `Path parameter "${idPathParam}" missing`);
|
|
25
|
+
const pathId = pathParams[idPathParam];
|
|
26
|
+
// We have to apply some type hacks here to support this use-case.
|
|
27
|
+
// If no id schema was defined, it doesn't hurt to set one here.
|
|
28
|
+
// If one was supplied, we know that the input must be a string, so
|
|
29
|
+
// it's safe to pipe from a literal.
|
|
30
|
+
idSchema = (idSchema
|
|
31
|
+
? z.literal(pathId).pipe(idSchema)
|
|
32
|
+
: z.literal(pathId));
|
|
33
|
+
}
|
|
34
|
+
const context = await createBodyContext(req);
|
|
35
|
+
return parseResourceRequest(context, { ...options, idSchema });
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Extractor which extracts a to-one JSON:API relationship from the body.
|
|
39
|
+
*
|
|
40
|
+
* @see {@link parseRelationshipRequest}
|
|
41
|
+
*/
|
|
42
|
+
export const jsonApiRelationship = (type, idSchema) => async (req) => {
|
|
43
|
+
const context = await createBodyContext(req);
|
|
44
|
+
return parseRelationshipRequest(context, type, idSchema);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Extractor which extracts to-many JSON:API relationships from the body.
|
|
48
|
+
*
|
|
49
|
+
* @see {@link parseRelationshipsRequest}
|
|
50
|
+
*/
|
|
51
|
+
export const jsonApiRelationships = (type, idSchema) => async (req) => {
|
|
52
|
+
const context = await createBodyContext(req);
|
|
53
|
+
return parseRelationshipsRequest(context, type, idSchema);
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Creates a {@link BodyContext} from an {@link HttpRequest}.
|
|
57
|
+
*/
|
|
58
|
+
const createBodyContext = async (req) => {
|
|
59
|
+
const body = await consumers.text(req.body);
|
|
60
|
+
return {
|
|
61
|
+
body: body,
|
|
62
|
+
/* node:coverage ignore next */
|
|
63
|
+
contentType: req.headers.get("content-type") ?? undefined,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,SAAS,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAOH,wBAAwB,EACxB,yBAAyB,EACzB,oBAAoB,GAIvB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GACrB,CAOI,KAAuF,EAGzF,EAAE,CACJ,CACI,GAAgB,EACqE,EAAE;IACvF,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AACvC,CAAC,CAAC;AAEN;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GACxB,CAOI,OAMC,EACD,WAAoB,EAStB,EAAE,CACJ,KAAK,EACD,GAAgB,EASlB,EAAE;IACA,IAAI,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAEhC,IAAI,WAAW,EAAE,CAAC;QACd,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACnD,MAAM,CAAC,UAAU,EAAE,yBAAyB,CAAC,CAAC;QAC9C,MAAM,CAAC,WAAW,IAAI,UAAU,EAAE,mBAAmB,WAAW,WAAW,CAAC,CAAC;QAC7E,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;QAEvC,kEAAkE;QAClE,gEAAgE;QAChE,mEAAmE;QACnE,oCAAoC;QACpC,QAAQ,GAAG,CAAC,QAAQ;YAChB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAqC,CAAC;YAC/D,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAyB,CAAC;IACrD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC7C,OAAO,oBAAoB,CAAC,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AACnE,CAAC,CAAC;AAEN;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAC5B,CACI,IAAY,EACZ,QAAoB,EAGtB,EAAE,CACJ,KAAK,EACD,GAAgB,EAGlB,EAAE;IACA,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC7C,OAAO,wBAAwB,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEN;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC7B,CAAC,IAAY,EAAE,QAA2B,EAAuB,EAAE,CACnE,KAAK,EAAE,GAAgB,EAAqB,EAAE;IAC1C,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAC7C,OAAO,yBAAyB,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9D,CAAC,CAAC;AAEN;;GAEG;AACH,MAAM,iBAAiB,GAAG,KAAK,EAAE,GAAgB,EAAwB,EAAE;IACvE,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5C,OAAO;QACH,IAAI,EAAE,IAAI;QACV,+BAA+B;QAC/B,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS;KAC5D,CAAC;AACN,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
package/dist/layer.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import "./augment.js";
|
|
2
|
+
import { type JsonApiMediaType } from "@jsonapi-serde/server/http";
|
|
3
|
+
import { ExtensionKey, type HttpRequest, type HttpResponse } from "@taxum/core/http";
|
|
4
|
+
import type { HttpService } from "@taxum/core/service";
|
|
5
|
+
export declare const JSON_API_MEDIA_TYPES: ExtensionKey<JsonApiMediaType[]>;
|
|
6
|
+
export declare const JSON_API_VERIFY_ACCEPT_MEDIA_TYPE: ExtensionKey<(acceptableTypes: JsonApiMediaType[]) => void>;
|
|
7
|
+
/**
|
|
8
|
+
* Layer that parses and validates the `Accept` header for JSON:API media types.
|
|
9
|
+
*
|
|
10
|
+
* On success, adds acceptable media types to the request extensions.
|
|
11
|
+
* On failure, responds with HTTP 400 and a JSON:API error document.
|
|
12
|
+
*/
|
|
13
|
+
export declare const jsonApiMediaTypesLayer: import("@taxum/core/layer").Layer<JsonApiMediaTypes, HttpService>;
|
|
14
|
+
declare class JsonApiMediaTypes implements HttpService {
|
|
15
|
+
private readonly inner;
|
|
16
|
+
constructor(inner: HttpService);
|
|
17
|
+
invoke(req: HttpRequest): Promise<HttpResponse>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
package/dist/layer.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import "./augment.js";
|
|
2
|
+
import { JsonApiError } from "@jsonapi-serde/server/common";
|
|
3
|
+
import { getAcceptableMediaTypes, MediaTypeParserError, } from "@jsonapi-serde/server/http";
|
|
4
|
+
import { ExtensionKey, TO_HTTP_RESPONSE, } from "@taxum/core/http";
|
|
5
|
+
import { layerFn } from "@taxum/core/layer";
|
|
6
|
+
export const JSON_API_MEDIA_TYPES = new ExtensionKey("JSON:API Media Types");
|
|
7
|
+
export const JSON_API_VERIFY_ACCEPT_MEDIA_TYPE = new ExtensionKey("JSON:API Document");
|
|
8
|
+
/**
|
|
9
|
+
* Layer that parses and validates the `Accept` header for JSON:API media types.
|
|
10
|
+
*
|
|
11
|
+
* On success, adds acceptable media types to the request extensions.
|
|
12
|
+
* On failure, responds with HTTP 400 and a JSON:API error document.
|
|
13
|
+
*/
|
|
14
|
+
export const jsonApiMediaTypesLayer = layerFn((inner) => new JsonApiMediaTypes(inner));
|
|
15
|
+
class JsonApiMediaTypes {
|
|
16
|
+
inner;
|
|
17
|
+
constructor(inner) {
|
|
18
|
+
this.inner = inner;
|
|
19
|
+
}
|
|
20
|
+
async invoke(req) {
|
|
21
|
+
let acceptableMediaTypes;
|
|
22
|
+
try {
|
|
23
|
+
acceptableMediaTypes = getAcceptableMediaTypes(req.headers.get("accept") ?? "");
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
/* node:coverage disable */
|
|
27
|
+
if (!(error instanceof MediaTypeParserError)) {
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
/* node:coverage enable */
|
|
31
|
+
return new JsonApiError({
|
|
32
|
+
status: "400",
|
|
33
|
+
code: "bad_request",
|
|
34
|
+
title: "Bad Request",
|
|
35
|
+
detail: error.message,
|
|
36
|
+
source: {
|
|
37
|
+
header: "accept",
|
|
38
|
+
},
|
|
39
|
+
})[TO_HTTP_RESPONSE]();
|
|
40
|
+
}
|
|
41
|
+
req.extensions.insert(JSON_API_MEDIA_TYPES, acceptableMediaTypes);
|
|
42
|
+
const res = await this.inner.invoke(req);
|
|
43
|
+
if (res.status.isSuccess()) {
|
|
44
|
+
res.extensions.get(JSON_API_VERIFY_ACCEPT_MEDIA_TYPE)?.(acceptableMediaTypes);
|
|
45
|
+
}
|
|
46
|
+
return res;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=layer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer.js","sourceRoot":"","sources":["../src/layer.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,EAAwB,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAClF,OAAO,EACH,uBAAuB,EAEvB,oBAAoB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACH,YAAY,EAGZ,gBAAgB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAG5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,YAAY,CAAqB,sBAAsB,CAAC,CAAC;AACjG,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,YAAY,CAE/D,mBAAmB,CAAC,CAAC;AAEvB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC,CAAC,KAAkB,EAAE,EAAE,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;AAEpG,MAAM,iBAAiB;IACF,KAAK,CAAc;IAEpC,YAAmB,KAAkB;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,GAAgB;QAChC,IAAI,oBAAwC,CAAC;QAE7C,IAAI,CAAC;YACD,oBAAoB,GAAG,uBAAuB,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,2BAA2B;YAC3B,IAAI,CAAC,CAAC,KAAK,YAAY,oBAAoB,CAAC,EAAE,CAAC;gBAC3C,MAAM,KAAK,CAAC;YAChB,CAAC;YACD,0BAA0B;YAE1B,OAAO,IAAI,YAAY,CAAC;gBACpB,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,aAAa;gBACpB,MAAM,EAAE,KAAK,CAAC,OAAO;gBACrB,MAAM,EAAE;oBACJ,MAAM,EAAE,QAAQ;iBACnB;aACJ,CAAC,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC3B,CAAC;QAED,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;QAElE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAEzC,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;YACzB,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,iCAAiC,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC;QAClF,CAAC;QAED,OAAO,GAAG,CAAC;IACf,CAAC;CACJ"}
|
package/dist/router.d.ts
ADDED
package/dist/router.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "./augment.js";
|
|
2
|
+
import { JsonApiError } from "@jsonapi-serde/server/common";
|
|
3
|
+
import { TO_HTTP_RESPONSE } from "@taxum/core/http";
|
|
4
|
+
export const notFoundHandler = () => {
|
|
5
|
+
return new JsonApiError({
|
|
6
|
+
status: "404",
|
|
7
|
+
code: "not_found",
|
|
8
|
+
title: "Resource not found",
|
|
9
|
+
})[TO_HTTP_RESPONSE]();
|
|
10
|
+
};
|
|
11
|
+
export const methodNotAllowedHandler = () => {
|
|
12
|
+
return new JsonApiError({
|
|
13
|
+
status: "405",
|
|
14
|
+
code: "method_not_allowed",
|
|
15
|
+
title: "Method not allowed",
|
|
16
|
+
})[TO_HTTP_RESPONSE]();
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAqB,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGvE,MAAM,CAAC,MAAM,eAAe,GAAY,GAAiB,EAAE;IACvD,OAAO,IAAI,YAAY,CAAC;QACpB,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,oBAAoB;KAC9B,CAAC,CAAC,gBAAgB,CAAC,EAAE,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAY,GAAiB,EAAE;IAC/D,OAAO,IAAI,YAAY,CAAC;QACpB,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,oBAAoB;KAC9B,CAAC,CAAC,gBAAgB,CAAC,EAAE,CAAC;AAC3B,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jsonapi-serde/integration-taxum",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Taxum integration for @jsonapi-serde/server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Ben Scholzen 'DASPRiD'",
|
|
7
|
+
"license": "BSD-3-Clause",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"jsonapi",
|
|
10
|
+
"typescript",
|
|
11
|
+
"zod",
|
|
12
|
+
"serializer",
|
|
13
|
+
"deserializer",
|
|
14
|
+
"parser",
|
|
15
|
+
"taxum"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/dasprid/jsonapi-serde-js.git"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist/**/*"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": [
|
|
27
|
+
"./dist/index.d.ts",
|
|
28
|
+
"./src/index.ts"
|
|
29
|
+
],
|
|
30
|
+
"@jsonapi-serde": "./src/index.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./augment": {
|
|
34
|
+
"types": [
|
|
35
|
+
"./dist/augment.d.ts",
|
|
36
|
+
"./src/augment.ts"
|
|
37
|
+
],
|
|
38
|
+
"@jsonapi-serde": "./src/augment.ts",
|
|
39
|
+
"default": "./dist/augment.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@standard-schema/spec": "^1.0.0",
|
|
44
|
+
"@taxum/core": "^0.8.0",
|
|
45
|
+
"zod": "^4.0.17",
|
|
46
|
+
"@jsonapi-serde/server": "^1.2.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@taxum/core": "^0.1.0",
|
|
50
|
+
"zod": "^4.0.14",
|
|
51
|
+
"@jsonapi-serde/server": "^1.2.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -p tsconfig.build.json",
|
|
55
|
+
"test": "tsx -C jsonapi-serde --test --test-reporter=spec --experimental-test-module-mocks --no-warnings=ExperimentalWarning test/**/*.ts",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"ci:test": "c8 --reporter=lcov pnpm test"
|
|
58
|
+
}
|
|
59
|
+
}
|