diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ab8b4e4..d3a7d0991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ ## Version 18 +### v18.6.0 + +- Feat: Supporting async functon as an argument for `EndpointsFactory::addOptions()`: + - I realized that it does not make sense for `.addOptions` just to proxy the static data; + - In case your options are static you can just `import` the corresponding `const` instead; + - Static options are deprecated and its support will be removed in v19. + +```ts +import { readFile } from "node:fs/promises"; +import { defaultEndpointsFactory } from "express-zod-api"; + +const endpointsFactory = defaultEndpointsFactory.addOptions(async () => { + const db = mongoose.connect("mongodb://connection.string"); + const privateKey = await readFile("private-key.pem", "utf-8"); + return { db, privateKey }; +}); +``` + ### v18.5.2 - Muted uploader logs related to non-eligible requests; diff --git a/README.md b/README.md index ebc2b01d7..5e3764821 100644 --- a/README.md +++ b/README.md @@ -296,11 +296,13 @@ In case you'd like to provide your endpoints with options that do not depend on instance, consider shorthand method `addOptions`. ```typescript +import { readFile } from "node:fs/promises"; import { defaultEndpointsFactory } from "express-zod-api"; -const endpointsFactory = defaultEndpointsFactory.addOptions({ - db: mongoose.connect("mongodb://connection.string"), - privateKey: fs.readFileSync("private-key.pem", "utf-8"), +const endpointsFactory = defaultEndpointsFactory.addOptions(async () => { + const db = mongoose.connect("mongodb://connection.string"); + const privateKey = await readFile("private-key.pem", "utf-8"); + return { db, privateKey }; }); ``` diff --git a/src/endpoints-factory.ts b/src/endpoints-factory.ts index 1e5f43875..d297a56fb 100644 --- a/src/endpoints-factory.ts +++ b/src/endpoints-factory.ts @@ -126,12 +126,24 @@ export class EndpointsFactory< ); } - public addOptions(options: AOUT) { + /** @todo remove the static options in v19 - it makes no sense */ + public addOptions( + options: AOUT | (() => Promise), + ) { return EndpointsFactory.#create( this.middlewares.concat( createMiddleware({ input: z.object({}), - middleware: async () => options, + middleware: + typeof options === "function" + ? options + : async ({ logger }) => { + logger.warn( + "addOptions: Static options are deprecated. " + + "Replace with async function or just import the const.", + ); + return options; + }, }), ), this.resultHandler, diff --git a/tests/unit/endpoints-factory.spec.ts b/tests/unit/endpoints-factory.spec.ts index eac3cd274..722828ad8 100644 --- a/tests/unit/endpoints-factory.spec.ts +++ b/tests/unit/endpoints-factory.spec.ts @@ -9,6 +9,7 @@ import { import { Endpoint } from "../../src/endpoint"; import { expectType } from "tsd"; import { AbstractLogger } from "../../src/logger"; +import { makeLoggerMock } from "../../src/testing"; import { serializeSchemaForTest } from "../helpers"; import { z } from "zod"; import { describe, expect, test, vi } from "vitest"; @@ -91,38 +92,47 @@ describe("EndpointsFactory", () => { }); describe(".addOptions()", () => { - test("Should create a new factory with an empty-input middleware and the same result handler", async () => { - const resultHandlerMock = createResultHandler({ - getPositiveResponse: () => z.string(), - getNegativeResponse: () => z.string(), - handler: vi.fn(), - }); - const factory = new EndpointsFactory(resultHandlerMock); - const newFactory = factory.addOptions({ + test.each([ + { option1: "some value", option2: "other value", - }); - expect(factory["middlewares"]).toStrictEqual([]); - expect(factory["resultHandler"]).toStrictEqual(resultHandlerMock); - expect(newFactory["middlewares"].length).toBe(1); - expect(newFactory["middlewares"][0].input).toBeInstanceOf(z.ZodObject); - expect( - (newFactory["middlewares"][0].input as z.AnyZodObject).shape, - ).toEqual({}); - expect( - await newFactory["middlewares"][0].middleware({ - input: {}, - options: {}, - request: {} as Request, - response: {} as Response, - logger: {} as AbstractLogger, - }), - ).toEqual({ + }, + async () => ({ option1: "some value", option2: "other value", - }); - expect(newFactory["resultHandler"]).toStrictEqual(resultHandlerMock); - }); + }), + ])( + "Should create a new factory with an empty-input middleware and the same result handler", + async (options) => { + const resultHandlerMock = createResultHandler({ + getPositiveResponse: () => z.string(), + getNegativeResponse: () => z.string(), + handler: vi.fn(), + }); + const factory = new EndpointsFactory(resultHandlerMock); + const newFactory = factory.addOptions(options); + expect(factory["middlewares"]).toStrictEqual([]); + expect(factory["resultHandler"]).toStrictEqual(resultHandlerMock); + expect(newFactory["middlewares"].length).toBe(1); + expect(newFactory["middlewares"][0].input).toBeInstanceOf(z.ZodObject); + expect( + (newFactory["middlewares"][0].input as z.AnyZodObject).shape, + ).toEqual({}); + expect( + await newFactory["middlewares"][0].middleware({ + input: {}, + options: {}, + request: {} as Request, + response: {} as Response, + logger: makeLoggerMock({ fnMethod: vi.fn }), + }), + ).toEqual({ + option1: "some value", + option2: "other value", + }); + expect(newFactory["resultHandler"]).toStrictEqual(resultHandlerMock); + }, + ); }); describe.each(["addExpressMiddleware" as const, "use" as const])(