From 85a34703eaf3844e5b5a497bcae2e0a5d9208b29 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Sat, 11 May 2024 10:16:31 +0200 Subject: [PATCH 1/3] Feat: .addOptions() supports async fn. --- README.md | 7 +-- src/endpoints-factory.ts | 8 +++- tests/unit/endpoints-factory.spec.ts | 65 ++++++++++++++++------------ 3 files changed, 47 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ebc2b01d7..c20144cf6 100644 --- a/README.md +++ b/README.md @@ -298,9 +298,10 @@ instance, consider shorthand method `addOptions`. ```typescript 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 = fs.readFileSync("private-key.pem", "utf-8"); + return { db, privateKey }; }); ``` diff --git a/src/endpoints-factory.ts b/src/endpoints-factory.ts index 1e5f43875..0ee5a93a3 100644 --- a/src/endpoints-factory.ts +++ b/src/endpoints-factory.ts @@ -126,12 +126,16 @@ export class EndpointsFactory< ); } - public addOptions(options: AOUT) { + /** @todo consider removal of static options since 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 () => options, }), ), this.resultHandler, diff --git a/tests/unit/endpoints-factory.spec.ts b/tests/unit/endpoints-factory.spec.ts index eac3cd274..c75d27a31 100644 --- a/tests/unit/endpoints-factory.spec.ts +++ b/tests/unit/endpoints-factory.spec.ts @@ -91,38 +91,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: {} as AbstractLogger, + }), + ).toEqual({ + option1: "some value", + option2: "other value", + }); + expect(newFactory["resultHandler"]).toStrictEqual(resultHandlerMock); + }, + ); }); describe.each(["addExpressMiddleware" as const, "use" as const])( From 6049005fba57926708662fb3b4bfcf8e87f321f7 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Sat, 11 May 2024 10:26:19 +0200 Subject: [PATCH 2/3] Emit deprecation warning. --- src/endpoints-factory.ts | 12 ++++++++++-- tests/unit/endpoints-factory.spec.ts | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/endpoints-factory.ts b/src/endpoints-factory.ts index 0ee5a93a3..d297a56fb 100644 --- a/src/endpoints-factory.ts +++ b/src/endpoints-factory.ts @@ -126,7 +126,7 @@ export class EndpointsFactory< ); } - /** @todo consider removal of static options since it makes no sense */ + /** @todo remove the static options in v19 - it makes no sense */ public addOptions( options: AOUT | (() => Promise), ) { @@ -135,7 +135,15 @@ export class EndpointsFactory< createMiddleware({ input: z.object({}), middleware: - typeof options === "function" ? options : async () => options, + 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 c75d27a31..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"; @@ -123,7 +124,7 @@ describe("EndpointsFactory", () => { options: {}, request: {} as Request, response: {} as Response, - logger: {} as AbstractLogger, + logger: makeLoggerMock({ fnMethod: vi.fn }), }), ).toEqual({ option1: "some value", From c2b7747a3e070bb24691c2c2af24838276521a40 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Sat, 11 May 2024 10:34:20 +0200 Subject: [PATCH 3/3] Changelog: 18.6.0. --- CHANGELOG.md | 18 ++++++++++++++++++ README.md | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) 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 c20144cf6..5e3764821 100644 --- a/README.md +++ b/README.md @@ -296,11 +296,12 @@ 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(async () => { const db = mongoose.connect("mongodb://connection.string"); - const privateKey = fs.readFileSync("private-key.pem", "utf-8"); + const privateKey = await readFile("private-key.pem", "utf-8"); return { db, privateKey }; }); ```