@efebia/fastify-zod-reply 1.1.0 → 1.2.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/lib/cjs/index.d.ts +5 -25
- package/lib/cjs/index.js +4 -27
- package/lib/cjs/plugin.d.ts +20 -0
- package/lib/cjs/plugin.js +51 -0
- package/lib/cjs/pluginV4.d.ts +20 -0
- package/lib/cjs/pluginV4.js +51 -0
- package/lib/cjs/route.js +17 -6
- package/lib/cjs/routeV4.js +11 -5
- package/lib/cjs/types.d.ts +1 -0
- package/lib/cjs/utils.d.ts +2 -0
- package/lib/cjs/utils.js +15 -1
- package/lib/esm/index.d.ts +5 -25
- package/lib/esm/index.js +3 -27
- package/lib/esm/plugin.d.ts +20 -0
- package/lib/esm/plugin.js +44 -0
- package/lib/esm/pluginV4.d.ts +20 -0
- package/lib/esm/pluginV4.js +44 -0
- package/lib/esm/route.js +17 -6
- package/lib/esm/routeV4.js +11 -5
- package/lib/esm/types.d.ts +1 -0
- package/lib/esm/utils.d.ts +2 -0
- package/lib/esm/utils.js +14 -1
- package/package.json +3 -2
package/lib/cjs/index.d.ts
CHANGED
|
@@ -1,35 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
payload: any;
|
|
4
|
-
};
|
|
5
|
-
export interface FastifyStatusCode {
|
|
6
|
-
ok: StatusCode<200>;
|
|
7
|
-
created: StatusCode<201>;
|
|
8
|
-
accepted: StatusCode<202>;
|
|
9
|
-
noContent: StatusCode<204>;
|
|
10
|
-
badRequest: StatusCode<400>;
|
|
11
|
-
unauthorized: StatusCode<401>;
|
|
12
|
-
forbidden: StatusCode<403>;
|
|
13
|
-
notFound: StatusCode<404>;
|
|
14
|
-
notAcceptable: StatusCode<406>;
|
|
15
|
-
conflict: StatusCode<409>;
|
|
16
|
-
internalServerError: StatusCode<500>;
|
|
17
|
-
}
|
|
18
|
-
export type FastifyReplyPluginOptions = {
|
|
19
|
-
statusCodes?: {
|
|
20
|
-
[key in keyof FastifyStatusCode]?: FastifyStatusCode[key];
|
|
21
|
-
};
|
|
22
|
-
};
|
|
1
|
+
import pluginV4, { type FastifyReplyV4PluginOptions, type StatusCodeV4, type StatusCodeV4Builder } from './pluginV4.js';
|
|
2
|
+
import { StatusCodeKey } from './types.js';
|
|
23
3
|
export type DecoratedReply = {
|
|
24
|
-
[key in
|
|
4
|
+
[key in StatusCodeKey]: <T>(val?: T) => T;
|
|
25
5
|
};
|
|
26
6
|
declare module 'fastify' {
|
|
27
7
|
interface FastifyReply extends DecoratedReply {
|
|
28
8
|
}
|
|
29
9
|
}
|
|
30
|
-
declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyPluginOptions, import("fastify").RawServerDefault, import("fastify").FastifyTypeProviderDefault, import("fastify").FastifyBaseLogger>;
|
|
31
|
-
export default _default;
|
|
32
10
|
export * from './error.js';
|
|
11
|
+
export * from './plugin.js';
|
|
33
12
|
export * from './route.js';
|
|
34
13
|
export * from './routeV4.js';
|
|
35
14
|
export * from './types.js';
|
|
15
|
+
export { FastifyReplyV4PluginOptions, pluginV4, StatusCodeV4, StatusCodeV4Builder };
|
package/lib/cjs/index.js
CHANGED
|
@@ -17,34 +17,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const defaultOptions = {
|
|
24
|
-
statusCodes: {
|
|
25
|
-
ok: { statusCode: 200, payload: { message: 'ok' } },
|
|
26
|
-
created: { statusCode: 201, payload: { message: 'created' } },
|
|
27
|
-
accepted: { statusCode: 202, payload: { message: 'accepted' } },
|
|
28
|
-
noContent: { statusCode: 204, payload: { message: 'noContent' } },
|
|
29
|
-
badRequest: { statusCode: 400, payload: { message: 'badRequest' } },
|
|
30
|
-
unauthorized: { statusCode: 401, payload: { message: 'unauthorized' } },
|
|
31
|
-
forbidden: { statusCode: 403, payload: { message: 'forbidden' } },
|
|
32
|
-
notFound: { statusCode: 404, payload: { message: 'notFound' } },
|
|
33
|
-
notAcceptable: { statusCode: 406, payload: { message: 'notAcceptable' } },
|
|
34
|
-
conflict: { statusCode: 409, payload: { message: 'conflict' } },
|
|
35
|
-
internalServerError: { statusCode: 500, payload: { message: 'internalServerError' } },
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
exports.default = (0, fastify_plugin_1.default)(async (fastify, opts) => {
|
|
39
|
-
const finalOptions = (0, utils_js_1.mergeDeep)(defaultOptions, opts);
|
|
40
|
-
Object.entries(finalOptions.statusCodes).forEach(([key, value]) => {
|
|
41
|
-
fastify.decorateReply(key, (0, reply_js_1.createReply)(value.statusCode, value.payload));
|
|
42
|
-
});
|
|
43
|
-
}, {
|
|
44
|
-
fastify: "5.x",
|
|
45
|
-
name: '@efebia/fastify-zod-reply'
|
|
46
|
-
});
|
|
20
|
+
exports.pluginV4 = void 0;
|
|
21
|
+
const pluginV4_js_1 = __importDefault(require("./pluginV4.js"));
|
|
22
|
+
exports.pluginV4 = pluginV4_js_1.default;
|
|
47
23
|
__exportStar(require("./error.js"), exports);
|
|
24
|
+
__exportStar(require("./plugin.js"), exports);
|
|
48
25
|
__exportStar(require("./route.js"), exports);
|
|
49
26
|
__exportStar(require("./routeV4.js"), exports);
|
|
50
27
|
__exportStar(require("./types.js"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { StatusCodeKey } from "./types.js";
|
|
3
|
+
export type StatusCode<TSchema extends z.ZodType> = {
|
|
4
|
+
statusCode: number;
|
|
5
|
+
payload: z.output<TSchema>;
|
|
6
|
+
schema: TSchema;
|
|
7
|
+
};
|
|
8
|
+
export declare class StatusCodeBuilder<TOpts extends Partial<Record<StatusCodeKey, StatusCode<any>>> = {}> {
|
|
9
|
+
readonly obj: TOpts;
|
|
10
|
+
constructor(obj?: TOpts);
|
|
11
|
+
set<TKey extends keyof TOpts extends never ? StatusCodeKey : Exclude<StatusCodeKey, keyof TOpts>, TSchema extends z.ZodType>(key: TKey, { schema, payload }: {
|
|
12
|
+
schema: TSchema;
|
|
13
|
+
payload: z.output<TSchema>;
|
|
14
|
+
}): StatusCodeBuilder<TOpts & { [key in TKey]: StatusCode<TSchema>; }>;
|
|
15
|
+
}
|
|
16
|
+
export type FastifyReplyPluginOptions = {
|
|
17
|
+
statusCodes?: StatusCodeBuilder;
|
|
18
|
+
};
|
|
19
|
+
declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyPluginOptions, import("fastify").RawServerDefault, import("fastify").FastifyTypeProviderDefault, import("fastify").FastifyBaseLogger>;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StatusCodeBuilder = void 0;
|
|
7
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const reply_js_1 = require("./reply.js");
|
|
10
|
+
const utils_js_1 = require("./utils.js");
|
|
11
|
+
class StatusCodeBuilder {
|
|
12
|
+
constructor(obj = {}) {
|
|
13
|
+
this.obj = obj;
|
|
14
|
+
}
|
|
15
|
+
set(key, { schema, payload }) {
|
|
16
|
+
return new StatusCodeBuilder(Object.assign(Object.assign({}, this.obj), { [key]: { statusCode: utils_js_1.statusByText[key], schema, payload } }));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.StatusCodeBuilder = StatusCodeBuilder;
|
|
20
|
+
const messageSchema = zod_1.z.object({ message: zod_1.z.string() });
|
|
21
|
+
const defaultOptions = {
|
|
22
|
+
statusCodes: new StatusCodeBuilder()
|
|
23
|
+
.set("ok", { schema: messageSchema, payload: { message: "ok" } })
|
|
24
|
+
.set("created", { schema: messageSchema, payload: { message: "created" } })
|
|
25
|
+
.set("accepted", { schema: messageSchema, payload: { message: "accepted" } })
|
|
26
|
+
.set("noContent", { schema: zod_1.z.undefined(), payload: undefined })
|
|
27
|
+
.set("badRequest", { schema: messageSchema, payload: { message: "badRequest" } })
|
|
28
|
+
.set("unauthorized", { schema: messageSchema, payload: { message: "unauthorized" } })
|
|
29
|
+
.set("forbidden", { schema: messageSchema, payload: { message: "forbidden" } })
|
|
30
|
+
.set("notFound", { schema: messageSchema, payload: { message: "notFound" } })
|
|
31
|
+
.set("notAcceptable", { schema: messageSchema, payload: { message: "notAcceptable" } })
|
|
32
|
+
.set("conflict", { schema: messageSchema, payload: { message: "conflict" } })
|
|
33
|
+
.set("internalServerError", { schema: messageSchema, payload: { message: "internalServerError" } })
|
|
34
|
+
};
|
|
35
|
+
exports.default = (0, fastify_plugin_1.default)(async (fastify, opts) => {
|
|
36
|
+
var _a, _b, _c;
|
|
37
|
+
const finalOptions = {
|
|
38
|
+
statusCodes: new StatusCodeBuilder(Object.assign(Object.assign({}, (_a = defaultOptions.statusCodes) === null || _a === void 0 ? void 0 : _a.obj), (_b = opts.statusCodes) === null || _b === void 0 ? void 0 : _b.obj)),
|
|
39
|
+
};
|
|
40
|
+
Object.entries(((_c = finalOptions.statusCodes) === null || _c === void 0 ? void 0 : _c.obj) || {}).forEach(([key, value]) => {
|
|
41
|
+
fastify.decorateReply(key, (0, reply_js_1.createReply)(value.statusCode, value.payload));
|
|
42
|
+
fastify.decorateReply(`SCHEMA_${value.statusCode}`, {
|
|
43
|
+
getter() {
|
|
44
|
+
return value.schema;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}, {
|
|
49
|
+
fastify: "5.x",
|
|
50
|
+
name: "@efebia/fastify-zod-reply",
|
|
51
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { StatusCodeKey } from "./types.js";
|
|
3
|
+
export type StatusCodeV4<TSchema extends z.ZodType> = {
|
|
4
|
+
statusCode: number;
|
|
5
|
+
payload: z.output<TSchema>;
|
|
6
|
+
schema: TSchema;
|
|
7
|
+
};
|
|
8
|
+
export declare class StatusCodeV4Builder<TOpts extends Partial<Record<StatusCodeKey, StatusCodeV4<any>>> = {}> {
|
|
9
|
+
readonly obj: TOpts;
|
|
10
|
+
constructor(obj?: TOpts);
|
|
11
|
+
set<TKey extends keyof TOpts extends never ? StatusCodeKey : Exclude<StatusCodeKey, keyof TOpts>, TSchema extends z.ZodType>(key: TKey, { schema, payload }: {
|
|
12
|
+
schema: TSchema;
|
|
13
|
+
payload: z.output<TSchema>;
|
|
14
|
+
}): StatusCodeV4Builder<TOpts & { [key in TKey]: StatusCodeV4<TSchema>; }>;
|
|
15
|
+
}
|
|
16
|
+
export type FastifyReplyV4PluginOptions = {
|
|
17
|
+
statusCodes?: StatusCodeV4Builder;
|
|
18
|
+
};
|
|
19
|
+
declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyV4PluginOptions, import("fastify").RawServerDefault, import("fastify").FastifyTypeProviderDefault, import("fastify").FastifyBaseLogger>;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.StatusCodeV4Builder = void 0;
|
|
7
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
8
|
+
const v4_1 = require("zod/v4");
|
|
9
|
+
const reply_js_1 = require("./reply.js");
|
|
10
|
+
const utils_js_1 = require("./utils.js");
|
|
11
|
+
class StatusCodeV4Builder {
|
|
12
|
+
constructor(obj = {}) {
|
|
13
|
+
this.obj = obj;
|
|
14
|
+
}
|
|
15
|
+
set(key, { schema, payload }) {
|
|
16
|
+
return new StatusCodeV4Builder(Object.assign(Object.assign({}, this.obj), { [key]: { statusCode: utils_js_1.statusByText[key], schema, payload } }));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.StatusCodeV4Builder = StatusCodeV4Builder;
|
|
20
|
+
const messageSchema = v4_1.z.object({ message: v4_1.z.string() });
|
|
21
|
+
const defaultOptions = {
|
|
22
|
+
statusCodes: new StatusCodeV4Builder()
|
|
23
|
+
.set("ok", { schema: messageSchema, payload: { message: "ok" } })
|
|
24
|
+
.set("created", { schema: messageSchema, payload: { message: "created" } })
|
|
25
|
+
.set("accepted", { schema: messageSchema, payload: { message: "accepted" } })
|
|
26
|
+
.set("noContent", { schema: v4_1.z.undefined(), payload: undefined })
|
|
27
|
+
.set("badRequest", { schema: messageSchema, payload: { message: "badRequest" } })
|
|
28
|
+
.set("unauthorized", { schema: messageSchema, payload: { message: "unauthorized" } })
|
|
29
|
+
.set("forbidden", { schema: messageSchema, payload: { message: "forbidden" } })
|
|
30
|
+
.set("notFound", { schema: messageSchema, payload: { message: "notFound" } })
|
|
31
|
+
.set("notAcceptable", { schema: messageSchema, payload: { message: "notAcceptable" } })
|
|
32
|
+
.set("conflict", { schema: messageSchema, payload: { message: "conflict" } })
|
|
33
|
+
.set("internalServerError", { schema: messageSchema, payload: { message: "internalServerError" } })
|
|
34
|
+
};
|
|
35
|
+
exports.default = (0, fastify_plugin_1.default)(async (fastify, opts) => {
|
|
36
|
+
var _a, _b, _c;
|
|
37
|
+
const finalOptions = {
|
|
38
|
+
statusCodes: new StatusCodeV4Builder(Object.assign(Object.assign({}, (_a = defaultOptions.statusCodes) === null || _a === void 0 ? void 0 : _a.obj), (_b = opts.statusCodes) === null || _b === void 0 ? void 0 : _b.obj)),
|
|
39
|
+
};
|
|
40
|
+
Object.entries(((_c = finalOptions.statusCodes) === null || _c === void 0 ? void 0 : _c.obj) || {}).forEach(([key, value]) => {
|
|
41
|
+
fastify.decorateReply(key, (0, reply_js_1.createReply)(value.statusCode, value.payload));
|
|
42
|
+
fastify.decorateReply(`SCHEMA_${value.statusCode}`, {
|
|
43
|
+
getter() {
|
|
44
|
+
return value.schema;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}, {
|
|
49
|
+
fastify: "5.x",
|
|
50
|
+
name: "@efebia/fastify-zod-reply",
|
|
51
|
+
});
|
package/lib/cjs/route.js
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.route = exports.createRoute = void 0;
|
|
4
4
|
const zod_to_json_schema_1 = require("zod-to-json-schema");
|
|
5
|
-
const
|
|
5
|
+
const error_js_1 = require("./error.js");
|
|
6
|
+
const mapZodError = (zodError, prefix) => {
|
|
7
|
+
return zodError.issues.map(issue => {
|
|
8
|
+
const pathStr = `Error at ${prefix}->${issue.path.join('->')}`;
|
|
9
|
+
return issue.message ? `${pathStr}->${issue.message}` : pathStr;
|
|
10
|
+
}).join('\n');
|
|
11
|
+
};
|
|
6
12
|
const parse = async (schema, payload, tag) => {
|
|
7
13
|
const result = await schema.safeParseAsync(payload);
|
|
8
14
|
return Object.assign(Object.assign({}, result), { tag });
|
|
@@ -60,16 +66,21 @@ const createRoute = ({ strict: globalStrict = false } = {}) => (schema, handler,
|
|
|
60
66
|
request.query = ((_c = results.find(r => r.tag === 'query')) === null || _c === void 0 ? void 0 : _c.data) || {};
|
|
61
67
|
},
|
|
62
68
|
preSerialization: (request, reply, payload, done) => {
|
|
63
|
-
|
|
69
|
+
var _a;
|
|
70
|
+
const foundLocalSchema = findStatusCode(reply.statusCode, Object.entries(schema.Reply.shape));
|
|
71
|
+
const foundGlobalSchema = reply[`SCHEMA_${reply.statusCode}`];
|
|
72
|
+
const foundSchema = (_a = foundLocalSchema === null || foundLocalSchema === void 0 ? void 0 : foundLocalSchema[1]) !== null && _a !== void 0 ? _a : foundGlobalSchema;
|
|
64
73
|
if (!foundSchema) {
|
|
65
|
-
request.log.
|
|
66
|
-
|
|
74
|
+
request.log.error(`[@efebia/fastify-zod-reply]: Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`);
|
|
75
|
+
reply.code(500);
|
|
76
|
+
return done(new error_js_1.FastifyZodReplyError(`Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`, 500));
|
|
67
77
|
}
|
|
68
|
-
const serialized = foundSchema
|
|
78
|
+
const serialized = foundSchema.safeParse(payload);
|
|
69
79
|
if (serialized.success) {
|
|
70
80
|
return done(null, serialized.data);
|
|
71
81
|
}
|
|
72
|
-
|
|
82
|
+
reply.code(500);
|
|
83
|
+
return done(new error_js_1.FastifyZodReplyError(mapZodError(serialized.error, 'reply'), 500));
|
|
73
84
|
},
|
|
74
85
|
};
|
|
75
86
|
};
|
package/lib/cjs/routeV4.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.routeV4 = exports.createRouteV4 = void 0;
|
|
4
4
|
const v4_1 = require("zod/v4");
|
|
5
|
+
const error_js_1 = require("./error.js");
|
|
5
6
|
const mapZodError = (zodError, prefix) => {
|
|
6
7
|
return zodError.issues.map(issue => {
|
|
7
8
|
const pathStr = `Error at ${prefix}->${issue.path.join('->')}`;
|
|
@@ -62,16 +63,21 @@ const createRouteV4 = ({ strict: globalStrict = false } = {}) => (schema, handle
|
|
|
62
63
|
request.query = ((_c = results.find(r => r.tag === 'query')) === null || _c === void 0 ? void 0 : _c.data) || {};
|
|
63
64
|
},
|
|
64
65
|
preSerialization: (request, reply, payload, done) => {
|
|
65
|
-
|
|
66
|
+
var _a;
|
|
67
|
+
const foundLocalSchema = findStatusCode(reply.statusCode, Object.entries(schema.Reply.shape));
|
|
68
|
+
const foundGlobalSchema = reply[`SCHEMA_${reply.statusCode}`];
|
|
69
|
+
const foundSchema = (_a = foundLocalSchema === null || foundLocalSchema === void 0 ? void 0 : foundLocalSchema[1]) !== null && _a !== void 0 ? _a : foundGlobalSchema;
|
|
66
70
|
if (!foundSchema) {
|
|
67
|
-
request.log.
|
|
68
|
-
|
|
71
|
+
request.log.error(`[@efebia/fastify-zod-reply]: Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`);
|
|
72
|
+
reply.code(500);
|
|
73
|
+
return done(new error_js_1.FastifyZodReplyError(`Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`, 500));
|
|
69
74
|
}
|
|
70
|
-
const serialized = foundSchema
|
|
75
|
+
const serialized = foundSchema.safeParse(payload);
|
|
71
76
|
if (serialized.success) {
|
|
72
77
|
return done(null, serialized.data);
|
|
73
78
|
}
|
|
74
|
-
|
|
79
|
+
reply.code(500);
|
|
80
|
+
return done(new error_js_1.FastifyZodReplyError(mapZodError(serialized.error, 'reply'), 500));
|
|
75
81
|
},
|
|
76
82
|
};
|
|
77
83
|
};
|
package/lib/cjs/types.d.ts
CHANGED
package/lib/cjs/utils.d.ts
CHANGED
package/lib/cjs/utils.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.statusByText = void 0;
|
|
3
4
|
exports.isObject = isObject;
|
|
4
5
|
exports.mergeDeep = mergeDeep;
|
|
5
6
|
function isObject(item) {
|
|
6
|
-
return
|
|
7
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
7
8
|
}
|
|
8
9
|
function mergeDeep(target, ...sources) {
|
|
9
10
|
if (!sources.length)
|
|
@@ -23,3 +24,16 @@ function mergeDeep(target, ...sources) {
|
|
|
23
24
|
}
|
|
24
25
|
return mergeDeep(target, ...sources);
|
|
25
26
|
}
|
|
27
|
+
exports.statusByText = {
|
|
28
|
+
ok: 200,
|
|
29
|
+
created: 201,
|
|
30
|
+
accepted: 202,
|
|
31
|
+
noContent: 204,
|
|
32
|
+
badRequest: 400,
|
|
33
|
+
unauthorized: 401,
|
|
34
|
+
forbidden: 403,
|
|
35
|
+
notFound: 404,
|
|
36
|
+
notAcceptable: 406,
|
|
37
|
+
conflict: 409,
|
|
38
|
+
internalServerError: 500,
|
|
39
|
+
};
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,35 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
payload: any;
|
|
4
|
-
};
|
|
5
|
-
export interface FastifyStatusCode {
|
|
6
|
-
ok: StatusCode<200>;
|
|
7
|
-
created: StatusCode<201>;
|
|
8
|
-
accepted: StatusCode<202>;
|
|
9
|
-
noContent: StatusCode<204>;
|
|
10
|
-
badRequest: StatusCode<400>;
|
|
11
|
-
unauthorized: StatusCode<401>;
|
|
12
|
-
forbidden: StatusCode<403>;
|
|
13
|
-
notFound: StatusCode<404>;
|
|
14
|
-
notAcceptable: StatusCode<406>;
|
|
15
|
-
conflict: StatusCode<409>;
|
|
16
|
-
internalServerError: StatusCode<500>;
|
|
17
|
-
}
|
|
18
|
-
export type FastifyReplyPluginOptions = {
|
|
19
|
-
statusCodes?: {
|
|
20
|
-
[key in keyof FastifyStatusCode]?: FastifyStatusCode[key];
|
|
21
|
-
};
|
|
22
|
-
};
|
|
1
|
+
import pluginV4, { type FastifyReplyV4PluginOptions, type StatusCodeV4, type StatusCodeV4Builder } from './pluginV4.js';
|
|
2
|
+
import { StatusCodeKey } from './types.js';
|
|
23
3
|
export type DecoratedReply = {
|
|
24
|
-
[key in
|
|
4
|
+
[key in StatusCodeKey]: <T>(val?: T) => T;
|
|
25
5
|
};
|
|
26
6
|
declare module 'fastify' {
|
|
27
7
|
interface FastifyReply extends DecoratedReply {
|
|
28
8
|
}
|
|
29
9
|
}
|
|
30
|
-
declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyPluginOptions, import("fastify").RawServerDefault, import("fastify").FastifyTypeProviderDefault, import("fastify").FastifyBaseLogger>;
|
|
31
|
-
export default _default;
|
|
32
10
|
export * from './error.js';
|
|
11
|
+
export * from './plugin.js';
|
|
33
12
|
export * from './route.js';
|
|
34
13
|
export * from './routeV4.js';
|
|
35
14
|
export * from './types.js';
|
|
15
|
+
export { FastifyReplyV4PluginOptions, pluginV4, StatusCodeV4, StatusCodeV4Builder };
|
package/lib/esm/index.js
CHANGED
|
@@ -1,31 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { createReply } from "./reply.js";
|
|
3
|
-
import { mergeDeep } from "./utils.js";
|
|
4
|
-
const defaultOptions = {
|
|
5
|
-
statusCodes: {
|
|
6
|
-
ok: { statusCode: 200, payload: { message: 'ok' } },
|
|
7
|
-
created: { statusCode: 201, payload: { message: 'created' } },
|
|
8
|
-
accepted: { statusCode: 202, payload: { message: 'accepted' } },
|
|
9
|
-
noContent: { statusCode: 204, payload: { message: 'noContent' } },
|
|
10
|
-
badRequest: { statusCode: 400, payload: { message: 'badRequest' } },
|
|
11
|
-
unauthorized: { statusCode: 401, payload: { message: 'unauthorized' } },
|
|
12
|
-
forbidden: { statusCode: 403, payload: { message: 'forbidden' } },
|
|
13
|
-
notFound: { statusCode: 404, payload: { message: 'notFound' } },
|
|
14
|
-
notAcceptable: { statusCode: 406, payload: { message: 'notAcceptable' } },
|
|
15
|
-
conflict: { statusCode: 409, payload: { message: 'conflict' } },
|
|
16
|
-
internalServerError: { statusCode: 500, payload: { message: 'internalServerError' } },
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
export default fp(async (fastify, opts) => {
|
|
20
|
-
const finalOptions = mergeDeep(defaultOptions, opts);
|
|
21
|
-
Object.entries(finalOptions.statusCodes).forEach(([key, value]) => {
|
|
22
|
-
fastify.decorateReply(key, createReply(value.statusCode, value.payload));
|
|
23
|
-
});
|
|
24
|
-
}, {
|
|
25
|
-
fastify: "5.x",
|
|
26
|
-
name: '@efebia/fastify-zod-reply'
|
|
27
|
-
});
|
|
1
|
+
import pluginV4 from './pluginV4.js';
|
|
28
2
|
export * from './error.js';
|
|
3
|
+
export * from './plugin.js';
|
|
29
4
|
export * from './route.js';
|
|
30
5
|
export * from './routeV4.js';
|
|
31
6
|
export * from './types.js';
|
|
7
|
+
export { pluginV4 };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { StatusCodeKey } from "./types.js";
|
|
3
|
+
export type StatusCode<TSchema extends z.ZodType> = {
|
|
4
|
+
statusCode: number;
|
|
5
|
+
payload: z.output<TSchema>;
|
|
6
|
+
schema: TSchema;
|
|
7
|
+
};
|
|
8
|
+
export declare class StatusCodeBuilder<TOpts extends Partial<Record<StatusCodeKey, StatusCode<any>>> = {}> {
|
|
9
|
+
readonly obj: TOpts;
|
|
10
|
+
constructor(obj?: TOpts);
|
|
11
|
+
set<TKey extends keyof TOpts extends never ? StatusCodeKey : Exclude<StatusCodeKey, keyof TOpts>, TSchema extends z.ZodType>(key: TKey, { schema, payload }: {
|
|
12
|
+
schema: TSchema;
|
|
13
|
+
payload: z.output<TSchema>;
|
|
14
|
+
}): StatusCodeBuilder<TOpts & { [key in TKey]: StatusCode<TSchema>; }>;
|
|
15
|
+
}
|
|
16
|
+
export type FastifyReplyPluginOptions = {
|
|
17
|
+
statusCodes?: StatusCodeBuilder;
|
|
18
|
+
};
|
|
19
|
+
declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyPluginOptions, import("fastify").RawServerDefault, import("fastify").FastifyTypeProviderDefault, import("fastify").FastifyBaseLogger>;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fp from "fastify-plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createReply } from "./reply.js";
|
|
4
|
+
import { statusByText } from "./utils.js";
|
|
5
|
+
export class StatusCodeBuilder {
|
|
6
|
+
constructor(obj = {}) {
|
|
7
|
+
this.obj = obj;
|
|
8
|
+
}
|
|
9
|
+
set(key, { schema, payload }) {
|
|
10
|
+
return new StatusCodeBuilder(Object.assign(Object.assign({}, this.obj), { [key]: { statusCode: statusByText[key], schema, payload } }));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const messageSchema = z.object({ message: z.string() });
|
|
14
|
+
const defaultOptions = {
|
|
15
|
+
statusCodes: new StatusCodeBuilder()
|
|
16
|
+
.set("ok", { schema: messageSchema, payload: { message: "ok" } })
|
|
17
|
+
.set("created", { schema: messageSchema, payload: { message: "created" } })
|
|
18
|
+
.set("accepted", { schema: messageSchema, payload: { message: "accepted" } })
|
|
19
|
+
.set("noContent", { schema: z.undefined(), payload: undefined })
|
|
20
|
+
.set("badRequest", { schema: messageSchema, payload: { message: "badRequest" } })
|
|
21
|
+
.set("unauthorized", { schema: messageSchema, payload: { message: "unauthorized" } })
|
|
22
|
+
.set("forbidden", { schema: messageSchema, payload: { message: "forbidden" } })
|
|
23
|
+
.set("notFound", { schema: messageSchema, payload: { message: "notFound" } })
|
|
24
|
+
.set("notAcceptable", { schema: messageSchema, payload: { message: "notAcceptable" } })
|
|
25
|
+
.set("conflict", { schema: messageSchema, payload: { message: "conflict" } })
|
|
26
|
+
.set("internalServerError", { schema: messageSchema, payload: { message: "internalServerError" } })
|
|
27
|
+
};
|
|
28
|
+
export default fp(async (fastify, opts) => {
|
|
29
|
+
var _a, _b, _c;
|
|
30
|
+
const finalOptions = {
|
|
31
|
+
statusCodes: new StatusCodeBuilder(Object.assign(Object.assign({}, (_a = defaultOptions.statusCodes) === null || _a === void 0 ? void 0 : _a.obj), (_b = opts.statusCodes) === null || _b === void 0 ? void 0 : _b.obj)),
|
|
32
|
+
};
|
|
33
|
+
Object.entries(((_c = finalOptions.statusCodes) === null || _c === void 0 ? void 0 : _c.obj) || {}).forEach(([key, value]) => {
|
|
34
|
+
fastify.decorateReply(key, createReply(value.statusCode, value.payload));
|
|
35
|
+
fastify.decorateReply(`SCHEMA_${value.statusCode}`, {
|
|
36
|
+
getter() {
|
|
37
|
+
return value.schema;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}, {
|
|
42
|
+
fastify: "5.x",
|
|
43
|
+
name: "@efebia/fastify-zod-reply",
|
|
44
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import { StatusCodeKey } from "./types.js";
|
|
3
|
+
export type StatusCodeV4<TSchema extends z.ZodType> = {
|
|
4
|
+
statusCode: number;
|
|
5
|
+
payload: z.output<TSchema>;
|
|
6
|
+
schema: TSchema;
|
|
7
|
+
};
|
|
8
|
+
export declare class StatusCodeV4Builder<TOpts extends Partial<Record<StatusCodeKey, StatusCodeV4<any>>> = {}> {
|
|
9
|
+
readonly obj: TOpts;
|
|
10
|
+
constructor(obj?: TOpts);
|
|
11
|
+
set<TKey extends keyof TOpts extends never ? StatusCodeKey : Exclude<StatusCodeKey, keyof TOpts>, TSchema extends z.ZodType>(key: TKey, { schema, payload }: {
|
|
12
|
+
schema: TSchema;
|
|
13
|
+
payload: z.output<TSchema>;
|
|
14
|
+
}): StatusCodeV4Builder<TOpts & { [key in TKey]: StatusCodeV4<TSchema>; }>;
|
|
15
|
+
}
|
|
16
|
+
export type FastifyReplyV4PluginOptions = {
|
|
17
|
+
statusCodes?: StatusCodeV4Builder;
|
|
18
|
+
};
|
|
19
|
+
declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyV4PluginOptions, import("fastify").RawServerDefault, import("fastify").FastifyTypeProviderDefault, import("fastify").FastifyBaseLogger>;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fp from "fastify-plugin";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
import { createReply } from "./reply.js";
|
|
4
|
+
import { statusByText } from "./utils.js";
|
|
5
|
+
export class StatusCodeV4Builder {
|
|
6
|
+
constructor(obj = {}) {
|
|
7
|
+
this.obj = obj;
|
|
8
|
+
}
|
|
9
|
+
set(key, { schema, payload }) {
|
|
10
|
+
return new StatusCodeV4Builder(Object.assign(Object.assign({}, this.obj), { [key]: { statusCode: statusByText[key], schema, payload } }));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const messageSchema = z.object({ message: z.string() });
|
|
14
|
+
const defaultOptions = {
|
|
15
|
+
statusCodes: new StatusCodeV4Builder()
|
|
16
|
+
.set("ok", { schema: messageSchema, payload: { message: "ok" } })
|
|
17
|
+
.set("created", { schema: messageSchema, payload: { message: "created" } })
|
|
18
|
+
.set("accepted", { schema: messageSchema, payload: { message: "accepted" } })
|
|
19
|
+
.set("noContent", { schema: z.undefined(), payload: undefined })
|
|
20
|
+
.set("badRequest", { schema: messageSchema, payload: { message: "badRequest" } })
|
|
21
|
+
.set("unauthorized", { schema: messageSchema, payload: { message: "unauthorized" } })
|
|
22
|
+
.set("forbidden", { schema: messageSchema, payload: { message: "forbidden" } })
|
|
23
|
+
.set("notFound", { schema: messageSchema, payload: { message: "notFound" } })
|
|
24
|
+
.set("notAcceptable", { schema: messageSchema, payload: { message: "notAcceptable" } })
|
|
25
|
+
.set("conflict", { schema: messageSchema, payload: { message: "conflict" } })
|
|
26
|
+
.set("internalServerError", { schema: messageSchema, payload: { message: "internalServerError" } })
|
|
27
|
+
};
|
|
28
|
+
export default fp(async (fastify, opts) => {
|
|
29
|
+
var _a, _b, _c;
|
|
30
|
+
const finalOptions = {
|
|
31
|
+
statusCodes: new StatusCodeV4Builder(Object.assign(Object.assign({}, (_a = defaultOptions.statusCodes) === null || _a === void 0 ? void 0 : _a.obj), (_b = opts.statusCodes) === null || _b === void 0 ? void 0 : _b.obj)),
|
|
32
|
+
};
|
|
33
|
+
Object.entries(((_c = finalOptions.statusCodes) === null || _c === void 0 ? void 0 : _c.obj) || {}).forEach(([key, value]) => {
|
|
34
|
+
fastify.decorateReply(key, createReply(value.statusCode, value.payload));
|
|
35
|
+
fastify.decorateReply(`SCHEMA_${value.statusCode}`, {
|
|
36
|
+
getter() {
|
|
37
|
+
return value.schema;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}, {
|
|
42
|
+
fastify: "5.x",
|
|
43
|
+
name: "@efebia/fastify-zod-reply",
|
|
44
|
+
});
|
package/lib/esm/route.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
2
|
-
|
|
2
|
+
import { FastifyZodReplyError } from './error.js';
|
|
3
|
+
const mapZodError = (zodError, prefix) => {
|
|
4
|
+
return zodError.issues.map(issue => {
|
|
5
|
+
const pathStr = `Error at ${prefix}->${issue.path.join('->')}`;
|
|
6
|
+
return issue.message ? `${pathStr}->${issue.message}` : pathStr;
|
|
7
|
+
}).join('\n');
|
|
8
|
+
};
|
|
3
9
|
const parse = async (schema, payload, tag) => {
|
|
4
10
|
const result = await schema.safeParseAsync(payload);
|
|
5
11
|
return Object.assign(Object.assign({}, result), { tag });
|
|
@@ -57,16 +63,21 @@ export const createRoute = ({ strict: globalStrict = false } = {}) => (schema, h
|
|
|
57
63
|
request.query = ((_c = results.find(r => r.tag === 'query')) === null || _c === void 0 ? void 0 : _c.data) || {};
|
|
58
64
|
},
|
|
59
65
|
preSerialization: (request, reply, payload, done) => {
|
|
60
|
-
|
|
66
|
+
var _a;
|
|
67
|
+
const foundLocalSchema = findStatusCode(reply.statusCode, Object.entries(schema.Reply.shape));
|
|
68
|
+
const foundGlobalSchema = reply[`SCHEMA_${reply.statusCode}`];
|
|
69
|
+
const foundSchema = (_a = foundLocalSchema === null || foundLocalSchema === void 0 ? void 0 : foundLocalSchema[1]) !== null && _a !== void 0 ? _a : foundGlobalSchema;
|
|
61
70
|
if (!foundSchema) {
|
|
62
|
-
request.log.
|
|
63
|
-
|
|
71
|
+
request.log.error(`[@efebia/fastify-zod-reply]: Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`);
|
|
72
|
+
reply.code(500);
|
|
73
|
+
return done(new FastifyZodReplyError(`Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`, 500));
|
|
64
74
|
}
|
|
65
|
-
const serialized = foundSchema
|
|
75
|
+
const serialized = foundSchema.safeParse(payload);
|
|
66
76
|
if (serialized.success) {
|
|
67
77
|
return done(null, serialized.data);
|
|
68
78
|
}
|
|
69
|
-
|
|
79
|
+
reply.code(500);
|
|
80
|
+
return done(new FastifyZodReplyError(mapZodError(serialized.error, 'reply'), 500));
|
|
70
81
|
},
|
|
71
82
|
};
|
|
72
83
|
};
|
package/lib/esm/routeV4.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
|
+
import { FastifyZodReplyError } from './error.js';
|
|
2
3
|
const mapZodError = (zodError, prefix) => {
|
|
3
4
|
return zodError.issues.map(issue => {
|
|
4
5
|
const pathStr = `Error at ${prefix}->${issue.path.join('->')}`;
|
|
@@ -59,16 +60,21 @@ export const createRouteV4 = ({ strict: globalStrict = false } = {}) => (schema,
|
|
|
59
60
|
request.query = ((_c = results.find(r => r.tag === 'query')) === null || _c === void 0 ? void 0 : _c.data) || {};
|
|
60
61
|
},
|
|
61
62
|
preSerialization: (request, reply, payload, done) => {
|
|
62
|
-
|
|
63
|
+
var _a;
|
|
64
|
+
const foundLocalSchema = findStatusCode(reply.statusCode, Object.entries(schema.Reply.shape));
|
|
65
|
+
const foundGlobalSchema = reply[`SCHEMA_${reply.statusCode}`];
|
|
66
|
+
const foundSchema = (_a = foundLocalSchema === null || foundLocalSchema === void 0 ? void 0 : foundLocalSchema[1]) !== null && _a !== void 0 ? _a : foundGlobalSchema;
|
|
63
67
|
if (!foundSchema) {
|
|
64
|
-
request.log.
|
|
65
|
-
|
|
68
|
+
request.log.error(`[@efebia/fastify-zod-reply]: Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`);
|
|
69
|
+
reply.code(500);
|
|
70
|
+
return done(new FastifyZodReplyError(`Reply schema of: ${request.routeOptions.url} does not have the specified status code: ${reply.statusCode} nor there is a global schema for this status code.`, 500));
|
|
66
71
|
}
|
|
67
|
-
const serialized = foundSchema
|
|
72
|
+
const serialized = foundSchema.safeParse(payload);
|
|
68
73
|
if (serialized.success) {
|
|
69
74
|
return done(null, serialized.data);
|
|
70
75
|
}
|
|
71
|
-
|
|
76
|
+
reply.code(500);
|
|
77
|
+
return done(new FastifyZodReplyError(mapZodError(serialized.error, 'reply'), 500));
|
|
72
78
|
},
|
|
73
79
|
};
|
|
74
80
|
};
|
package/lib/esm/types.d.ts
CHANGED
package/lib/esm/utils.d.ts
CHANGED
package/lib/esm/utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function isObject(item) {
|
|
2
|
-
return
|
|
2
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
3
3
|
}
|
|
4
4
|
export function mergeDeep(target, ...sources) {
|
|
5
5
|
if (!sources.length)
|
|
@@ -19,3 +19,16 @@ export function mergeDeep(target, ...sources) {
|
|
|
19
19
|
}
|
|
20
20
|
return mergeDeep(target, ...sources);
|
|
21
21
|
}
|
|
22
|
+
export const statusByText = {
|
|
23
|
+
ok: 200,
|
|
24
|
+
created: 201,
|
|
25
|
+
accepted: 202,
|
|
26
|
+
noContent: 204,
|
|
27
|
+
badRequest: 400,
|
|
28
|
+
unauthorized: 401,
|
|
29
|
+
forbidden: 403,
|
|
30
|
+
notFound: 404,
|
|
31
|
+
notAcceptable: 406,
|
|
32
|
+
conflict: 409,
|
|
33
|
+
internalServerError: 500,
|
|
34
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efebia/fastify-zod-reply",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"fastify": "^5.3.0",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"build:esm": "tsc -p ./tsconfig.build.json --module nodenext --moduleResolution nodenext --outDir lib/esm",
|
|
16
16
|
"build:cjs": "tsc -p ./tsconfig.build.json --module commonjs --moduleResolution node --outDir lib/cjs",
|
|
17
17
|
"build": "yarn build:esm && yarn build:cjs",
|
|
18
|
-
"test": "node --import tsx --test ./**/*.test.ts"
|
|
18
|
+
"test": "node --import tsx --test ./**/*.test.ts",
|
|
19
|
+
"test:single": "node --import tsx --test"
|
|
19
20
|
},
|
|
20
21
|
"main": "./lib/esm/index.js",
|
|
21
22
|
"types": "./lib/esm/index.d.ts",
|