-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(frameworks): added lazy framework to enable users to create inst…
…ance of the app asynchronously
- Loading branch information
Showing
4 changed files
with
213 additions
and
0 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
Empty file.
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,105 @@ | ||
//#region Imports | ||
|
||
import { IncomingMessage, ServerResponse } from 'http'; | ||
import { FrameworkContract } from '../../contracts'; | ||
import { ILogger, createDefaultLogger } from '../../core'; | ||
|
||
//#endregion | ||
|
||
/** | ||
* The framework that asynchronously instantiates your application and forwards the request to the framework as quickly as possible. | ||
* | ||
* @example```typescript | ||
* import express from 'express'; | ||
* import { ServerlessAdapter } from '@h4ad/serverless-adapter'; | ||
* import { ExpressFramework } from '@h4ad/serverless-adapter/lib/frameworks/express'; | ||
* import { LazyFramework } from '@h4ad/serverless-adapter/lib/frameworks/lazy'; | ||
* | ||
* const expressFramework = new ExpressFramework(); | ||
* const factory = async () => { | ||
* const app = express(); | ||
* | ||
* // do some asynchronous stuffs like create the database; | ||
* await new Promise(resolve => setTimeout(resolve, 100); | ||
* | ||
* return app; | ||
* }; | ||
* const framework = new LazyFramework(expressFramework, factory); | ||
* | ||
* export const handler = ServerlessAdapter.new(null) | ||
* .setFramework(framework) | ||
* // set other configurations and then build | ||
* .build(); | ||
* ``` | ||
*/ | ||
export class LazyFramework<TApp> implements FrameworkContract<null> { | ||
//#region Constructor | ||
|
||
/** | ||
* Default Constructor | ||
*/ | ||
constructor( | ||
protected readonly framework: FrameworkContract<TApp>, | ||
protected readonly factory: () => Promise<TApp>, | ||
protected readonly logger: ILogger = createDefaultLogger(), | ||
) { | ||
this.delayedFactory = Promise.resolve() | ||
.then(() => factory()) | ||
.then(app => { | ||
this.cachedApp = app; | ||
}) | ||
.catch((error: Error) => { | ||
// deal with the error only when receive some request | ||
// to be able to return some message to user | ||
this.logger.error( | ||
'SERVERLESS_ADAPTER:LAZY_FRAMEWORK: An error occours during the creation of your app.', | ||
); | ||
this.logger.error(error); | ||
}); | ||
} | ||
|
||
//#endregion | ||
|
||
//#region Protected Properties | ||
|
||
/** | ||
* The cached version of the app | ||
*/ | ||
protected cachedApp?: TApp; | ||
|
||
/** | ||
* The delayed factory to create an instance of the app | ||
*/ | ||
protected readonly delayedFactory: Promise<void>; | ||
|
||
//#endregion | ||
|
||
//#region Public Methods | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public sendRequest( | ||
app: null, | ||
request: IncomingMessage, | ||
response: ServerResponse, | ||
): void { | ||
if (this.cachedApp) | ||
return this.framework.sendRequest(this.cachedApp, request, response); | ||
|
||
this.delayedFactory.then(() => { | ||
if (!this.cachedApp) { | ||
return response.emit( | ||
'error', | ||
new Error( | ||
'SERVERLESS_ADAPTER:LAZY_FRAMEWORK: The instance of the app returned by the factory is not valid, see the logs to learn more.', | ||
), | ||
); | ||
} | ||
|
||
return this.framework.sendRequest(this.cachedApp, request, response); | ||
}); | ||
} | ||
|
||
//#endregion | ||
} |
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,101 @@ | ||
import { | ||
ILogger, | ||
ServerlessRequest, | ||
ServerlessResponse, | ||
waitForStreamComplete, | ||
} from '../../src'; | ||
import { LazyFramework } from '../../src/frameworks/lazy/lazy.framework'; | ||
import { FrameworkMock } from '../mocks/framework.mock'; | ||
|
||
describe(LazyFramework.name, () => { | ||
it('should can lazy create an instance of any app and return the cached version', async () => { | ||
const appInstance = Symbol('Your app'); | ||
|
||
const mockFramework = new FrameworkMock(200, { | ||
data: true, | ||
}); | ||
|
||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
mockFramework.sendRequest = jest.fn(mockFramework.sendRequest); | ||
|
||
const factory = jest.fn( | ||
() => new Promise(resolve => setTimeout(() => resolve(appInstance), 100)), | ||
); | ||
|
||
const framework = new LazyFramework(mockFramework, factory); | ||
|
||
const firstRequest = new ServerlessRequest({ | ||
method: 'POST', | ||
headers: {}, | ||
url: '/users', | ||
}); | ||
const firstResponse = new ServerlessResponse({ | ||
method: 'POST', | ||
}); | ||
|
||
framework.sendRequest(null, firstRequest, firstResponse); | ||
|
||
await waitForStreamComplete(firstResponse); | ||
|
||
expect(framework['factory']).toHaveBeenCalledTimes(1); | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
expect(mockFramework.sendRequest).toHaveBeenLastCalledWith( | ||
appInstance, | ||
firstRequest, | ||
firstResponse, | ||
); | ||
|
||
const secondRequest = new ServerlessRequest({ | ||
method: 'GET', | ||
headers: {}, | ||
url: '/users', | ||
}); | ||
const secondResponse = new ServerlessResponse({ | ||
method: 'GET', | ||
}); | ||
|
||
framework.sendRequest(null, secondRequest, secondResponse); | ||
|
||
await waitForStreamComplete(secondResponse); | ||
|
||
expect(factory).toHaveBeenCalledTimes(1); | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
expect(mockFramework.sendRequest).toHaveBeenCalledTimes(2); | ||
// eslint-disable-next-line @typescript-eslint/unbound-method | ||
expect(mockFramework.sendRequest).toHaveBeenLastCalledWith( | ||
appInstance, | ||
secondRequest, | ||
secondResponse, | ||
); | ||
}); | ||
|
||
it('should throw error if error occours in factory function', async () => { | ||
const mockFramework = new FrameworkMock(200, { | ||
data: true, | ||
}); | ||
|
||
const mockLogger = { error: jest.fn() } as unknown as ILogger; | ||
const error = new Error('Something Wrong Occours'); | ||
|
||
const framework = new LazyFramework( | ||
mockFramework, | ||
() => Promise.reject(error), | ||
mockLogger, | ||
); | ||
|
||
const request = new ServerlessRequest({ | ||
method: 'GET', | ||
headers: {}, | ||
url: '/users', | ||
}); | ||
const response = new ServerlessResponse({ | ||
method: 'GET', | ||
}); | ||
|
||
framework.sendRequest(null, request, response); | ||
|
||
await expect( | ||
async () => await waitForStreamComplete(response), | ||
).rejects.toThrowError('factory is not valid,'); | ||
}); | ||
}); |