@efebia/fastify-zod-reply 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,3 +2,72 @@ export declare class FastifyZodReplyError extends Error {
2
2
  statusCode: number;
3
3
  constructor(message?: string, statusCode?: number);
4
4
  }
5
+ export declare const createError: (statusCode: number) => (message: string | {
6
+ message: string;
7
+ }) => FastifyZodReplyError;
8
+ type FastifyZodReplyErrorObjectItem = {
9
+ statusCode: number;
10
+ payload: string | {
11
+ message: string;
12
+ };
13
+ };
14
+ declare const DEFAULT_OBJECT: {
15
+ readonly badRequest: {
16
+ readonly statusCode: 400;
17
+ readonly payload: {
18
+ readonly message: "badRequest";
19
+ };
20
+ };
21
+ readonly unauthorized: {
22
+ readonly statusCode: 401;
23
+ readonly payload: {
24
+ readonly message: "unauthorized";
25
+ };
26
+ };
27
+ readonly forbidden: {
28
+ readonly statusCode: 403;
29
+ readonly payload: {
30
+ readonly message: "forbidden";
31
+ };
32
+ };
33
+ readonly notFound: {
34
+ readonly statusCode: 404;
35
+ readonly payload: {
36
+ readonly message: "notFound";
37
+ };
38
+ };
39
+ readonly notAcceptable: {
40
+ readonly statusCode: 406;
41
+ readonly payload: {
42
+ readonly message: "notAcceptable";
43
+ };
44
+ };
45
+ readonly conflict: {
46
+ readonly statusCode: 409;
47
+ readonly payload: {
48
+ readonly message: "conflict";
49
+ };
50
+ };
51
+ readonly internalServerError: {
52
+ readonly statusCode: 500;
53
+ readonly payload: {
54
+ readonly message: "internalServerError";
55
+ };
56
+ };
57
+ };
58
+ type ReverseFastifyZodReplyErrorObjectItem<TOverrides extends Record<string, FastifyZodReplyErrorObjectItem>> = {
59
+ [key in keyof TOverrides as TOverrides[key]['statusCode']]: {
60
+ label: key;
61
+ payload: TOverrides[key]['payload'];
62
+ };
63
+ };
64
+ type Deduplicate<TOverrides extends Record<string, FastifyZodReplyErrorObjectItem>> = {
65
+ [key in keyof typeof DEFAULT_OBJECT as typeof DEFAULT_OBJECT[key]['statusCode'] extends keyof ReverseFastifyZodReplyErrorObjectItem<TOverrides> ? never : key]: typeof DEFAULT_OBJECT[key];
66
+ } & TOverrides;
67
+ type FastifyZodReplyErrorObject<TOverrides extends Record<string, FastifyZodReplyErrorObjectItem>> = {
68
+ [key in keyof Deduplicate<TOverrides>]: (arg?: string | {
69
+ message: string;
70
+ }) => FastifyZodReplyError;
71
+ };
72
+ export declare const buildHTTPErrorObject: <const TRecord extends Record<string, FastifyZodReplyErrorObjectItem>>(overrides?: TRecord) => FastifyZodReplyErrorObject<TRecord>;
73
+ export {};
package/lib/cjs/error.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FastifyZodReplyError = void 0;
3
+ exports.buildHTTPErrorObject = exports.createError = exports.FastifyZodReplyError = void 0;
4
4
  class FastifyZodReplyError extends Error {
5
5
  constructor(message, statusCode) {
6
6
  super(message);
@@ -8,3 +8,39 @@ class FastifyZodReplyError extends Error {
8
8
  }
9
9
  }
10
10
  exports.FastifyZodReplyError = FastifyZodReplyError;
11
+ const createError = (statusCode) => (message) => {
12
+ const customError = new FastifyZodReplyError("", statusCode);
13
+ if (typeof message === "string")
14
+ customError.message = message;
15
+ else
16
+ customError.message = message.message;
17
+ return customError;
18
+ };
19
+ exports.createError = createError;
20
+ const DEFAULT_OBJECT = {
21
+ badRequest: { statusCode: 400, payload: { message: "badRequest" } },
22
+ unauthorized: { statusCode: 401, payload: { message: "unauthorized" } },
23
+ forbidden: { statusCode: 403, payload: { message: "forbidden" } },
24
+ notFound: { statusCode: 404, payload: { message: "notFound" } },
25
+ notAcceptable: { statusCode: 406, payload: { message: "notAcceptable" } },
26
+ conflict: { statusCode: 409, payload: { message: "conflict" } },
27
+ internalServerError: {
28
+ statusCode: 500,
29
+ payload: { message: "internalServerError" },
30
+ },
31
+ };
32
+ const buildHTTPErrorObject = (overrides) => {
33
+ return Object.fromEntries([...Object.entries(overrides !== null && overrides !== void 0 ? overrides : {})
34
+ .concat(Object.entries(DEFAULT_OBJECT))
35
+ .reduce((acc, [label, value]) => {
36
+ if (acc.get(value.statusCode))
37
+ return acc;
38
+ acc.set(value.statusCode, { label, obj: value });
39
+ return acc;
40
+ }, new Map())
41
+ .entries()].map(([statusCode, value]) => {
42
+ const baseErrorFn = (0, exports.createError)(statusCode);
43
+ return [value.label, (arg) => baseErrorFn(arg !== null && arg !== void 0 ? arg : value.obj.payload)];
44
+ }));
45
+ };
46
+ exports.buildHTTPErrorObject = buildHTTPErrorObject;
@@ -23,12 +23,13 @@ export type FastifyReplyPluginOptions = {
23
23
  export type DecoratedReply = {
24
24
  [key in keyof FastifyStatusCode]: <T>(val?: T) => T;
25
25
  };
26
- declare module 'fastify' {
26
+ declare module "fastify" {
27
27
  interface FastifyReply extends DecoratedReply {
28
28
  }
29
29
  }
30
30
  declare const _default: import("fastify").FastifyPluginCallback<FastifyReplyPluginOptions, import("fastify").RawServerDefault, import("fastify").FastifyTypeProviderDefault, import("fastify").FastifyBaseLogger>;
31
31
  export default _default;
32
- export * from './error.js';
33
- export * from './routeV4.js';
34
- export * from './types.js';
32
+ export { buildHTTPErrorObject, FastifyZodReplyError } from "./error.js";
33
+ export * from "./routeV4.js";
34
+ export * from "./sseRouteV4.js";
35
+ export * from "./types.js";
package/lib/cjs/index.js CHANGED
@@ -17,33 +17,42 @@ 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
+ exports.FastifyZodReplyError = exports.buildHTTPErrorObject = void 0;
20
21
  const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
21
22
  const reply_js_1 = require("./reply.js");
22
23
  const utils_js_1 = require("./utils.js");
23
24
  const defaultOptions = {
24
25
  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: undefined },
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
- }
26
+ ok: { statusCode: 200, payload: { message: "ok" } },
27
+ created: { statusCode: 201, payload: { message: "created" } },
28
+ accepted: { statusCode: 202, payload: { message: "accepted" } },
29
+ noContent: { statusCode: 204, payload: null },
30
+ badRequest: { statusCode: 400, payload: { message: "badRequest" } },
31
+ unauthorized: { statusCode: 401, payload: { message: "unauthorized" } },
32
+ forbidden: { statusCode: 403, payload: { message: "forbidden" } },
33
+ notFound: { statusCode: 404, payload: { message: "notFound" } },
34
+ notAcceptable: { statusCode: 406, payload: { message: "notAcceptable" } },
35
+ conflict: { statusCode: 409, payload: { message: "conflict" } },
36
+ internalServerError: {
37
+ statusCode: 500,
38
+ payload: { message: "internalServerError" },
39
+ },
40
+ },
37
41
  };
