From 609526e1f6fd873dbe66996d107219f337dcfdc1 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Sun, 10 Mar 2024 23:54:31 +0100 Subject: [PATCH 01/17] Feat: providing options to Result Handler. --- src/endpoint.ts | 12 +++++++++--- src/result-handler.ts | 1 + src/server-helpers.ts | 2 ++ tests/unit/result-handler.spec.ts | 7 ++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/endpoint.ts b/src/endpoint.ts index e171309e7..61a5a1b3b 100644 --- a/src/endpoint.ts +++ b/src/endpoint.ts @@ -232,14 +232,15 @@ export class Endpoint< request, response, logger, + options, }: { method: Method | AuxMethod; input: Readonly; // Issue #673: input is immutable, since this.inputSchema is combined with ones of middlewares request: Request; response: Response; logger: AbstractLogger; + options: OPT; }) { - const options = {} as OPT; for (const def of this.#middlewares) { if (method === "options" && def.type === "proprietary") { continue; @@ -271,7 +272,6 @@ export class Endpoint< break; } } - return options; } async #parseAndRunHandler({ @@ -308,6 +308,7 @@ export class Endpoint< logger, input, output, + options, }: { error: Error | null; request: Request; @@ -315,6 +316,7 @@ export class Endpoint< logger: AbstractLogger; input: FlatObject; output: FlatObject | null; + options: OPT; }) { try { await this.#resultHandler.handler({ @@ -324,6 +326,7 @@ export class Endpoint< response, logger, input, + options, }); } catch (e) { lastResortHandler({ @@ -348,6 +351,7 @@ export class Endpoint< siblingMethods?: Method[]; }) { const method = getActualMethod(request); + const options = {} as OPT; let output: FlatObject | null = null; let error: Error | null = null; if (config.cors) { @@ -366,12 +370,13 @@ export class Endpoint< } const input = getInput(request, config.inputSources); try { - const options = await this.#runMiddlewares({ + await this.#runMiddlewares({ method, input, request, response, logger, + options, }); if (response.writableEnded) { return; @@ -393,6 +398,7 @@ export class Endpoint< response, error, logger, + options, }); } } diff --git a/src/result-handler.ts b/src/result-handler.ts index c42757d1c..566d17d23 100644 --- a/src/result-handler.ts +++ b/src/result-handler.ts @@ -22,6 +22,7 @@ interface ResultHandlerParams { input: FlatObject | null; /** null in case of errors or failures */ output: FlatObject | null; + options: FlatObject; error: Error | null; request: Request; response: Response; diff --git a/src/server-helpers.ts b/src/server-helpers.ts index 92c0523bf..5c5c91611 100644 --- a/src/server-helpers.ts +++ b/src/server-helpers.ts @@ -31,6 +31,7 @@ export const createParserFailureHandler = response, input: null, output: null, + options: {}, logger: getChildLogger ? await getChildLogger({ request, parent: rootLogger }) : rootLogger, @@ -59,6 +60,7 @@ export const createNotFoundHandler = error, input: null, output: null, + options: {}, }); } catch (e) { lastResortHandler({ diff --git a/tests/unit/result-handler.spec.ts b/tests/unit/result-handler.spec.ts index 8750c0a67..f2c65992c 100644 --- a/tests/unit/result-handler.spec.ts +++ b/tests/unit/result-handler.spec.ts @@ -9,7 +9,7 @@ import { defaultResultHandler, withMeta, } from "../../src"; -import { ApiResponse } from "../../src/api-response"; +import { ApiResponse } from "../../src"; import { metaProp } from "../../src/metadata"; import { describe, expect, test, vi } from "vitest"; import { @@ -51,6 +51,7 @@ describe("ResultHandler", () => { request: requestMock as unknown as Request, response: responseMock as unknown as Response, logger: loggerMock, + options: {}, }); expect(loggerMock.error).toHaveBeenCalledTimes(1); expect(loggerMock.error.mock.calls[0][0]).toMatch( @@ -86,6 +87,7 @@ describe("ResultHandler", () => { ), input: { something: 453 }, output: { anything: 118 }, + options: {}, request: requestMock as unknown as Request, response: responseMock as unknown as Response, logger: loggerMock, @@ -103,6 +105,7 @@ describe("ResultHandler", () => { error: createHttpError(404, "Something not found"), input: { something: 453 }, output: { anything: 118 }, + options: {}, request: requestMock as unknown as Request, response: responseMock as unknown as Response, logger: loggerMock, @@ -120,6 +123,7 @@ describe("ResultHandler", () => { error: null, input: { something: 453 }, output: { anything: 118, items: ["One", "Two", "Three"] }, + options: {}, request: requestMock as unknown as Request, response: responseMock as unknown as Response, logger: loggerMock, @@ -165,6 +169,7 @@ describe("ResultHandler", () => { error: null, input: { something: 453 }, output: { anything: 118 }, + options: {}, request: requestMock as unknown as Request, response: responseMock as unknown as Response, logger: loggerMock, From 00dee95d7302621af0022b3e4fcf76330857d8c4 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Mon, 11 Mar 2024 00:00:41 +0100 Subject: [PATCH 02/17] Minor: jsdoc. --- src/result-handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/result-handler.ts b/src/result-handler.ts index 566d17d23..683048ace 100644 --- a/src/result-handler.ts +++ b/src/result-handler.ts @@ -22,6 +22,7 @@ interface ResultHandlerParams { input: FlatObject | null; /** null in case of errors or failures */ output: FlatObject | null; + /** can be empty: check presence of the required property using "in" operator */ options: FlatObject; error: Error | null; request: Request; From 3fe8c36e14b2ce924380aa3a486ee06dd3940e4f Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Mon, 11 Mar 2024 00:08:10 +0100 Subject: [PATCH 03/17] Ref: better types regarding OPT handling. --- src/endpoint.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/endpoint.ts b/src/endpoint.ts index 61a5a1b3b..84d82f601 100644 --- a/src/endpoint.ts +++ b/src/endpoint.ts @@ -239,7 +239,7 @@ export class Endpoint< request: Request; response: Response; logger: AbstractLogger; - options: OPT; + options: FlatObject; }) { for (const def of this.#middlewares) { if (method === "options" && def.type === "proprietary") { @@ -316,7 +316,7 @@ export class Endpoint< logger: AbstractLogger; input: FlatObject; output: FlatObject | null; - options: OPT; + options: FlatObject; }) { try { await this.#resultHandler.handler({ @@ -351,7 +351,7 @@ export class Endpoint< siblingMethods?: Method[]; }) { const method = getActualMethod(request); - const options = {} as OPT; + const options: Partial = {}; let output: FlatObject | null = null; let error: Error | null = null; if (config.cors) { @@ -386,7 +386,11 @@ export class Endpoint< return; } output = await this.#parseOutput( - await this.#parseAndRunHandler({ input, options, logger }), + await this.#parseAndRunHandler({ + input, + logger, + options: options as OPT, // ensured the complete OPT by writableEnded condition and try-catch + }), ); } catch (e) { error = makeErrorFromAnything(e); From a96b73d3441c248ed792f023f1e7a144660225d9 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Mon, 11 Mar 2024 00:11:47 +0100 Subject: [PATCH 04/17] Ref: better types regarding OPT handling (2). --- src/endpoint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/endpoint.ts b/src/endpoint.ts index 84d82f601..04b7038ea 100644 --- a/src/endpoint.ts +++ b/src/endpoint.ts @@ -239,7 +239,7 @@ export class Endpoint< request: Request; response: Response; logger: AbstractLogger; - options: FlatObject; + options: Partial; }) { for (const def of this.#middlewares) { if (method === "options" && def.type === "proprietary") { @@ -316,7 +316,7 @@ export class Endpoint< logger: AbstractLogger; input: FlatObject; output: FlatObject | null; - options: FlatObject; + options: Partial; }) { try { await this.#resultHandler.handler({ From c2db764bda1b80d6b9fafe126a6908097335e873 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Mon, 11 Mar 2024 12:31:28 +0100 Subject: [PATCH 05/17] Testing the feature. --- tests/unit/endpoint.spec.ts | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/tests/unit/endpoint.spec.ts b/tests/unit/endpoint.spec.ts index 803f9eed1..d32469361 100644 --- a/tests/unit/endpoint.spec.ts +++ b/tests/unit/endpoint.spec.ts @@ -67,6 +67,7 @@ describe("Endpoint", () => { }), middleware: middlewareMock, }); + const resultHandlerSpy = vi.spyOn(defaultResultHandler, "handler"); const factory = new EndpointsFactory(defaultResultHandler).addMiddleware( middlewareDefinitionMock, ); @@ -113,6 +114,15 @@ describe("Endpoint", () => { logger: loggerMock, }); expect(loggerMock.error).toHaveBeenCalledTimes(0); + expect(resultHandlerSpy).toHaveBeenCalledWith({ + error: null, + input: { n: 453 }, + logger: loggerMock, + options: { inc: 454 }, + output: { inc2: 455, str: "453.00", transform: 4 }, + request: requestMock, + response: responseMock, + }); expect(responseMock.status).toHaveBeenCalledWith(200); expect(responseMock.json).toHaveBeenCalledWith({ status: "success", @@ -259,13 +269,12 @@ describe("Endpoint", () => { describe("#handleResult", () => { test("Should handle errors within ResultHandler", async () => { - const factory = new EndpointsFactory( - createResultHandler({ - getPositiveResponse: () => z.object({}), - getNegativeResponse: () => z.object({}), - handler: () => assert.fail("Something unexpected happened"), - }), - ); + const resultHandler = createResultHandler({ + getPositiveResponse: () => z.object({}), + getNegativeResponse: () => z.object({}), + handler: vi.fn(() => assert.fail("Something unexpected happened")), + }); + const factory = new EndpointsFactory(resultHandler); const endpoint = factory.build({ method: "get", input: z.object({}), @@ -274,11 +283,22 @@ describe("Endpoint", () => { }), handler: async () => ({ test: "OK" }), }); - const { loggerMock, responseMock } = await testEndpoint({ endpoint }); + const { loggerMock, responseMock, requestMock } = await testEndpoint({ + endpoint, + }); expect(loggerMock.error).toHaveBeenCalledTimes(1); expect(loggerMock.error.mock.calls[0][0]).toBe( "Result handler failure: Something unexpected happened.", ); + expect(resultHandler.handler).toHaveBeenCalledWith({ + error: null, + logger: loggerMock, + input: {}, + options: {}, + output: { test: "OK" }, + request: requestMock, + response: responseMock, + }); expect(responseMock.status).toHaveBeenCalledTimes(1); expect(responseMock.status.mock.calls[0][0]).toBe(500); expect(responseMock.json).toHaveBeenCalledTimes(0); From 832ff9f905c7f456d92eb3c528a6567e08d6de34 Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Mon, 11 Mar 2024 12:55:25 +0100 Subject: [PATCH 06/17] Updating the dataflow --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index d34803ba4..54c6c6899 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file From 16da377af94ab7ec549b0b27e79edbf6b16cad5a Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Mon, 11 Mar 2024 13:00:56 +0100 Subject: [PATCH 07/17] Adjusting size of the diagram --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index 54c6c6899..c785940ad 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file From 67ec1824d26f486b420212d40566f64451325d9f Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Mon, 11 Mar 2024 18:01:33 +0100 Subject: [PATCH 08/17] More adjustments to the sizing --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index c785940ad..40fb4857c 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file From cef31eda313ca37a1305e91d1cc9635a56263fc5 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Mon, 11 Mar 2024 18:22:21 +0100 Subject: [PATCH 09/17] Readme: Sample for the special needs section. --- README.md | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c66712a8..f32b92dc5 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,10 @@ Start your API server with I/O schema validation and custom middlewares in minut 8. [Connect to your own express app](#connect-to-your-own-express-app) 6. [Special needs](#special-needs) 1. [Different responses for different status codes](#different-responses-for-different-status-codes) - 2. [Array response](#array-response) for migrating legacy APIs - 3. [Headers as input source](#headers-as-input-source) - 4. [Accepting raw data](#accepting-raw-data) + 2. [Resources cleanup](#resources-cleanup) + 3. [Array response](#array-response) for migrating legacy APIs + 4. [Headers as input source](#headers-as-input-source) + 5. [Accepting raw data](#accepting-raw-data) 7. [Integration and Documentation](#integration-and-documentation) 1. [Generating a Frontend Client](#generating-a-frontend-client) 2. [Creating a documentation](#creating-a-documentation) @@ -923,6 +924,42 @@ createResultHandler({ }); ``` +## Resources cleanup + +If some entities (database clients for example) do not care about automatically releasing their resources when their +instances are destroyed (when the function that created them exits), you can do the following. Return these instances +in a middleware so that they become `options` for Endpoint's handler. Those `options` are also available as an argument +to a Result Handler. Create your own one and clean up resources in accordance with the documentation of that software. +The `options` may however be empty or incomplete in case of errors or failures, so it is necessary to check for the +presence of the particular one programmatically. + +```typescript +import { + createResultHandler, + EndpointsFactory, + createMiddleware, +} from "express-zod-api"; + +const resultHandlerWithCleanup = createResultHandler({ + handler: ({ options }) => { + if ("dbClient" in options && options.dbClient) { + (options.dbClient as DBClient).close(); // sample cleanup + } + // your implementation + }, +}); + +const dbProvider = createMiddleware({ + handler: async () => ({ + dbClient: new DBClient(), // sample entity that requires cleanup + }), +}); + +const dbEquippedFactory = new EndpointsFactory( + resultHandlerWithCleanup, +).addMiddleware(dbProvider); +``` + ## Array response Please avoid doing this in new projects: responding with array is a bad practice keeping your endpoints from evolving From a38f4aa317b46413d4d81214a06946b74f4d541f Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Mon, 11 Mar 2024 22:14:24 +0100 Subject: [PATCH 10/17] Improving dataflow schema readbility --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index 40fb4857c..88fa16f2d 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file From 2bfdf55a9e011e1c75a6852953efbb33d9035392 Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Mon, 11 Mar 2024 22:17:03 +0100 Subject: [PATCH 11/17] Better alignment --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index 88fa16f2d..4fd46bb6b 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file From 7b50faae459bdbda08564708515568b21ca12c36 Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Mon, 11 Mar 2024 22:19:05 +0100 Subject: [PATCH 12/17] Dedicated color for output --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index 4fd46bb6b..7c76b097f 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file From e6d025a20a80db83d504d772ac8fe860ff63a841 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Mon, 11 Mar 2024 22:35:04 +0100 Subject: [PATCH 13/17] Changelog: 17.3.0. --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75b04eea0..ac60204c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,41 @@ ## Version 17 +### v17.3.0 + +- Featuring `options` in Result Handler. + - The same ones that come from the middlewares to Endpoint's handler. + - You can use them for cleaning up resources (if required) allocated by the entities created by middlewares. + - Suggested use case: database clients that do not close their connections when their instances are destroyed. + - The `options` coming to Result Handler can be empty or incomplete in case of errors and failures. + +```typescript +import { + createResultHandler, + EndpointsFactory, + createMiddleware, +} from "express-zod-api"; + +const resultHandlerWithCleanup = createResultHandler({ + handler: ({ options }) => { + if ("dbClient" in options && options.dbClient) { + (options.dbClient as DBClient).close(); // sample cleanup + } + // your implementation + }, +}); + +const dbProvider = createMiddleware({ + handler: async () => ({ + dbClient: new DBClient(), // sample entity that requires cleanup + }), +}); + +const dbEquippedFactory = new EndpointsFactory( + resultHandlerWithCleanup, +).addMiddleware(dbProvider); +``` + ### v17.2.1 - Fixed a bug due to which a custom logger instance could be perceived as a simplified `winston` logger config. From be3923aa486959d5a371065a705c9a060f893e80 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Sat, 16 Mar 2024 17:51:12 +0100 Subject: [PATCH 14/17] Readme: moving. --- README.md | 80 +++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 7e697e5e0..c2aa00699 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ Start your API server with I/O schema validation and custom middlewares in minut 8. [Connect to your own express app](#connect-to-your-own-express-app) 6. [Special needs](#special-needs) 1. [Different responses for different status codes](#different-responses-for-different-status-codes) - 2. [Resources cleanup](#resources-cleanup) - 3. [Array response](#array-response) for migrating legacy APIs - 4. [Headers as input source](#headers-as-input-source) - 5. [Accepting raw data](#accepting-raw-data) + 2. [Array response](#array-response) for migrating legacy APIs + 3. [Headers as input source](#headers-as-input-source) + 4. [Accepting raw data](#accepting-raw-data) + 5. [Resources cleanup](#resources-cleanup) 7. [Integration and Documentation](#integration-and-documentation) 1. [Generating a Frontend Client](#generating-a-frontend-client) 2. [Creating a documentation](#creating-a-documentation) @@ -925,42 +925,6 @@ createResultHandler({ }); ``` -## Resources cleanup - -If some entities (database clients for example) do not care about automatically releasing their resources when their -instances are destroyed (when the function that created them exits), you can do the following. Return these instances -in a middleware so that they become `options` for Endpoint's handler. Those `options` are also available as an argument -to a Result Handler. Create your own one and clean up resources in accordance with the documentation of that software. -The `options` may however be empty or incomplete in case of errors or failures, so it is necessary to check for the -presence of the particular one programmatically. - -```typescript -import { - createResultHandler, - EndpointsFactory, - createMiddleware, -} from "express-zod-api"; - -const resultHandlerWithCleanup = createResultHandler({ - handler: ({ options }) => { - if ("dbClient" in options && options.dbClient) { - (options.dbClient as DBClient).close(); // sample cleanup - } - // your implementation - }, -}); - -const dbProvider = createMiddleware({ - handler: async () => ({ - dbClient: new DBClient(), // sample entity that requires cleanup - }), -}); - -const dbEquippedFactory = new EndpointsFactory( - resultHandlerWithCleanup, -).addMiddleware(dbProvider); -``` - ## Array response Please avoid doing this in new projects: responding with array is a bad practice keeping your endpoints from evolving @@ -1028,6 +992,42 @@ const rawAcceptingEndpoint = defaultEndpointsFactory.build({ }); ``` +## Resources cleanup + +If some entities (database clients for example) do not care about automatically releasing their resources when their +instances are destroyed (when the function that created them exits), you can do the following. Return these instances +in a middleware so that they become `options` for Endpoint's handler. Those `options` are also available as an argument +to a Result Handler. Create your own one and clean up resources in accordance with the documentation of that software. +The `options` may however be empty or incomplete in case of errors or failures, so it is necessary to check for the +presence of the particular one programmatically. + +```typescript +import { + createResultHandler, + EndpointsFactory, + createMiddleware, +} from "express-zod-api"; + +const resultHandlerWithCleanup = createResultHandler({ + handler: ({ options }) => { + if ("dbClient" in options && options.dbClient) { + (options.dbClient as DBClient).close(); // sample cleanup + } + // your implementation + }, +}); + +const dbProvider = createMiddleware({ + handler: async () => ({ + dbClient: new DBClient(), // sample entity that requires cleanup + }), +}); + +const dbEquippedFactory = new EndpointsFactory( + resultHandlerWithCleanup, +).addMiddleware(dbProvider); +``` + # Integration and Documentation ## Generating a Frontend Client From e75943e7da7b3750356f4234c6c9ac8a74c268b7 Mon Sep 17 00:00:00 2001 From: Robin Tail Date: Sat, 16 Mar 2024 17:52:42 +0100 Subject: [PATCH 15/17] Readme: sortening. --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c2aa00699..88933141a 100644 --- a/README.md +++ b/README.md @@ -995,11 +995,10 @@ const rawAcceptingEndpoint = defaultEndpointsFactory.build({ ## Resources cleanup If some entities (database clients for example) do not care about automatically releasing their resources when their -instances are destroyed (when the function that created them exits), you can do the following. Return these instances -in a middleware so that they become `options` for Endpoint's handler. Those `options` are also available as an argument -to a Result Handler. Create your own one and clean up resources in accordance with the documentation of that software. -The `options` may however be empty or incomplete in case of errors or failures, so it is necessary to check for the -presence of the particular one programmatically. +instances are destroyed, you can do the following. Return these instances in a middleware so that they become `options` +for Endpoint's handler. Those `options` are also available as an argument to a Result Handler. Create your own one and +clean up resources in accordance with the documentation of that software. The `options` may however be empty or +incomplete in case of errors or failures, so it is necessary to check for their presence programmatically. ```typescript import { From 00c236fdee34c8aa11a04130d57546af6615a4ea Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Sat, 16 Mar 2024 18:07:51 +0100 Subject: [PATCH 16/17] Diagram: sizing and arrangement --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index 7c76b097f..521822d03 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file From 8a4c0da9e7bd760c0d6ae454e893be6d21acebd0 Mon Sep 17 00:00:00 2001 From: Anna Bocharova Date: Sat, 16 Mar 2024 18:11:51 +0100 Subject: [PATCH 17/17] Opacity adjustment --- dataflow.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataflow.svg b/dataflow.svg index 521822d03..aec6684c5 100644 --- a/dataflow.svg +++ b/dataflow.svg @@ -1,4 +1,4 @@ -
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file +
Endpoint
Endpoint
options
options
input
schema
input...
output
schema
output...
handler
handler
Middleware N
Middleware N
options
options
middleware
middleware
input
schema
input...
   Middleware 1
   Middleware 1
middleware
middleware
input
schema
input...
Request
Request
.query
.query
.body
.body
ResultHandler
ResultHandler
Response
Response
error
error
GET & DELETE
GET & DELETE
PUT & PATCH
PUT & PATCH
.files
.files
POST
POST
.params
.params
.method
.method
.headers
.headers
 opt-in
 opt-in
custom only
custom only
Text is not SVG - cannot display
\ No newline at end of file