-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(AsyncMiddleware): Add module (#6)
Co-authored-by: Ryan Cumming <etaoins@gmail.com>
- Loading branch information
Showing
7 changed files
with
301 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# 🐨 Async Middleware 🐨 | ||
|
||
## Introduction | ||
|
||
This add-on wraps an asynchronously-initialised middleware to allow it to be synchronously attached to a Koa application. | ||
This is useful if you want to keep the initialisation of your Koa application synchronous for simplicity and interoperability with tooling like [Koa Cluster], | ||
but you need to perform asynchronous work like [GraphQL schema introspection] in order to build one of the middlewares in your chain. | ||
|
||
[koa cluster]: https://github.com/koajs/cluster | ||
[graphql schema introspection]: https://graphql.org/learn/introspection/ | ||
|
||
## Usage | ||
|
||
```typescript | ||
import { AsyncMiddleware } from 'seek-koala'; | ||
|
||
const initGraphMiddleware: = async () => { | ||
const schema = await introspectSchema(); | ||
|
||
return new GraphServer(schema).getMiddleware(); | ||
}; | ||
|
||
const graphMiddleware = AsyncMiddleware.lazyLoad(initGraphMiddleware); | ||
|
||
app.use(graphMiddleware); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import Koa from 'koa'; | ||
|
||
import { lazyLoad } from './asyncMiddleware'; | ||
|
||
describe('asyncMiddleware', () => { | ||
const makeCtx = (fields: Record<string, unknown> = {}): Koa.Context => | ||
(({ | ||
state: {}, | ||
method: 'GET', | ||
...fields, | ||
} as unknown) as Koa.Context); | ||
|
||
const ctx: Koa.Context = makeCtx(); | ||
const next = jest.fn().mockRejectedValue(new Error('why are you here')); | ||
|
||
it('should cache a successfully initialised inner middleware', async () => { | ||
const innerMiddleware = jest.fn(() => (ctx.status = 201)); | ||
const createInnerMiddleware = jest.fn().mockResolvedValue(innerMiddleware); | ||
const middleware = lazyLoad(createInnerMiddleware); | ||
|
||
await expect(middleware(ctx, next)).resolves.toBe(201); | ||
|
||
expect(createInnerMiddleware).toHaveBeenCalledTimes(1); | ||
expect(innerMiddleware).toHaveBeenCalledTimes(1); | ||
|
||
await expect(middleware(ctx, next)).resolves.toBe(201); | ||
|
||
expect(createInnerMiddleware).toHaveBeenCalledTimes(1); | ||
expect(innerMiddleware).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
it('should retry inner middleware initialisation on failure', async () => { | ||
const err = new Error('middleware initialisation failed!'); | ||
const innerMiddleware = jest.fn(() => (ctx.status = 201)); | ||
const createInnerMiddleware = jest | ||
.fn() | ||
.mockRejectedValueOnce(err) | ||
.mockResolvedValue(innerMiddleware); | ||
const middleware = lazyLoad(createInnerMiddleware); | ||
|
||
await expect(middleware(ctx, next)).rejects.toThrow(err); | ||
|
||
expect(createInnerMiddleware).toHaveBeenCalledTimes(1); | ||
|
||
await expect(middleware(ctx, next)).resolves.toBe(201); | ||
|
||
expect(createInnerMiddleware).toHaveBeenCalledTimes(2); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { Middleware } from 'koa'; | ||
|
||
/** | ||
* Wraps an asynchronously-initialised middleware to allow it to be | ||
* synchronously attached to a Koa application. | ||
* | ||
* This lazy loads the function supplied through the `init` parameter at time of | ||
* request. If initialisation fails, the error is thrown up the chain for | ||
* in-flight requests, and initialisation is retried on the next request. | ||
* | ||
* @param init - Function to asynchronously initialise a middleware | ||
*/ | ||
export const lazyLoad = <State, Context>( | ||
init: () => Promise<Middleware<State, Context>>, | ||
): Middleware<State, Context> => { | ||
let cache: Promise<Middleware<State, Context>> | undefined; | ||
|
||
const initOrInvalidate = async () => { | ||
try { | ||
return await init(); | ||
} catch (err) { | ||
cache = undefined; | ||
|
||
/* eslint-disable-next-line no-throw-literal */ | ||
throw err; | ||
} | ||
}; | ||
|
||
return async (ctx, next) => { | ||
const middleware = await (cache ?? (cache = initOrInvalidate())); | ||
|
||
return middleware(ctx, next) as unknown; | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,8 @@ | ||
import * as MetricsMiddleware from './metricsMiddleware/metricsMiddleware'; | ||
import * as RequestLogging from './requestLogging/requestLogging'; | ||
import * as SecureHeaders from './secureHeaders/secureHeaders'; | ||
import * as TracingHeaders from './tracingHeaders/tracingHeaders'; | ||
import * as VersionMiddleware from './versionMiddleware/versionMiddleware'; | ||
export * as AsyncMiddleware from './asyncMiddleware/asyncMiddleware'; | ||
export * as MetricsMiddleware from './metricsMiddleware/metricsMiddleware'; | ||
export * as RequestLogging from './requestLogging/requestLogging'; | ||
export * as SecureHeaders from './secureHeaders/secureHeaders'; | ||
export * as TracingHeaders from './tracingHeaders/tracingHeaders'; | ||
export * as VersionMiddleware from './versionMiddleware/versionMiddleware'; | ||
|
||
export { AppIdentifier } from './types'; | ||
export { | ||
MetricsMiddleware, | ||
RequestLogging, | ||
SecureHeaders, | ||
TracingHeaders, | ||
VersionMiddleware, | ||
}; |
Oops, something went wrong.