38
42
  exports.default = (0, fastify_plugin_1.default)(async (fastify, opts) => {
39
43
  const finalOptions = (0, utils_js_1.mergeDeep)(defaultOptions, opts);
40
44
  Object.entries(finalOptions.statusCodes).forEach(([key, value]) => {
41
45
  fastify.decorateReply(key, (0, reply_js_1.createReply)(value.statusCode, value.payload));
42
46
  });
47
+ fastify.decorateRequest("abortController");
48
+ fastify.decorateReply("sse");
43
49
  }, {
44
50
  fastify: "5.x",
45
- name: '@efebia/fastify-zod-reply'
51
+ name: "@efebia/fastify-zod-reply",
46
52
  });
47
- __exportStar(require("./error.js"), exports);
53
+ var error_js_1 = require("./error.js");
54
+ Object.defineProperty(exports, "buildHTTPErrorObject", { enumerable: true, get: function () { return error_js_1.buildHTTPErrorObject; } });
55
+ Object.defineProperty(exports, "FastifyZodReplyError", { enumerable: true, get: function () { return error_js_1.FastifyZodReplyError; } });
48
56
  __exportStar(require("./routeV4.js"), exports);
57
+ __exportStar(require("./sseRouteV4.js"), exports);
49
58
  __exportStar(require("./types.js"), exports);
