@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 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
+ [![Test](https://github.com/DASPRiD/jsonapi-serde-js/actions/workflows/test.yml/badge.svg)](https://github.com/DASPRiD/jsonapi-serde-js/actions/workflows/test.yml)
4
+ [![codecov](https://codecov.io/gh/DASPRiD/jsonapi-serde-js/graph/badge.svg?token=UfRUzGqiN3&component=integration_taxum)](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'
@@ -0,0 +1,7 @@
1
+ import { type ToHttpResponse } from "@taxum/core/http";
2
+ declare module "@jsonapi-serde/server/common" {
3
+ interface JsonApiDocument extends ToHttpResponse {
4
+ }
5
+ interface JsonApiError extends ToHttpResponse {
6
+ }
7
+ }
@@ -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[]>;
@@ -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"}
@@ -0,0 +1,5 @@
1
+ export * from "./error-handler.js";
2
+ export * from "./extract.js";
3
+ export * from "./layer.js";
4
+ export * from "./layer.js";
5
+ export * from "./router.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./error-handler.js";
2
+ export * from "./extract.js";
3
+ export * from "./layer.js";
4
+ export * from "./layer.js";
5
+ export * from "./router.js";
6
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -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"}
@@ -0,0 +1,4 @@
1
+ import "./augment.js";
2
+ import type { Handler } from "@taxum/core/routing";
3
+ export declare const notFoundHandler: Handler;
4
+ export declare const methodNotAllowedHandler: Handler;
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
+ }