Repository: RobinTail/express-zod-api (ts)

Issue:
Add dependancy injection for handlers
Currently the package provides a logger in a sort of DI way which is extremely useful.
I believe it should be possible to generalise this so users could add their own dependancy objects to pass to handlers when building endpoints.
I've got some code that can sort of do this already however it is not perfect yet and would be a breaking change when it shouldn't be so not ready for a pull request yet.
An example usage would be
```
new EndPointFactory(...)
  .addDependancy({db:SomeDataBaseObject})
  .build({
    input:...
    output:...
    handler: async ({input, db, logger})=>{
      db.query();
      return ...;
    }),
  });
```

Sorry for any mistakes, its 04:00 am here.
What do you think of this?
I'd also like to expand it to add dependancies that get created with a function on each request too for stuff like specific database connections instead of a single database object.

Pull request:
Feature 230: EndpointsFactory::addOptions() method
Closes #230 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28ff4911..5e7d5926 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,32 @@
 
 ## Version 4
 
+### v4.1.0
+
+- Feature #230. The shorthand method `EndpointsFactory::addOptions()`.
+  - You may find it useful in case you'd like to provide your Endpoint's handler with some entities that do not depend
+    on Request, maybe a database connection instance of similar.
+  - Under the hood the method creates a middleware with an empty input and attaches it to the factory.
+  - The argument supplied to the method is available within `options` parameter of the Endpoint's `handler`.
+
+```typescript
+import { defaultEndpointsFactory } from "express-zod-api";
+
+const newFactory = defaultEndpointsFactory.addOptions({
+  db: mongoose.connect("mongodb://connection.string"),
+  privateKey: fs.readFileSync("private-key.pem", "utf-8"),
+});
+
+const endpoint = newFactory.build({
+  method: "get",
+  input: z.object({}),
+  output: z.object({}),
+  handler: async ({ options }) => {
+    return {}; // options: { db, privateKey }
+  },
+});
+```
+
 ### v4.0.0
 
 - The deprecated parameter `type` of `EndpointsFactory::build({...})` has been removed.
diff --git a/README.md b/README.md
index a68d1dbc..6d24032d 100644
--- a/README.md
+++ b/README.md
@@ -27,18 +27,19 @@ Start your API server with I/O schema validation and custom middlewares in minut
    7. [Try it](#try-it)
 4. [Fascinating features](#fascinating-features)
    1. [Middlewares](#middlewares)
-   2. [Refinements](#refinements)
-   3. [Transformations](#transformations)
-   4. [Route path params](#route-path-params)
-   5. [Response customization](#response-customization)
-   6. [Non-object response](#non-object-response) including file downloads
-   7. [File uploads](#file-uploads)
-   8. [Customizing logger](#customizing-logger)
-   9. [Usage with your own express app](#usage-with-your-own-express-app)
-   10. [Multiple schemas for one route](#multiple-schemas-for-one-route)
-   11. [Customizing input sources](#customizing-input-sources)
-   12. [Exporting endpoint types to frontend](#exporting-endpoint-types-to-frontend)
-   13. [Creating a documentation](#creating-a-documentation)
+   2. [Options](#options)
+   3. [Refinements](#refinements)
+   4. [Transformations](#transformations)
+   5. [Route path params](#route-path-params)
+   6. [Response customization](#response-customization)
+   7. [Non-object response](#non-object-response) including file downloads
+   8. [File uploads](#file-uploads)
+   9. [Customizing logger](#customizing-logger)
+   10. [Usage with your own express app](#usage-with-your-own-express-app)
+   11. [Multiple schemas for one route](#multiple-schemas-for-one-route)
+   12. [Customizing input sources](#customizing-input-sources)
+   13. [Exporting endpoint types to frontend](#exporting-endpoint-types-to-frontend)
+   14. [Creating a documentation](#creating-a-documentation)
 5. [Known issues](#known-issues)
    1. [Excessive properties in endpoint output](#excessive-properties-in-endpoint-output)
 6. [Your input to my output](#your-input-to-my-output)
@@ -265,6 +266,20 @@ const endpointsFactory = defaultEndpointsFactory.addMiddleware(authMiddleware);
 
 You can connect as many middlewares as you want, they will be executed in order.
 
+## Options
+
+In case you'd like to provide your endpoints with options that do not depend on Request, like database connection
+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"),
+});
+```
+
 ## Refinements
 
 By the way, you can implement additional validation within schema.
diff --git a/src/endpoints-factory.ts b/src/endpoints-factory.ts
index cfbba249..1d77cd5f 100644
--- a/src/endpoints-factory.ts
+++ b/src/endpoints-factory.ts
@@ -3,7 +3,7 @@ import { ApiResponse } from "./api-response";
 import { Endpoint, Handler } from "./endpoint";
 import { FlatObject, IOSchema, hasUpload, Merge } from "./common-helpers";
 import { Method, MethodsDefinition } from "./method";
-import { MiddlewareDefinition } from "./middleware";
+import { createMiddleware, MiddlewareDefinition } from "./middleware";
 import { mimeJson, mimeMultipart } from "./mime";
 import {
   defaultResultHandler,
@@ -60,6 +60,18 @@ export class EndpointsFactory<
     );
   }
 
+  public addOptions<OUT extends FlatObject>(options: OUT) {
+    return EndpointsFactory.#create<MwIN, MwOUT & OUT, POS, NEG>(
+      this.middlewares.concat(
+        createMiddleware({
+          input: z.object({}),
+          middleware: async () => options,
+        })
+      ),
+      this.resultHandler
+    );
+  }
+
   public build<IN extends IOSchema, OUT extends IOSchema, M extends Method>({
     input,
     output,


Test patch:
diff --git a/tests/unit/endpoints-factory.spec.ts b/tests/unit/endpoints-factory.spec.ts
index 8a7ea930..68025459 100644
--- a/tests/unit/endpoints-factory.spec.ts
+++ b/tests/unit/endpoints-factory.spec.ts
@@ -1,3 +1,5 @@
+import { Request, Response } from "express";
+import { Logger } from "winston";
 import { createMiddleware, EndpointsFactory, z } from "../../src";
 import { Endpoint } from "../../src/endpoint";
 import { ResultHandlerDefinition } from "../../src/result-handler";
@@ -54,6 +56,37 @@ describe("EndpointsFactory", () => {
     });
   });
 
+  describe(".addOptions()", () => {
+    test("Should create a new factory with an empty-input middleware and the same result handler", async () => {
+      const resultHandlerMock = { handler: jest.fn() };
+      const factory = new EndpointsFactory(
+        resultHandlerMock as unknown as ResultHandlerDefinition<any, any>
+      );
+      const newFactory = factory.addOptions({
+        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.shape).toEqual({});
+      expect(
+        await newFactory["middlewares"][0].middleware({
+          input: {},
+          options: {},
+          request: {} as Request,
+          response: {} as Response,
+          logger: {} as Logger,
+        })
+      ).toEqual({
+        option1: "some value",
+        option2: "other value",
+      });
+      expect(newFactory["resultHandler"]).toStrictEqual(resultHandlerMock);
+    });
+  });
+
   describe(".build()", () => {
     test("Should create an endpoint with simple middleware", () => {
       const middleware = createMiddleware({