@@ -1,8 +1,4 @@
1
1
  import { FastifyReply } from "fastify";
2
- import { FastifyZodReplyError } from "./error.js";
3
2
  type ReplyFunction<T> = T extends (...args: any[]) => any ? (this: FastifyReply, ...args: Parameters<T>) => ReturnType<T> : never;
4
3
  export declare const createReply: (statusCode: number, defaultPayload: object) => ReplyFunction<any>;
5
- export declare const createError: (statusCode: number) => (message: string | {
6
- message: string;
7
- }) => FastifyZodReplyError;
8
4
  export {};
package/lib/cjs/reply.js CHANGED
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createError = exports.createReply = void 0;
3
+ exports.createReply = void 0;
4
4
  const error_js_1 = require("./error.js");
5
5
  const createReply = (statusCode, defaultPayload) => {
6
6
  return function (payload) {
7
7
  const finalPayload = payload !== null && payload !== void 0 ? payload : defaultPayload;
8
8
  if (typeof finalPayload === 'string')
9
- throw (0, exports.createError)(statusCode)(finalPayload);
9
+ throw (0, error_js_1.createError)(statusCode)(finalPayload);
10
10
  if (typeof finalPayload !== 'undefined')
11
11
  this.type("application/json");
12
12
  this.code(statusCode);
@@ -14,12 +14,3 @@ const createReply = (statusCode, defaultPayload) => {
14
14
  };
15
15
  };
16
16
  exports.createReply = createReply;
17
- const createError = (statusCode) => (message) => {
18
- const customError = new error_js_1.FastifyZodReplyError("", statusCode);
19
- if (typeof message === "string")
20
- customError.message = message;
21
- else
22
- customError.message = message.message;
23
- return customError;
24
- };
25
- exports.createError = createError;
@@ -0,0 +1,17 @@
1
+ import { z } from "zod/v4";
2
+ import { RouteV4Options } from "./routeV4.js";
3
+ export declare const parse: (schema: z.ZodTypeAny, payload: any, tag: string) => Promise<{
4
+ tag: string;
5
+ success: true;
6
+ data: unknown;
7
+ error?: never;
8
+ } | {
9
+ tag: string;
10
+ success: false;
11
+ data?: never;
12
+ error: z.ZodError<unknown>;
13
+ }>;
14
+ export declare const strictifySchema: (schema: z.ZodType, strict: boolean) => any;
15
+ export declare const parseStrict: (tag: keyof Exclude<NonNullable<RouteV4Options["strict"]>, boolean>, value: NonNullable<RouteV4Options["strict"]>) => boolean;
16
+ export declare const findStatusCode: (statusCode: number, availableStatusCodes: [string | number, any][]) => [string | number, any] | undefined;
17
+ export declare const mapZodError: (zodError: z.ZodError, prefix: string) => string;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapZodError = exports.findStatusCode = exports.parseStrict = exports.strictifySchema = exports.parse = void 0;
4
+ const parse = async (schema, payload, tag) => {
5
+ const result = await schema.safeParseAsync(payload);
6
+ return Object.assign(Object.assign({}, result), { tag });
7
+ };
8
+ exports.parse = parse;
9
+ const strictifySchema = (schema, strict) => {
10
+ if (!strict)
11
+ return schema;
12
+ return "strict" in schema && typeof schema["strict"] === "function"
13
+ ? schema.strict()
14
+ : schema;
15
+ };
16
+ exports.strictifySchema = strictifySchema;
17
+ const parseStrict = (tag, value) => {
18
+ if (typeof value === "boolean")
19
+ return value;
20
+ return value[tag];
21
+ };
22
+ exports.parseStrict = parseStrict;
23
+ const findStatusCode = (statusCode, availableStatusCodes) => {
24
+ return availableStatusCodes.find(([key]) => {
25
+ if (!["number", "string"].includes(typeof key))
26
+ return false;
27
+ if (typeof key === "number")
28
+ return statusCode === key;
29
+ if (/^[0-9]{3}$/.test(key))
30
+ return statusCode === parseInt(key);
31
+ if (/^[0-9]xx$/i.test(key))
32
+ return statusCode.toString()[0] === key[0];
33
+ });
34
+ };
35
+ exports.findStatusCode = findStatusCode;
36
+ const mapZodError = (zodError, prefix) => {
37
+ return zodError.issues
38
+ .map((issue) => {
39
+ const pathStr = `Error at ${prefix}->${issue.path.join("->")}`;
40
+ return issue.message ? `${pathStr}->${issue.message}` : pathStr;
41
+ })
42
+ .join("\n");
43
+ };
44
+ exports.mapZodError = mapZodError;
@@ -1,19 +1,21 @@
1
- import { z } from 'zod/v4';
2
- import { APIHandler, APIOptions, RouteSecurity, RouteTag } from './types.js';
1
+ import { z } from "zod/v4";
2
+ import { APIHandler, APIOptions, RouteSecurity, RouteTag } from "./types.js";
3
3
  export type BaseZodV4Schema = {
4
4
  Body?: z.ZodTypeAny;
5
5
  Params?: z.ZodTypeAny;
6
6
  Query?: z.ZodTypeAny;
7
7
  Headers?: z.ZodTypeAny;
8
- Reply: z.ZodObject;
9
- Security?: (RouteSecurity[keyof RouteSecurity])[];
8
+ Reply: z.ZodObject<{
9
+ [key: string | number]: z.ZodTypeAny;
10
+ }>;
11
+ Security?: RouteSecurity[keyof RouteSecurity][];
10
12
  Tags?: (keyof RouteTag)[];
11
13
  };
12
14
  export type FastifyZodV4Schema<TZodSchema extends BaseZodV4Schema> = {
13
- Body: TZodSchema['Body'] extends z.ZodTypeAny ? z.output<TZodSchema['Body']> : undefined;
14
- Params: TZodSchema['Params'] extends z.ZodTypeAny ? z.output<TZodSchema['Params']> : undefined;
15
- Querystring: TZodSchema['Query'] extends z.ZodTypeAny ? z.output<TZodSchema['Query']> : undefined;
16
- Reply: TZodSchema['Reply'] extends z.ZodTypeAny ? z.input<TZodSchema['Reply']>[keyof z.input<TZodSchema['Reply']>] : undefined;
15
+ Body: TZodSchema["Body"] extends z.ZodTypeAny ? z.output<TZodSchema["Body"]> : undefined;
16
+ Params: TZodSchema["Params"] extends z.ZodTypeAny ? z.output<TZodSchema["Params"]> : undefined;
17
+ Querystring: TZodSchema["Query"] extends z.ZodTypeAny ? z.output<TZodSchema["Query"]> : undefined;
18
+ Reply: TZodSchema["Reply"] extends z.ZodTypeAny ? z.input<TZodSchema["Reply"]>[keyof z.input<TZodSchema["Reply"]>] : undefined;
17
19
  };
18
20
  export type RouteV4Options = {
19
21
  strict?: boolean | {
@@ -23,9 +25,9 @@ export type RouteV4Options = {
23
25
  headers: boolean;
24
26
  };
25
27
  };
26
- export declare const createRouteV4: ({ strict: globalStrict }?: RouteV4Options) => <TSchema extends BaseZodV4Schema, FastifySchema extends FastifyZodV4Schema<TSchema> = FastifyZodV4Schema<TSchema>>(schema: TSchema, handler: APIHandler<FastifySchema>, options?: RouteV4Options) => APIOptions<FastifySchema> & {
28
+ export declare const createRouteV4: <RequestAugmentation extends object = {}, ReplyAugmentation extends object = {}>({ strict: globalStrict }?: RouteV4Options) => <TSchema extends BaseZodV4Schema, FastifySchema extends FastifyZodV4Schema<TSchema> = FastifyZodV4Schema<TSchema>>(schema: TSchema, handler: NoInfer<APIHandler<FastifySchema, RequestAugmentation, ReplyAugmentation>>, options?: RouteV4Options) => APIOptions<FastifySchema> & {
27
29
  handler: APIHandler<FastifySchema>;
28
30
  };
29
- export declare const routeV4: <TSchema extends BaseZodV4Schema, FastifySchema extends FastifyZodV4Schema<TSchema> = FastifyZodV4Schema<TSchema>>(schema: TSchema, handler: APIHandler<FastifySchema>, options?: RouteV4Options) => APIOptions<FastifySchema> & {
31
+ export declare const routeV4: <TSchema extends BaseZodV4Schema, FastifySchema extends FastifyZodV4Schema<TSchema> = FastifyZodV4Schema<TSchema>>(schema: TSchema, handler: NoInfer<APIHandler<FastifySchema, {}, {}>>, options?: RouteV4Options) => APIOptions<FastifySchema> & {
30
32
  handler: APIHandler<FastifySchema>;
31
33
  };
@@ -3,67 +3,54 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.routeV4 = exports.createRouteV4 = void 0;
4
4
  const v4_1 = require("zod/v4");
5
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
- };
12
- const parse = async (schema, payload, tag) => {
13
- const result = await schema.safeParseAsync(payload);
14
- return Object.assign(Object.assign({}, result), { tag });
15
- };
16
- const findStatusCode = (statusCode, availableStatusCodes) => {
17
- return availableStatusCodes.find(([key]) => {
18
- if (!['number', 'string'].includes(typeof key))
19
- return false;
20
- if (typeof key === 'number')
21
- return statusCode === key;
22
- if (/^[0-9]{3}$/.test(key))
23
- return statusCode === parseInt(key);
24
- if (/^[0-9]xx$/i.test(key))
25
- return statusCode.toString()[0] === key[0];
26
- });
27
- };
28
- const strictifySchema = (schema, strict) => {
29
- if (!strict)
30
- return schema;
31
- return 'strict' in schema && typeof schema['strict'] === 'function' ? schema.strict() : schema;
32
- };
33
- const parseStrict = (tag, value) => {
34
- if (typeof value === 'boolean')
35
- return value;
36
- return value[tag];
37
- };
6
+ const routeHelpers_js_1 = require("./routeHelpers.js");
38
7
  const createRouteV4 = ({ strict: globalStrict = false } = {}) => (schema, handler, options) => {
39
- const strict = typeof (options === null || options === void 0 ? void 0 : options.strict) !== 'undefined' ? options === null || options === void 0 ? void 0 : options.strict : globalStrict;
40
- const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && { body: v4_1.z.toJSONSchema(strictifySchema(schema.Body, parseStrict('body', strict)), { reused: 'inline', target: "draft-7", io: 'input' }) })), (schema.Params && { params: v4_1.z.toJSONSchema(strictifySchema(schema.Params, parseStrict('params', strict)), { reused: 'inline', target: "draft-7", io: 'input' }) })), (schema.Query && { querystring: v4_1.z.toJSONSchema(strictifySchema(schema.Query, parseStrict('query', strict)), { reused: 'inline', target: "draft-7", io: 'input' }) })), (schema.Headers && { headers: v4_1.z.toJSONSchema(strictifySchema(schema.Headers, parseStrict('headers', strict)), { reused: 'inline', target: "draft-7", io: 'input' }) })), { response: v4_1.z.toJSONSchema(schema.Reply.partial(), { reused: 'inline', target: "draft-7" })['properties'] }), (schema.Security && { security: schema.Security })), (schema.Tags && { tags: schema.Tags }));
8
+ const strict = typeof (options === null || options === void 0 ? void 0 : options.strict) !== "undefined" ? options === null || options === void 0 ? void 0 : options.strict : globalStrict;
9
+ const finalResult = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (schema.Body && {
10
+ body: v4_1.z.toJSONSchema((0, routeHelpers_js_1.strictifySchema)(schema.Body, (0, routeHelpers_js_1.parseStrict)("body", strict)), { reused: "inline", target: "draft-7", io: "input" }),
11
+ })), (schema.Params && {
12
+ params: v4_1.z.toJSONSchema((0, routeHelpers_js_1.strictifySchema)(schema.Params, (0, routeHelpers_js_1.parseStrict)("params", strict)), { reused: "inline", target: "draft-7", io: "input" }),
13
+ })), (schema.Query && {
14
+ querystring: v4_1.z.toJSONSchema((0, routeHelpers_js_1.strictifySchema)(schema.Query, (0, routeHelpers_js_1.parseStrict)("query", strict)), { reused: "inline", target: "draft-7", io: "input" }),
15
+ })), (schema.Headers && {
16
+ headers: v4_1.z.toJSONSchema((0, routeHelpers_js_1.strictifySchema)(schema.Headers, (0, routeHelpers_js_1.parseStrict)("headers", strict)), { reused: "inline", target: "draft-7", io: "input" }),
17
+ })), { response: v4_1.z.toJSONSchema(schema.Reply.partial(), {
18
+ reused: "inline",
19
+ target: "draft-7",
20
+ })["properties"] }), (schema.Security && { security: schema.Security })), (schema.Tags && { tags: schema.Tags }));
41
21
  return {
42
22
  schema: finalResult,
43
23
  handler,
44
24
  preHandler: async (request, reply) => {
45
25
  var _a, _b, _c;
46
26
  const results = await Promise.all([
47
- ...(schema.Body ? [parse(schema.Body, request.body, 'body')] : []),
48
- ...(schema.Params ? [parse(schema.Params, request.params, 'params')] : []),
49
- ...(schema.Query ? [parse(schema.Query, request.query, 'query')] : []),
27
+ ...(schema.Body ? [(0, routeHelpers_js_1.parse)(schema.Body, request.body, "body")] : []),
28
+ ...(schema.Params
29
+ ? [(0, routeHelpers_js_1.parse)(schema.Params, request.params, "params")]
30
+ : []),
31
+ ...(schema.Query
32
+ ? [(0, routeHelpers_js_1.parse)(schema.Query, request.query, "query")]
33
+ : []),
50
34
  ]);
51
35
  for (const result of results) {
52
36
  if (!result.success) {
53
37
  return reply
54
38
  .code(400)
55
- .type('application/json')
39
+ .type("application/json")
56
40
  .send({
57
- message: mapZodError(result.error, result.tag),
41
+ message: (0, routeHelpers_js_1.mapZodError)(result.error, result.tag),
58
42
  });
59
43
  }
60
44
  }
61
- request.body = ((_a = results.find(r => r.tag === 'body')) === null || _a === void 0 ? void 0 : _a.data) || {};
62
- request.params = ((_b = results.find(r => r.tag === 'params')) === null || _b === void 0 ? void 0 : _b.data) || {};
63
- request.query = ((_c = results.find(r => r.tag === 'query')) === null || _c === void 0 ? void 0 : _c.data) || {};
45
+ request.body =
46
+ ((_a = results.find((r) => r.tag === "body")) === null || _a === void 0 ? void 0 : _a.data) || {};
47
+ request.params =
48
+ ((_b = results.find((r) => r.tag === "params")) === null || _b === void 0 ? void 0 : _b.data) || {};
49
+ request.query =
50
+ ((_c = results.find((r) => r.tag === "query")) === null || _c === void 0 ? void 0 : _c.data) || {};
64
51
  },
65
52
  preSerialization: (request, reply, payload, done) => {
66
- const foundSchema = findStatusCode(reply.statusCode, Object.entries(schema.Reply.shape));
53
+ const foundSchema = (0, routeHelpers_js_1.findStatusCode)(reply.statusCode, Object.entries(schema.Reply.shape));
67
54
  if (!foundSchema) {
68
55
  if (reply.statusCode >= 400)
69
56
  return done(null, payload);
@@ -74,7 +61,7 @@ const createRouteV4 = ({ strict: globalStrict = false } = {}) => (schema, handle
74
61
  if (serialized.success) {
75
62
  return done(null, serialized.data);
76
63
  }
77
- return done(new error_js_1.FastifyZodReplyError(mapZodError(serialized.error, 'reply'), 500));
64
+ return done(new error_js_1.FastifyZodReplyError((0, routeHelpers_js_1.mapZodError)(serialized.error, "reply"), 500));
78
65
  },
79
66
  };
80
67
  };
@@ -0,0 +1,9 @@
1
+ import { FastifyReply } from "fastify";
2
+ import { z } from "zod/v4";
3
+ import { SSEReplyShape, SSERouteV4Options } from "./sseRouteV4.js";
4
+ export declare function sendSseStream<T extends SSEReplyShape>({ reply, stream, schema, options, }: {
5
+ reply: FastifyReply;
6
+ stream: AsyncGenerator<T>;
7
+ schema: z.ZodType<T>;
8
+ options?: SSERouteV4Options["sse"];
9
+ }): Promise<never>;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
3
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
4
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
5
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
6
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
7
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
8
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
9
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
10
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
11
+ function fulfill(value) { resume("next", value); }
12
+ function reject(value) { resume("throw", value); }
13
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.sendSseStream = sendSseStream;
17
+ const node_stream_1 = require("node:stream");
18
+ const error_js_1 = require("./error.js");
19
+ const routeHelpers_js_1 = require("./routeHelpers.js");
20
+ async function sendSseStream({ reply, stream, schema, options = {}, }) {
21
+ var _a;
22
+ reply.headers({
23
+ "Content-Type": "text/event-stream",
24
+ "Cache-Control": "no-cache, no-transform",
25
+ Connection: "keep-alive",
26
+ Vary: "Origin",
27
+ "X-Accel-Buffering": "no",
28
+ });
29
+ const sseStream = node_stream_1.Readable.from(createSSEStream({
30
+ stream,
31
+ schema,
32
+ logger: reply.log,
33
+ validate: (_a = options.validateStream) !== null && _a !== void 0 ? _a : true,
34
+ intervalMs: options.keepAliveInterval,
35
+ onError: options.onError,
36
+ }));
37
+ return reply.send(sseStream);
38
+ }
39
+ function createSSEStream(_a) {
40
+ return __asyncGenerator(this, arguments, function* createSSEStream_1({ stream, schema, logger, validate, onError, intervalMs = 30000, }) {
41
+ const streamIterator = stream[Symbol.asyncIterator]();
42
+ let keepAliveTimeout = null;
43
+ const scheduleKeepAlive = () => new Promise((resolve) => {
44
+ if (keepAliveTimeout)
45
+ clearTimeout(keepAliveTimeout);
46
+ keepAliveTimeout = setTimeout(() => resolve({ isKeepAlive: true }), intervalMs);
47
+ });
48
+ let nextChunkPromise = streamIterator.next();
49
+ try {
50
+ while (true) {
51
+ const keepAlivePromise = scheduleKeepAlive();
52
+ const result = yield __await(Promise.race([nextChunkPromise, keepAlivePromise]));
53
+ if ("isKeepAlive" in result && result.isKeepAlive) {
54
+ yield yield __await(": keep-alive\n\n");
55
+ continue;
56
+ }
57
+ if ("done" in result) {
58
+ if (keepAliveTimeout)
59
+ clearTimeout(keepAliveTimeout);
60
+ if (result.done)
61
+ break;
62
+ let itemToSend;
63
+ if (validate) {
64
+ const validation = schema.safeParse(result.value);
65
+ if (!validation.success) {
66
+ const errorMessage = (0, routeHelpers_js_1.mapZodError)(validation.error, "stream-item");
67
+ logger.error(`SSE Stream validation error: ${errorMessage}`);
68
+ if (onError)
69
+ onError(new error_js_1.FastifyZodReplyError(errorMessage, 500));
70
+ yield yield __await(formatSSEMessage({ event: "error", data: errorMessage }));
71
+ nextChunkPromise = streamIterator.next();
72
+ continue;
73
+ }
74
+ itemToSend = validation.data;
75
+ }
76
+ else {
77
+ itemToSend = result.value;
78
+ }
79
+ yield yield __await(formatSSEMessage(itemToSend));
80
+ nextChunkPromise = streamIterator.next();
81
+ }
82
+ }
83
+ }
84
+ catch (error) {
85
+ if (onError)
86
+ onError(error);
87
+ logger.error(error);
88
+ if (error instanceof Error && error.name !== "AbortError") {
89
+ yield yield __await(formatSSEMessage({ event: "error", data: error.message }));
90
+ }
91
+ }
92
+ finally {
93
+ if (keepAliveTimeout)
94
+ clearTimeout(keepAliveTimeout);
95
+ }
96
+ });
97
+ }
98
+ function formatSSEMessage(message) {
99
+ let output = "";
100
+ if (message.id !== undefined) {
101
+ output += `id: ${String(message.id)}\n`;
102
+ }
103
+ if (message.retry !== undefined) {
104
+ output += `retry: ${message.retry}\n`;
105
+ }
106
+ if (message.event) {
107
+ output += `event: ${message.event}\n`;
108
+ }
109
+ output += `data: ${JSON.stringify(message.data)}\n\n`;
110
+ return output;
111
+ }
@@ -0,0 +1,57 @@
1
+ import { z } from "zod/v4";
2
+ import { APIHandler, APIOptions, RouteSecurity, RouteTag } from "./types.js";
3
+ export type SSEReplyShape = {
4
+ data: unknown;
5
+ event?: string | undefined;
6
+ id?: string | number | undefined;
7
+ retry?: number | undefined;
8
+ };
9
+ export type SSEBaseZodV4Schema = {
10
+ Body?: z.ZodTypeAny;
11
+ Params?: z.ZodTypeAny;
12
+ Query?: z.ZodTypeAny;
13
+ Headers?: z.ZodTypeAny;
14
+ Reply: z.ZodObject<{
15
+ SSE: z.ZodType<SSEReplyShape>;
16
+ [key: string | number]: z.ZodTypeAny;
17
+ }>;
18
+ Security?: RouteSecurity[keyof RouteSecurity][];
19
+ Tags?: (keyof RouteTag)[];
20
+ };
21
+ type TransformSSETo200<T> = {
22
+ [K in keyof T as K extends "SSE" ? 200 : K]: T[K];
23
+ };
24
+ export type FastifySSEZodV4Schema<TZodSchema extends SSEBaseZodV4Schema> = {
25
+ Body: TZodSchema["Body"] extends z.ZodTypeAny ? z.output<TZodSchema["Body"]> : undefined;
26
+ Params: TZodSchema["Params"] extends z.ZodTypeAny ? z.output<TZodSchema["Params"]> : undefined;
27
+ Querystring: TZodSchema["Query"] extends z.ZodTypeAny ? z.output<TZodSchema["Query"]> : undefined;
28
+ Reply: TZodSchema["Reply"] extends z.ZodTypeAny ? TransformSSETo200<z.input<TZodSchema["Reply"]>> : undefined;
29
+ };
30
+ export type SSERouteV4Options = {
31
+ strict?: boolean | {
32
+ body: boolean;
33
+ query: boolean;
34
+ params: boolean;
35
+ headers: boolean;
36
+ };
37
+ sse?: {
38
+ keepAliveInterval?: number;
39
+ onError?: undefined | ((error: unknown) => void);
40
+ validateStream?: boolean;
41
+ };
42
+ };
43
+ export type SSEAugmentedAPIHandler<TSchema extends SSEBaseZodV4Schema, FastifySchema extends FastifySSEZodV4Schema<TSchema>, RequestAugmentation extends object = {}, ReplyAugmentation extends object = {}> = APIHandler<FastifySchema, RequestAugmentation & {
44
+ abortController: AbortController;
45
+ }, ReplyAugmentation & {
46
+ sse<T extends z.input<TSchema["Reply"]>["SSE"]>(options: {
47
+ stream: AsyncGenerator<T>;
48
+ onError?: (error: unknown) => void;
49
+ }): Promise<T>;
50
+ }>;
51
+ export declare function createSSERouteV4<RequestAugmentation extends object = {}, ReplyAugmentation extends object = {}>(globalOptions?: SSERouteV4Options): <TSchema extends SSEBaseZodV4Schema, FastifySchema extends FastifySSEZodV4Schema<TSchema> = FastifySSEZodV4Schema<TSchema>>(schema: TSchema, handler: NoInfer<SSEAugmentedAPIHandler<TSchema, FastifySchema, RequestAugmentation, ReplyAugmentation>>, options?: SSERouteV4Options) => APIOptions<FastifySchema> & {
52
+ handler: APIHandler<FastifySchema>;
53
+ };
54
+ export declare const sseRouteV4: <TSchema extends SSEBaseZodV4Schema, FastifySchema extends FastifySSEZodV4Schema<TSchema> = FastifySSEZodV4Schema<TSchema>>(schema: TSchema, handler: NoInfer<SSEAugmentedAPIHandler<TSchema, FastifySchema, {}, {}>>, options?: SSERouteV4Options) => APIOptions<FastifySchema> & {
55
+ handler: APIHandler<FastifySchema>;
56
+ };
57
+ export {};